Can only capture JSLiteralExpression elements with a PsiReferenceProvider?

Hi,

I'm trying to write a JavaScript plugin that provides references for properties off of this. So for example: this.foo, where foo is defined in an HTML file so it's flagged as unresolved by default.

I register a subclass of JavaScriptReferenceContributor in the plugin.xml file. Then I tried this to see if it would work for a simple case:

@Override
    public void registerReferenceProviders(PsiReferenceRegistrar registrar)
    {
        PsiElementPattern.Capture<JSReferenceExpression> pattern = PlatformPatterns.psiElement(JSReferenceExpression.class);

        registrar.registerReferenceProvider(pattern, new PsiReferenceProvider() {
            @NotNull
            @Override
            public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
                return new PsiReference[0];
            }
        });
    }

However if I set a break point inside getReferencesByElement, it never gets hit. If I change it to just this:

PsiElementPattern.Capture<PsiElement> pattern = PlatformPatterns.psiElement();

Then the break point does git hit, but psiElement is only ever a JSLiteralExpression. Am I missing something?

14 comments

It can be helpful to dump PSI tree to be sure which element to ĺook for  ( you have to start idea with internal features enabled )
From my experience with  XML  reference provides ,  you need to look for leaf lements like XmlAttrubuteValue

0

Correct me if i'm wrong, but XmlAttrubuteValue isn't a PsiNamedElement? So you can't make use of it for a LookupBuilder/ReferenceContributor?
Is there a common way of getting around that problem for Xml references?

0

What's lookup builder? Maybe I need this too? ( this is my forst plugin )

XmlAttributeValue is good for code completion and display of error.  Though poly-References do not display as error when
not resolbvable

0

Yeah, I return a LookupBuilder for my references; You can see an example here under the getVariants() method
It allows you customize a lot of the things about the small intellisense window that appears.

But, if i remember correctly, it requires a PsiNamedElement which makes referencing an XmlAttributeValue hard... I'm still not actually sure how to do it! :)

0

