Treat part of JS identifier specially depending on context

Answered

Hi, I'm working on Svelte plugin (https://github.com/tomblachut/svelte-intellij

Svelte has syntax for subscribing to stores. It repurposes identifiers on the compiler level and looks like this:

<script>
  import {foo} from 'foo';
console.log($foo);
</script>

<div>{$foo}</div>


(Assume that I have JS properly injected or lazily parsed inside those curly brackets)

Basically dollar works as unary operator that cannot be followed by whitespace. You can't declare variable names starting with $ in Svelte files[0].
I would like to link usages of foo to their declarations and stop highlighting $foo as unresolved variable.

My initial idea was to extend JS parser and lexer a bit. I created LayeredLexer that breaks IDENTIFIER tokens into DOLLAR and IDENTIFIER. Next I wanted to change parser to interpret such combination as a call to internal function[1] or just valid identifier to start.

There's the complication though. You can actually use $-prefixed identifiers inside function arguments (eg. const x = $x => $x.toUpperCase();), in other words everything would need to work depending on context.

Now I'm thinking that I could swap IDENTIFIER tokens with custom designed lazily parsable tokens and then parse them depending on parent AST or PSI nodes. I imagine that approach wouldn't require me to extend JS language too much.

Or maybe the better approach is to inject reference into substring of identifier with ReferenceContributor or something like that? What about disabling $ part / disabling JSReferenceExpression?

I'm asking mainly to limit time wasted on experiments because I'm probably missing many details. Any pointers are highly appreciated. Cheers.

[0] There's actually exception to this rule, $$props global but I handled that on the lexer level.
[1] Store can be thought of as one value container and $ operator is a "get" function with effective signature of <T>(store: Store<T>) => T. Would be nice to preserve that property in the plugin so code insight provides proper completions etc.. Sadly I have no idea how to do that.

1 comment
Comment actions Permalink

Hi Tomasz,

The easiest approach is to implement PsiReferenceContributor, we added it for WEB-24393. Built-in javascript resolver won't start if PsiReferenceContributor returns a result. Probably, find usages still won't work, because it scans by words initially, and 'foo' is treated differently with '$foo'. You may take a look at FindUsagesProvider#getWordsScanner, but I don't guarantee that this is the best approach to fix this case. Rename will also require some tweaking. 

1

Please sign in to leave a comment.