Skip to main content
Jump to: navigation, search

Difference between revisions of "Extending PDT"

m (Extending PDT 2.2 moved to Extending PDT: Information is also relevant to current PDT version and might confuse possible contributors.)
Line 80: Line 80:
 
}
 
}
 
</source>  
 
</source>  
<pre>public class DummyGoalEvaluator extends GoalEvaluator {
+
<source lang="java">
 +
 
 +
public class DummyGoalEvaluator extends GoalEvaluator {
 
   private String className;
 
   private String className;
  
Line 101: Line 103:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>  
 
That's all. In case if 'MyClass' exists Code Assist and Navigation will work out of the box since they are both based on the type inference engine.  
 
That's all. In case if 'MyClass' exists Code Assist and Navigation will work out of the box since they are both based on the type inference engine.  
  
Line 118: Line 120:
  
 
First of all, we need to define the completion context - a class that verifies that the cursor is positioned after the object call operator and that the object type is a View.  
 
First of all, we need to define the completion context - a class that verifies that the cursor is positioned after the object call operator and that the object type is a View.  
<pre>public class XYZCompletionContext extends ClassMemberContext {
+
<source lang="java">
 +
 
 +
public class XYZCompletionContext extends ClassMemberContext {
  
 
   public boolean isValid(ISourceModule sourceModule, int offset,
 
   public boolean isValid(ISourceModule sourceModule, int offset,
Line 153: Line 157:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>
 +
 
Register new context in the completion engine:  
 
Register new context in the completion engine:  
<pre>public class XYZContextResolver extends CompletionContextResolver
+
<source lang="java">
 +
 
 +
public class XYZContextResolver extends CompletionContextResolver
 
     implements ICompletionContextResolver {
 
     implements ICompletionContextResolver {
  
Line 162: Line 169:
 
     }
 
     }
 
}
 
}
</pre>  
+
</source>
 +
 
Add the completion strategy that does the actual job - adds "helperName" method to the completion list:  
 
Add the completion strategy that does the actual job - adds "helperName" method to the completion list:  
<pre>public class XYZCompletionStrategy
+
 
 +
<source lang="java">
 +
 
 +
public class XYZCompletionStrategy
 
     extends ClassMembersStrategy implements ICompletionStrategy {
 
     extends ClassMembersStrategy implements ICompletionStrategy {
  
Line 190: Line 201:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>  
 +
 
 
Create an association between the completion context and completion strategy:  
 
Create an association between the completion context and completion strategy:  
<pre>public class XYZCompletionStrategyFactory implements ICompletionStrategyFactory {
+
 
 +
<source lang="java">  
 +
 
 +
public class XYZCompletionStrategyFactory implements ICompletionStrategyFactory {
  
 
   public ICompletionStrategy[] create(ICompletionContext[] contexts) {
 
   public ICompletionStrategy[] create(ICompletionContext[] contexts) {
Line 205: Line 220:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>
 +
 
And, finally, plugin.xml contributions:  
 
And, finally, plugin.xml contributions:  
  
Line 227: Line 243:
  
 
Implement the indexing visitor:  
 
Implement the indexing visitor:  
<pre>public class XYZIndexingVisitorExtension  
+
 
 +
<source lang="java">
 +
 
 +
public class XYZIndexingVisitorExtension  
 
     extends PhpIndexingVisitorExtension {
 
     extends PhpIndexingVisitorExtension {
  
Line 292: Line 311:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>  
 +
 
 
Note: this visitor is very similar to the structured model contribution [[Extending PDT 2.2#SModelVisitor|visitor]].  
 
Note: this visitor is very similar to the structured model contribution [[Extending PDT 2.2#SModelVisitor|visitor]].  
  
Line 312: Line 332:
  
 
<span id="Snippet1">Code snippet #1</span>  
 
<span id="Snippet1">Code snippet #1</span>  
<pre>/**
+
 
 +
<source lang="java">/**
 
  * Please don't judge me for this code, it's for the sake of example only
 
  * Please don't judge me for this code, it's for the sake of example only
 
  * (even though I've seen PHP code like this in real PHP applications&nbsp;:-])
 
  * (even though I've seen PHP code like this in real PHP applications&nbsp;:-])
Line 331: Line 352:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>
 +
 
Let's say we wish to see select(), insert() and delete() methods in the Outline and PHP Explorer under MySQL_DB class. We'll need to extend the following extension point:  
 
Let's say we wish to see select(), insert() and delete() methods in the Outline and PHP Explorer under MySQL_DB class. We'll need to extend the following extension point:  
  
Line 346: Line 368:
 
<span id="SModelVisitor">
 
<span id="SModelVisitor">
 
Source element requester extension implementation:
 
Source element requester extension implementation:
</span>  
+
</span>
<pre>public class XYZSourceElementRequestor extends
+
 +
<source lang="java">  
 +
 
 +
public class XYZSourceElementRequestor extends
 
     PHPSourceElementRequestorExtension {
 
     PHPSourceElementRequestorExtension {
  
Line 416: Line 441:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>
 +
 
Voila! Methods are added into PHP Explorer and Outline views, and even clicking on them changes focus in the editor:  
 
Voila! Methods are added into PHP Explorer and Outline views, and even clicking on them changes focus in the editor:  
  
Line 431: Line 457:
 
to contribute a custom buildpath container using the phpTreeContentProviders extension point:
 
to contribute a custom buildpath container using the phpTreeContentProviders extension point:
  
<pre>
+
<source lang="xml">
  
 
   <extension point="org.eclipse.php.ui.phpTreeContentProviders">
 
   <extension point="org.eclipse.php.ui.phpTreeContentProviders">
Line 437: Line 463:
 
   </extension>
 
   </extension>
  
</pre>
+
</source>
  
 
All you need to do is to create the labelProvider and contentProvider elements, by implementing an org.eclipse.jface.viewers.ILabelProvider and an org.eclipse.jface.viewers.ITreeContentProvider:
 
All you need to do is to create the labelProvider and contentProvider elements, by implementing an org.eclipse.jface.viewers.ILabelProvider and an org.eclipse.jface.viewers.ITreeContentProvider:
  
  
<pre>
+
<source lang="java">
  
 
public class PackageTreeLabelProvider extends LabelProvider
 
public class PackageTreeLabelProvider extends LabelProvider
Line 470: Line 496:
 
}
 
}
  
</pre>
+
</source>
  
  
<pre>
+
<source lang="java">
  
 
public class PackageTreeContentProvider extends ScriptExplorerContentProvider
 
public class PackageTreeContentProvider extends ScriptExplorerContentProvider
Line 531: Line 557:
 
}
 
}
  
</pre>
+
</source>
  
  
Line 548: Line 574:
  
 
Let's say we need to add [http://pecl.php.net/package/newt PHP-newt] extension functions to the Code Assist. First, create the language model provider class:  
 
Let's say we need to add [http://pecl.php.net/package/newt PHP-newt] extension functions to the Code Assist. First, create the language model provider class:  
<pre>public class NewtLanguageModelProvider  
+
 
 +
<source lang="java">
 +
 
 +
public class NewtLanguageModelProvider  
 
     implements ILanguageModelProvider {
 
     implements ILanguageModelProvider {
  
Line 559: Line 588:
 
   }
 
   }
 
}
 
}
</pre>  
+
</source>
 +
 
Please not that this interface has been changed since PDT 2.1 according to [https://bugs.eclipse.org/bugs/show_bug.cgi?id=291729 bug 291729]  
 
Please not that this interface has been changed since PDT 2.1 according to [https://bugs.eclipse.org/bugs/show_bug.cgi?id=291729 bug 291729]  
  

Revision as of 03:28, 10 October 2012

Purpose

There are different purposes for extending PDT. One of them is adding support for specific PHP framework to the IDE features like: Code Assist, Navigation (CTRL + click), Presentation (Outline, PHP Explorer). In this document we'll describe how to achieve these goals using PDT extension points. If you have any questions regarding this document, please ask them on PDT-Dev mailing list.

Extending

Code Assist

Type inference hinting

Suppose your framework uses the following language structure for object instantiation:

$myObject = ClassRegistry::init('MyClass');

In this case PDT type inference engine is unable to detect the type of $myObject variable, so we'll have to add a specific rule that helps him. The following extension point allows to provide additional rules to the PHP type inference engine:

 org.eclipse.php.core.goalEvaluatorFactories

For our example what we need to contribute is:

<extension point="org.eclipse.php.core.goalEvaluatorFactories">
  <factory
      class="com.xyz.php.fmwrk.XYZGoalEvaluatorFactory"
      priority="100">
  </factory>
</extension>

Please note the priority is set to 100 in order to override the default PHP goal evaluator (its priority is 10).

public class XYZGoalEvaluatorFactory implements IGoalEvaluatorFactory {
 
  public GoalEvaluator createEvaluator(IGoal goal) {
    Class<?> goalClass = goal.getClass();
 
    // We're overriding only the expression type goal:
    if (goalClass == ExpressionTypeGoal.class) {
      ASTNode expression = ((ExpressionTypeGoal) goal).getExpression();
 
      // Check the expression AST node type
      if (expression instanceof StaticMethodInvocation) {
        StaticMethodInvocation inv = (StaticMethodInvocation) expression;
        ASTNode reciever = inv.getReceiver();
 
        // Check that the class name is 'CallRegistry':
        if (reciever instanceof SimpleReference
            && "ClassRegistry".equals(((SimpleReference) reciever)
                .getName())) {
 
          // Check that the method name is 'init'
          if ("init".equals(inv.getCallName().getName())) {
 
            // Take the first call argument:
            List arguments = inv.getArgs().getChilds();
            if (arguments.size() == 1) {
              Object first = arguments.get(0);
 
              if (first instanceof Scalar
                 && ((Scalar) first).getScalarType() == Scalar.TYPE_STRING) {
 
                String className = ((Scalar) first).getValue();
 
                // Return the evaluated type through dummy
                // evaluator
                return new DummyGoalEvaluator(goal, className);
              }
            }
          }
        }
      }
    }
 
    // Give the control to the default PHP goal evaluator
    return null;
  }
}
public class DummyGoalEvaluator extends GoalEvaluator {
  private String className;
 
  public DummyGoalEvaluator(IGoal goal, String className) {
    super(goal);
    this.className = className;
  }
 
  public Object produceResult() {
    return new PHPClassType(className);
  }
 
  public IGoal[] init() {
    return null;
  }
 
  public IGoal[] subGoalDone(IGoal subgoal, Object result,
      GoalState state) {
    return null;
  }
}

That's all. In case if 'MyClass' exists Code Assist and Navigation will work out of the box since they are both based on the type inference engine.

Code assist strategies

Some PHP frameworks provide class fields or methods that are not declared explicitly in a code. For instance, Zend Framework view helpers can be accessed from the view class using:

 $this->helperName()

Behind the scenes, view loads the Zend_View_Helper_HelperName class (note the naming convention), creates an object instance of it, and calls its helperName() method.

In order to provide Code Assist for "helperName" after "$this->" we'll need to extend the following extensions:

 org.eclipse.php.core.completionContextResolvers
org.eclipse.php.core.completionStrategyFactories

First of all, we need to define the completion context - a class that verifies that the cursor is positioned after the object call operator and that the object type is a View.

public class XYZCompletionContext extends ClassMemberContext {
 
  public boolean isValid(ISourceModule sourceModule, int offset,
      CompletionRequestor requestor) {
 
    // Call to super to verify that cursor is in the class member call
    // context
    if (super.isValid(sourceModule, offset, requestor)) {
 
      // This context only supports "-&gt;" trigger type (not the "::")
      if (getTriggerType() == Trigger.OBJECT) {
 
        IType[] recieverClass = getLhsTypes();
        // recieverClass contains types for the expression from the left
        // side of "-&gt;"
        for (IType c&nbsp;: recieverClass) {
          if (!isViewer(c)) {
            return false;
          }
        }
        return true;
      }
    }
 
    return false;
  }
 
  /**
   * Check that the type of the class is Viewer
   */
  private boolean isViewer(IType type) {
    // XXX: add more sophisticated check
    return "Viewer".equalsIgnoreCase(type.getElementName());
  }
}

Register new context in the completion engine:

public class XYZContextResolver extends CompletionContextResolver
    implements ICompletionContextResolver {
 
    public ICompletionContext[] createContexts() {
        return new ICompletionContext[] { new XYZCompletionContext(); };
    }
}

Add the completion strategy that does the actual job - adds "helperName" method to the completion list:

public class XYZCompletionStrategy
    extends ClassMembersStrategy implements ICompletionStrategy {
 
  public XYZCompletionStrategy(ICompletionContext context) {
    super(context);
  }
 
  public void apply(ICompletionReporter reporter) throws Exception {
    XYZCompletionContext context = (XYZCompletionContext) getContext();
    IType type = context.getLhsTypes()[0];
    for (String helperName&nbsp;: getHelperNames()) {
      // Create fake model element for the helper method
      FakeMethod fakeHelperMethod = new FakeMethod((ModelElement) type,
          helperName);
 
      // Report the method to the completion proposals list
      reporter.reportMethod(fakeHelperMethod, "()",
          getReplacementRange(context));
    }
  }
 
  private String[] getHelperNames() {
    // XXX: calculate existing helper names from code
    return new String[] { "helperName" };
  }
}

Create an association between the completion context and completion strategy:

 
 
public class XYZCompletionStrategyFactory implements ICompletionStrategyFactory {
 
  public ICompletionStrategy[] create(ICompletionContext[] contexts) {
    List&lt;ICompletionStrategy&gt; result = new LinkedList&lt;ICompletionStrategy&gt;();
    for (ICompletionContext context&nbsp;: contexts) {
      if (context.getClass() == XYZCompletionContext.class) {
        result.add(new XYZCompletionStrategy(context));
      }
    }
    return (ICompletionStrategy[]) result
        .toArray(new ICompletionStrategy[result.size()]);
  }
}

And, finally, plugin.xml contributions:

  <extension point="org.eclipse.php.core.completionStrategyFactories">
    <factory class="com.xyz.php.framework.XYZCompletionStrategyFactory"/>
 </extension>
 
 <extension point="org.eclipse.php.core.completionContextResolvers">
    <resolver class="com.xyz.php.framework.XYZContextResolver"/>
 </extension>

CTRL + click

Contributing to index

When you click while holding CTRL pressed on some PHP element in an editor, or when you ask for Code Assist by pressing CTRL+Space selection engine (or completion engine in case of Code Assist) tries to resolve PHP elements by accessing index. Index contains all the PHP element declarations and references that present in a workspace. The following example indexes non-existing select(), delete() and insert() methods from code snippet #1.

Extension point that we gonna use is:

 org.eclipse.php.core.phpIndexingVisitors

Implement the indexing visitor:

public class XYZIndexingVisitorExtension 
    extends PhpIndexingVisitorExtension {
 
  private ClassDeclaration currentClass;
  private MethodDeclaration currentMethod;
  private Set&lt;Scalar&gt; deferredMethods = new HashSet&lt;Scalar&gt;();
 
  public boolean visit(TypeDeclaration s) throws Exception {
    if (s instanceof ClassDeclaration) {
      currentClass = (ClassDeclaration) s;
    }
    return true;
  }
 
  public boolean endvisit(TypeDeclaration s) throws Exception {
    for (Scalar method&nbsp;: deferredMethods) {
      int start = method.sourceStart();
      int length = method.sourceEnd() - method.sourceStart();
      String name = method.getValue().replaceAll("['\"]", "");
 
      modifyDeclaration(method, new DeclarationInfo(IModelElement.METHOD,
          Modifiers.AccPublic, start, length, start, length, name,
          null, null, currentClass.getName()));
    }
    currentClass = null;
    deferredMethods.clear();
    return true;
  }
 
  public boolean visit(MethodDeclaration s) throws Exception {
    if (currentClass&nbsp;!= null &amp;&amp; "__call".equals(s.getName())) {
      currentMethod = s;
    }
    return true;
  }
 
  public boolean endvisit(MethodDeclaration s) throws Exception {
    currentMethod = null;
    return true;
  }
 
  public boolean visit(Statement s) throws Exception {
    if (s instanceof IfStatement &amp;&amp; currentClass&nbsp;!= null
        &amp;&amp; currentMethod&nbsp;!= null) {
 
      Expression condition = ((IfStatement) s).getCondition();
      if (condition instanceof InfixExpression) {
 
        InfixExpression infixExp = (InfixExpression) condition;
        if (infixExp.getOperatorType() == InfixExpression.OP_IS_EQUAL) {
 
          Expression rightExp = infixExp.getRight();
          if (rightExp instanceof Scalar) {
 
            Scalar scalar = (Scalar) rightExp;
            if (scalar.getScalarType() == Scalar.TYPE_STRING) {
              deferredMethods.add(scalar);
            }
          }
        }
      }
    }
    return true;
  }
}

Note: this visitor is very similar to the structured model contribution visitor.

Finally, register this class in plugin.xml:

 <extension point="org.eclipse.php.core.phpIndexingVisitors">
  <visitor class="com.xyz.php.framework.XYZIndexingVisitorExtension" />
</extension>

Outline and PHP Explorer

Adding additional nodes to the PHP explorer can be achieved in 2 different ways.

Contributing to the structured model

Sometimes you need to present some elements that don't really exist. For example, you may want to present fields and methods that are processed using __get() or __call() magic methods:

Code snippet #1

/**
 * Please don't judge me for this code, it's for the sake of example only
 * (even though I've seen PHP code like this in real PHP applications&nbsp;:-])
 */
class MySQL_DB {
 
   public function __call($name, $arguments) {
      if ($name == "select") {
         mysql_query($this-&gt;db, "SELECT * FROM ".$arguments[0]);
         // ...
      }
      else if ($name == "insert") {
         // ...
      }
      else if ($name == "delete") {
         // ...
      }
   }
}

Let's say we wish to see select(), insert() and delete() methods in the Outline and PHP Explorer under MySQL_DB class. We'll need to extend the following extension point:

 org.eclipse.php.core.phpSourceElementRequestors

plugin.xml contribution:

 <extension point="org.eclipse.php.core.phpSourceElementRequestors">
    <requestor
          class="com.xyz.php.framework.XYZSourceElementRequestor">
    </requestor>
 </extension>

Source element requester extension implementation:

 
 
public class XYZSourceElementRequestor extends
    PHPSourceElementRequestorExtension {
 
  private ClassDeclaration currentClass;
  private MethodDeclaration currentMethod;
  private Set&lt;Scalar&gt; deferredMethods = new HashSet&lt;Scalar&gt;();
 
  public boolean visit(TypeDeclaration s) throws Exception {
    if (s instanceof ClassDeclaration) {
      currentClass = (ClassDeclaration) s;
    }
    return true;
  }
 
  public boolean endvisit(TypeDeclaration s) throws Exception {
    currentClass = null;
 
    for (Scalar method&nbsp;: deferredMethods) {
      ISourceElementRequestor.MethodInfo methodInfo =
        new ISourceElementRequestor.MethodInfo();
 
      methodInfo.name = method.getValue().replaceAll("['\"]", "");
      methodInfo.modifiers = Modifiers.AccPublic;
      methodInfo.nameSourceStart = method.sourceStart();
      methodInfo.nameSourceEnd = method.sourceEnd();
      methodInfo.declarationStart = method.sourceStart();
      fRequestor.enterMethod(methodInfo);
      fRequestor.exitMethod(method.sourceEnd());
    }
    deferredMethods.clear();
    return true;
  }
 
  public boolean visit(MethodDeclaration s) throws Exception {
    if (currentClass&nbsp;!= null &amp;&amp; "__call".equals(s.getName())) {
      currentMethod = s;
    }
    return true;
  }
 
  public boolean endvisit(MethodDeclaration s) throws Exception {
    currentMethod = null;
    return true;
  }
 
  public boolean visit(Statement s) throws Exception {
    if (s instanceof IfStatement &amp;&amp; currentClass&nbsp;!= null
        &amp;&amp; currentMethod&nbsp;!= null) {
 
      Expression condition = ((IfStatement) s).getCondition();
      if (condition instanceof InfixExpression) {
 
        InfixExpression infixExp = (InfixExpression) condition;
        if (infixExp.getOperatorType() == InfixExpression.OP_IS_EQUAL) {
 
          Expression rightExp = infixExp.getRight();
          if (rightExp instanceof Scalar) {
 
            Scalar scalar = (Scalar) rightExp;
            if (scalar.getScalarType() == Scalar.TYPE_STRING) {
              deferredMethods.add(scalar);
            }
          }
        }
      }
    }
    return true;
  }
}

Voila! Methods are added into PHP Explorer and Outline views, and even clicking on them changes focus in the editor:

Requestor1.png

Contributing a PHPTreeContentProvider

Since PDT 3.1.1. additional nodes can be added to the PHP explorer using theorg.eclipse.php.ui.phpTreeContentProviders extension point. Let's say you want

to contribute a custom buildpath container using the phpTreeContentProviders extension point:

   <extension point="org.eclipse.php.ui.phpTreeContentProviders">
      <provider labelProvider="com.dubture.composer.core.ui.explorer.PackageTreeLabelProvider" contentProvider="com.dubture.composer.core.ui.explorer.PackageTreeContentProvider"/>
   </extension>

All you need to do is to create the labelProvider and contentProvider elements, by implementing an org.eclipse.jface.viewers.ILabelProvider and an org.eclipse.jface.viewers.ITreeContentProvider:


public class PackageTreeLabelProvider extends LabelProvider
{
 
    @Override
    public String getText(Object element)
    {
        if (element instanceof PackagePath) {
            PackagePath path = (PackagePath) element;
            return path.getPackageName();
        }
 
        return null;
    }
 
    @Override
    public Image getImage(Object element)
    {
        if (element instanceof PackagePath) {
            return PHPPluginImages
                    .get(PHPPluginImages.IMG_OBJS_LIBRARY);
        }
 
        return null;
    }
}


public class PackageTreeContentProvider extends ScriptExplorerContentProvider
{
    public PackageTreeContentProvider()
    {
        super(true);
    }
 
 
    @Override
    public Object[] getChildren(Object parentElement)
    {
        if (parentElement instanceof PackagePath) {
 
            PackagePath pPath = (PackagePath) parentElement;
            IScriptProject scriptProject = pPath.getProject();
            IBuildpathEntry entry = pPath.getEntry();
 
            try {
 
                IProjectFragment[] allProjectFragments;
                allProjectFragments = scriptProject.getAllProjectFragments();
                for (IProjectFragment fragment : allProjectFragments) {
                    if (fragment instanceof ExternalProjectFragment) {
                        ExternalProjectFragment external = (ExternalProjectFragment) fragment;
                        if (external.getBuildpathEntry().equals(entry)) {
                            return super.getChildren(external);
                        }
                    }
                }
            } catch (ModelException e) {
                Logger.logException(e);
            }
        } else if (parentElement instanceof ComposerBuildpathContainer) {
            ComposerBuildpathContainer container = (ComposerBuildpathContainer) parentElement;
 
            IAdaptable[] children = container.getChildren();
 
            if (children == null || children.length == 0) {
                return NO_CHILDREN;
            }
 
            return children;
        } else if (parentElement instanceof IScriptProject) {
            try {
                IProject project = ((IScriptProject)parentElement).getProject();
                if (project.hasNature(ComposerNature.NATURE_ID)) {
                    return new Object[]{new ComposerBuildpathContainer((IScriptProject) parentElement)};
                }
            } catch (Exception e) {
                Logger.logException(e);
            }
        }
        return null;
    }
}


PhpTreeContentProvider.png

The example has been taken from the Composer Eclipse Plugin


Language library contributions

PHP language library contains builtin classes, functions and constants - you can see it under PHP project as a "PHP Language Library" node (see image above). This model is used in Code Assist and PHP Function View. By default, it contains only predefined set of PHP extensions. There's a way to contribute more PHP language fragments to this library. The key extension point is:

 org.eclipse.php.core.languageModelProviders

Let's say we need to add PHP-newt extension functions to the Code Assist. First, create the language model provider class:

public class NewtLanguageModelProvider 
    implements ILanguageModelProvider {
 
  public IPath getPath(IScriptProject project) {
    return new Path("resources/newt");
  }
 
  public Plugin getPlugin() {
    return MyPlugin.getDefault();
  }
}

Please not that this interface has been changed since PDT 2.1 according to bug 291729

Register this class in plugin.xml:

 <extension point="org.eclipse.php.core.languageModelProviders">
  <provider class="com.xyz.php.framework.NewtLanguageModelProvider" />
</extension>

Create resources/newt folder in your plug-in directory, and add this folder to the build.properties, then place PHP stub files under this folder.

Error reporting

Sometimes there's a need to add custom PHP source code validator that reports additional errors or warnings to user. Please learn how to do that from this plugin. This example adds validator that checks PHP code for places possibly vulnerable for XSS attack, and reports warnings to developer.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.