You can always use

  public static LookupElementBuilder create(@NotNull Object lookupObject, @NotNull String lookupString) {

and provide the lookupString explicitly.

0

Thanks Yann, I'll definitely check that out :)
It'll be great if that also automagically allows the user to rename the reference too!

0

Not all elements support injecting references via the reference provider. JSReferenceExpression already has a reference that is resolved according to the JavaScript resolve rules; adding another reference on top of that does not make any sense.

0

LookupElementBuilder is a class used for displaying completion items; it has nothing to do with any other features. To handle rename in your reference, you need to implement the handleElementRename() method.

0

Actually in my case it does make sense. I'm developing custom language plugin for templating system based on Rhino javascript engine. For example JSCallExpression foo.bar() can either reference method in java class, or can be defined elsewhere in the same filetype, typically with 'macros.' prefix (eg. macros.foo = {bar: function(p){...}} ). Another case relates to global variables (JSReferenceExpression) registered in java class. It's not hard to find referenced java method via ClassInheritorsSearch or OverridingMethodsSearch, for prefixed functions I'd propably try to use stub index. And my problem is obvious - I'd like to implement ctrl+click, find usages and rename refactoring in both cases. So is there any other way to inject references to JSCallExpression / JSReferenceExpression than psi.referenceContributor? I found that 'in 13: user-configurable reference injections can be provided via referenceInjector extension point', but it's working only with JSLiteralExpression too.  Any help would be appreciated.

0

Hi Dennis, your solution seems to be promising, but I'll need some more help. The templates I'm writing support for basically consist of html, javascript enclosed in <%, %> markers and some other (at the moment unimportant) stuff. So my language implements TemplateLanguage with HTML as defaultTemplateLang and myFileViewProvider#getLanguages() returns MyTemplate.INSTANCE, JavascriptLanguage.INSTANCE, StdLanguages.HTML. MyFileViewProvider#createFile(lang) looks like this:

ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
if (parserDefinition == null)
{
     return null;
}

Language templateDataLanguage = getTemplateDataLanguage(myManager, myVirtualFile);
if (lang == templateDataLanguage)
{
     PsiFileImpl file = (PsiFileImpl) parserDefinition.createFile(this);
     file.setContentElementType(new TemplateDataElementType("TEMPLATE_MARKUP", MyTemplate.INSTANCE, MyTemplateTypes.T_TEMPLATE_HTML_CODE, MyTemplateTypes.T_OUTER_TEMPLATE_ELEMENT));
     return file;
}
else if (lang == MyTemplate.INSTANCE)
{
     return parserDefinition.createFile(this);
}
else if (lang == JavascriptLanguage.INSTANCE)
{
     PsiFileImpl file = (PsiFileImpl) parserDefinition.createFile(this);     // HERE
     file.setContentElementType(new TemplateDataElementType("TEMPLATE_JS", JavascriptLanguage.INSTANCE, MyTemplateTypes.T_TEMPLATE_JAVASCRIPT_CODE, MyTemplateTypes.T_INNER_TEMPLATE_ELEMENT)
     {...

I'd like to have specific JS dialect for my inner js (MyTemplateTypes.T_TEMPLATE_JAVASCRIPT_CODE). So what I did was the following - I created a new language which extended JSLanguageDialect (similar to AngularJSLanguage) and in myFileViewProvider I replaced JavascriptLanguage.INSTANCE with the new language instance. Problem occurs on line marked 'HERE':

Language: JavaScript != Language: MyTemplateInnerJs: Language: JavaScript != Language: MyTemplateInnerJs
java.lang.AssertionError: Language: JavaScript != Language: MyTemplateInnerJs
     at com.intellij.extapi.psi.PsiFileBase.<init>(PsiFileBase.java:47)
     at com.intellij.lang.javascript.psi.impl.JSFileImpl.<init>(JSFileImpl.java:54)
     at com.intellij.lang.javascript.JavascriptParserDefinition.createFile(JavascriptParserDefinition.java:75)
     at ool.idea.plugin.file.MyTemplateFileViewProvider.createFile(MyTemplateFileViewProvider.java:130
        ...

Which means that parser returns elements of different language than expected. I've noticed, that you have your own lexer and parser for some angular-specific stuff, but I don't think that that's the reason it doesn't work. I've also tried to extend MyTemplate from JSLanguageDialect, which somehow seems to work, but some things break (brace matcher, javascript live templates are in every completion popup etc.), in most of files I get following exception

java.lang.AssertionError: Invalid context: JSLiteralExpression
     at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.getReferencesFromProviders(ReferenceProvidersRegistry.java:62)
     at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.getReferencesFromProviders(ReferenceProvidersRegistry.java:57)
     at com.intellij.lang.javascript.psi.impl.JSLiteralExpressionImpl.createRefs(JSLiteralExpressionImpl.java:68)
     at com.intellij.lang.javascript.psi.impl.JSLiteralExpressionImpl.getReferences(JSLiteralExpressionImpl.java:57)
     at com.intellij.lang.javascript.validation.JSAnnotatingVisitor.checkReferences(JSAnnotatingVisitor.java:207)
     at com.intellij.lang.javascript.validation.JSAnnotatingVisitor.visitJSLiteralExpression(JSAnnotatingVisitor.java:151)
     at com.intellij.lang.javascript.psi.impl.JSLiteralExpressionImpl.accept(JSLiteralExpressionImpl.java:82)
     at com.intellij.lang.javascript.validation.JSAnnotatingVisitor.annotate(JSAnnotatingVisitor.java:104)
     at com.intellij.codeInsight.daemon.impl.DefaultHighlightVisitor.a(DefaultHighlightVisitor.java:160)
        ...

and myReferenceExpressionResolver#doResolve() is never called, in some other files it actually is called (all I do is return super.doResolve();) but the original functionality is broken and I don't have references to anything anymore. The desired behavior is as follows: variables / functions from inner template js never reference anything from js files, functionality or referencing in terms of single file is unchanged, references to JSCallExpression / JSReferenceExpression can be injected. I've tried one more thing which is not working either (but I'd be happy if it did) - resolveScopeEnlarger, which in getAdditionalResolveScope(virtualFile, project) returns LocalScope with all psiFiles included (via specific include directive, e.g. <%@ include_once "file.jsm" %>).

0

As far as I understand you have 3 languages in your file view provider: HTML for handling HTML sequences, MyTemplate for handling <% %> and JavaScript for <%%> content.
Main idea here is that you'll need to have some language extending JSLanguageDialect instead of JavascriptLanguage.INSTANCE (somethink like AngularJSLanguage).
Then, you can provide your dialect specific factory like AngularJS plugin:

 
<dialectSpecificHandlersFactory language="AngularJS" implementationClass="org.angularjs.codeInsight.AngularJSSpecificHandlersFactory"/>
0

That's exactly what I did. I finally got over the first exception by providing parser definition for inner js language, where I had getFileNodeType() return new JSFileElementType(MyTemplateInnerJs.INSTANCE) and now it works. Anyway thanks, you really helped me a lot.

0

Sorry, yes, I forgot to mention that.
By the way, we've done a big refactoring of JavaScript code insight engine for WebStorm 10/IntelliJ 14.1, which resulted in greap JS API changes so if you want to get your plugin working before public EAP is started, please contact me: dennis.ushakov@jetbrains.com

0

Please sign in to leave a comment.