Implementing PsiReference for fully qualified identifiers in custom language

Hi,

 

I'm working on a custom language plugin which has the ability to refer to a declaration by its fully qualified identifier in any reference.  Currently the entire fully qualified string is treated as a single reference: "ancestor.parent.declaration", this works because "ancestor.parent.declaration" is parsed as a single token and PsiElement and is what is returned for its parents getNameIdentifier() implementation. So for an example: "(call ancestor.parent.declaration)" it produces a Psi tree like:

 

--CallPsiElement [implements PsiNameIdentifierOwner]

----- IdentifierPsiElement [ancestor.parent.declaration, implements PsiReference]

 

What I would like to achieve is treating each part of the fully qualified identifier (i.e., in the above example "ancestor", "parent", and "declaration) as individual references, with the previous reference qualifying the next where a qualifier exists.  My thought was to parse each qualifier individually and treat a qualified name as an expression so I'd get a Psi tree like this:

 

--CallPsiElement [should this still implement PsiNameIdentifierOwner?]

----IdentifierExpressionPsiElement

------ IdentifierPsiElement [ancestor, implements PsiReference]

------ IdentifierPsiElement [parent, implements PsiReference]

------ IdentifierPsiElement [declaration, implements PsiReference]

 

I can see that the Java references support this, since each part of a package name is clickable.  Though I'm not sure where to start with implementing this.  Any suggestions would be helpful.

 

0
2 comments

After giving this some thought and rubber-ducky debugging from writing this post, I've come up with this which (I think) should work.  Though, I'm not sure if it's the correct approach.

-- CallPsiElement

---- CallNamePsiElement

------ IdentifierExpressionPsiElement [implements PsiNameIdentifierOwner]

-------- IdentifierPsiElement [ancestor, implements PsiReference]

------ IdentifierExpressionPsiElement [implements PsiNameIdentifierOwner]

-------- IdentifierPsiElement [parent, implements PsiReference]

------ IdentifierExpressionPsiElement [implements PsiNameIdentifierOwner]

-------- IdentifierPsiElement [declaration, implements PsiReference]

 

Would this be the way to go?

0

Right, so a bit more digging and I've made some progress!  From what I can tell, the PsiNameIdentifierOwner interface isn't necessary for providing control-click functionality on PsiElement with references in them, all that is necessary is the containing PsiFile can call findReferecesAt(offset) and return an array of PsiReferences.

With that in mind, I discovered I could represent my expression elements in any way as long as a composite element with an identifier returned references via getReferences().  Though, wondering what the best way would be I took a look at the PsiReferenceExpression implementation for Java code.  The PSI structure looks something like this:

 

-- PsiMethodCallExpression [ancestor.parent.declaration()]

---- PsiReferenceExpression [ancestor.parent.declaration]

------ PsiReferenceExpression [ancestor.parent]

-------- PsiReferenceExpression [ancestor]

---------- IDENTIFIER [ancestor]

-------- DOT

-------- IDENTIFIER [parent]

------ DOT

------ IDENTIFIER [declaration]

 

I opted to copy this approach because it allows me to easily check the parent of the last name after the final '.' to decide what type the reference might refer to (i.e., if it refers to a method or variable declaration), and an easy way to qualify that reference by checking for a child PsiReference.  Is there any way to create a pull request against IntelliJ Custom Language docs to add a little bit about this?  Would like to document this for others that want to do something similar if possible!

 

edit: additionally, it seems if I implement PsiNameIdentifierOwner on a PsiElement with PsiReferenceExpressions I would need to return one of my children.  By having the element containing the 'declaration' identifier as the first child of the call expression it means that references to that identifier will be highlighted by the HighlightIdentifierScanner (or something like that) so it works out nicely.

0

Please sign in to leave a comment.