How do I make a Java PsiLiteralExpression behave as a declaration for references and refactoring?

Answered

I want to link elements in a markdown file to java string literals so the string literal appears as the declaration and the markdown element as a reference.

The issue is that the java string literals can override literals by the same name from a super class so sometimes the literal will refer to a literal in another class.

Markdown elements in multiple files can refer to string literal in the same java file and elements from one markdown file can refer to string literal in several java class files.

I figured that the only way to accomplish this is via a FakePsiElement which is used as a fake declaration with both the Java string literal and Markdown referring to the fake element in their references.

I had it working without the fake psi element except without having string literal override ones in a base class. I put it into an overview diagram with all relevant points:

Q1: The question I have is how to have all elements be refactored together when they reference the same FakePsiLiteralExpression?

Q2: Do I need to provide a SearchReferences extension point and when the elementToSearch is a FakePsiLiteralExpression then provide references from markdown elements that use it?

Q3: How do I make sure that a FakePsiLiteralExpression element is treated identically to another instance which refers to the same underlying PsiLiteralExpression instance?

Q4: Is having equals() return true and hashCode() return the same value for these FakePsiLiteralExpression instances enough or do I need to have a manager to ensure that for every PsiLiteralExpression the same instance of FakePsiLiteralExpression is always used?

3 comments
Comment actions Permalink

Another question is about FakePsiLiteralExpression.isPhysical() should it return false or delegate to underlying PsiLiteralExpression.isPhysical()?

0
Comment actions Permalink

After playing around with this I made the following diagram to show the relationships between test classes, spec resource declaration defines which test spec file (markdown) will be used to generate the test data and the options used for each test.

In the example, java test class MdCachedFileElementsTest declares test spec file to befile_element_stash_spec_test.md and two options: find-headers, find-items and inherits option type from its super class.

Test class MdCachedFileElementsTest2 declares test spec file also as file_element_stash_spec_test.md, but defines its own options: find-headers, find-items and type from its super class.

In the markdown file find-headers appears in two tests so each of these options will resolve to corresponding definition in the two test classes. Option type will resolve to one test class and one in the super class of a test class. It is also possible for an option to be defined in a super class but overridden in a sub class.

I would like to navigate between the Java option declaration and the markdown option use in both directions and perform rename refactoring on all Java and Markdown options (matched by name) at the same time.

Any recommendations on how to do this would be greatly appreciated. I can handle one to many condition, and it works. However, as soon as there is a many to many condition, only one set of options is renamed.

public abstract class ActionSpecTestCase extends LightPlatformCodeInsightFixtureSpecTestCase {
    final static public String TYPE_ACTION = "type";
    final static public String SKIP_ACTION = "no-op";

    final private static Map<String, DataHolder> optionsMap = new HashMap<>();
    static {
        optionsMap.put("type", new MutableDataSet().set(CUSTOM_OPTION, ActionSpecTestCase::typeOption));
    }
}

public class MdCachedFileElementsTest extends ActionSpecTestCase {
    private static final String SPEC_RESOURCE = "file_element_stash_spec_test.md";

    final private static Map<String, DataHolder> optionsMap = new HashMap<>();
    static {
        optionsMap.put("find-headers", new MutableDataSet().set(FIND_CLASSES, new Class<?>[] { MdHeaderElement.class }));
        optionsMap.put("find-items", new MutableDataSet().set(FIND_CLASSES, new Class<?>[] { MdOrderedListItem.class, MdUnorderedListItem.class }));
    }
}

public class MdCachedFileElementsTest2 {
    private static final String SPEC_RESOURCE = "file_element_stash_spec_test.md";

    final private static Map<String, DataHolder> optionsMap = new HashMap<>();
    static {
        optionsMap.put("type", new MutableDataSet().set(CUSTOM_OPTION, ActionSpecTestCase::typeOption));
        optionsMap.put("find-headers", new MutableDataSet().set(FIND_CLASSES, new Class<?>[] { MdHeaderElement.class }));
        optionsMap.put("find-items", new MutableDataSet().set(FIND_CLASSES, new Class<?>[] { MdOrderedListItem.class, MdUnorderedListItem.class }));
    }
}

The markdown test spec looks like the following:

```````````````````````````````` example(Elements: 1) options(find-list-items)
* list item 1
* list item 2

[ref]: example.com

[ref]
    
.
.
 MdUnorderedListItemImpl:[0, 14, "* lis … t item 1\n"]
   BULLET_LIST_ITEM_MARKER:[0, 2, "* "]
   MdTextBlockImpl:[2, 14, "list  … item 1\n"]
     TEXT:[2, 13, "list  … item 1"]
     EOL:[13, 14, "\n"]
 MdUnorderedListItemImpl:[14, 28, "* lis … t item 2\n"]
   BULLET_LIST_ITEM_MARKER:[14, 16, "* "]
   MdTextBlockImpl:[16, 28, "list  … item 2\n"]
     TEXT:[16, 27, "list  … item 2"]
     EOL:[27, 28, "\n"]
````````````````````````````````


```````````````````````````````` example(Elements: 2) options(find-headers)
# Header 1
    
# Header 2
    
.
.
 MdEnhAtxHeaderImpl:[0, 11, "# Hea … der 1\n"]
   HEADER_ATX_MARKER:[0, 1, "#"]
   WHITESPACE:[1, 2, " "]
   MdEnhHeaderTextImpl:[2, 10, "Header 1"]
     HEADER_TEXT:[2, 10, "Header 1"]
   EOL:[10, 11, "\n"]
 MdEnhAtxHeaderImpl:[16, 27, "# Hea … der 2\n"]
   HEADER_ATX_MARKER:[16, 17, "#"]
   WHITESPACE:[17, 18, " "]
   MdEnhHeaderTextImpl:[18, 26, "Header 2"]
     HEADER_TEXT:[18, 26, "Header 2"]
   EOL:[26, 27, "\n"]
````````````````````````````````


```````````````````````````````` example(Elements: 3) options(find-headers, type[# Header 3])
# Header 1
    
# Header 2
    
⦙
    
.
# Header 1
    
# Header 2
    
# Header 3⦙
    
.
----- Before Action -----
 MdEnhAtxHeaderImpl:[0, 11, "# Hea … der 1\n"]
   HEADER_ATX_MARKER:[0, 1, "#"]
   WHITESPACE:[1, 2, " "]
   MdEnhHeaderTextImpl:[2, 10, "Header 1"]
     HEADER_TEXT:[2, 10, "Header 1"]
   EOL:[10, 11, "\n"]
 MdEnhAtxHeaderImpl:[16, 27, "# Hea … der 2\n"]
   HEADER_ATX_MARKER:[16, 17, "#"]
   WHITESPACE:[17, 18, " "]
   MdEnhHeaderTextImpl:[18, 26, "Header 2"]
     HEADER_TEXT:[18, 26, "Header 2"]
   EOL:[26, 27, "\n"]

----- After Action ------
 MdEnhAtxHeaderImpl:[0, 11, "# Hea … der 1\n"]
   HEADER_ATX_MARKER:[0, 1, "#"]
   WHITESPACE:[1, 2, " "]
   MdEnhHeaderTextImpl:[2, 10, "Header 1"]
     HEADER_TEXT:[2, 10, "Header 1"]
   EOL:[10, 11, "\n"]
 MdEnhAtxHeaderImpl:[16, 27, "# Hea … der 2\n"]
   HEADER_ATX_MARKER:[16, 17, "#"]
   WHITESPACE:[17, 18, " "]
   MdEnhHeaderTextImpl:[18, 26, "Header 2"]
     HEADER_TEXT:[18, 26, "Header 2"]
   EOL:[26, 27, "\n"]
 MdEnhAtxHeaderImpl:[32, 43, "# Hea … der 3\n"]
   HEADER_ATX_MARKER:[32, 33, "#"]
   WHITESPACE:[33, 34, " "]
   MdEnhHeaderTextImpl:[34, 42, "Header 3"]
     HEADER_TEXT:[34, 42, "Header 3"]
   EOL:[42, 43, "\n"]
````````````````````````````````
0
Comment actions Permalink

Made it work by providing all possible references in ReferenceSearch extension for all related items no matter which element is being searched. Rename refactoring then affects all items. 

0

Please sign in to leave a comment.