Phase 5: add rename and find usages
This commit is contained in:
35
AGENTS.md
35
AGENTS.md
@@ -59,20 +59,23 @@ class and still prints `OK`. `jaitest` therefore passes
|
|||||||
Last verified state (all green, `./jaigradle check` and `verifyPlugin` too):
|
Last verified state (all green, `./jaigradle check` and `verifyPlugin` too):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
dev.hgh.HarnessSmokeTest tests=2
|
dev.hgh.HarnessSmokeTest tests=2
|
||||||
dev.hgh.jai.JaiFileTypeTest tests=4
|
dev.hgh.jai.JaiFileTypeTest tests=4
|
||||||
dev.hgh.jai.editor.JaiEditorSupportTest tests=7
|
dev.hgh.jai.completion.JaiCompletionTest tests=6
|
||||||
dev.hgh.jai.editor.JaiFoldingBuilderTest tests=3
|
dev.hgh.jai.editor.JaiEditorSupportTest tests=7
|
||||||
dev.hgh.jai.highlighting.* tests=8
|
dev.hgh.jai.editor.JaiFoldingBuilderTest tests=3
|
||||||
dev.hgh.jai.lexer.JaiCorpusLexerTest tests=2 <- the Tier 0 gate
|
dev.hgh.jai.findusages.JaiFindUsagesTest tests=2
|
||||||
dev.hgh.jai.lexer.JaiLexerTest tests=13
|
dev.hgh.jai.highlighting.* tests=8
|
||||||
dev.hgh.jai.parser.JaiCorpusParserTest tests=1 <- the Tier 3 gate
|
dev.hgh.jai.lexer.JaiCorpusLexerTest tests=2 <- the Tier 0 gate
|
||||||
dev.hgh.jai.parser.JaiParserGoldenTest tests=5 <- Tier 2 golden trees
|
dev.hgh.jai.lexer.JaiLexerTest tests=13
|
||||||
dev.hgh.jai.parser.JaiParserLongTailTest tests=1 <- focused parser regressions
|
dev.hgh.jai.parser.JaiCorpusParserTest tests=1 <- the Tier 3 gate
|
||||||
dev.hgh.jai.parser.DebugParseTest tests=2 <- scratch harness, inert
|
dev.hgh.jai.parser.JaiParserGoldenTest tests=5 <- Tier 2 golden trees
|
||||||
dev.hgh.jai.reference.JaiReferenceTest tests=4
|
dev.hgh.jai.parser.JaiParserLongTailTest tests=1 <- focused parser regressions
|
||||||
dev.hgh.jai.structure.JaiStructureViewTest tests=3
|
dev.hgh.jai.parser.DebugParseTest tests=2 <- scratch harness, inert
|
||||||
-> total 55, failures+errors 0
|
dev.hgh.jai.refactoring.JaiRenameTest tests=3
|
||||||
|
dev.hgh.jai.reference.JaiReferenceTest tests=9
|
||||||
|
dev.hgh.jai.structure.JaiStructureViewTest tests=3
|
||||||
|
-> total 71, failures+errors 0
|
||||||
```
|
```
|
||||||
|
|
||||||
The corpus gates report what they actually did; check both lines are still there:
|
The corpus gates report what they actually did; check both lines are still there:
|
||||||
@@ -180,7 +183,9 @@ Tier 3: parsed 714 files, 714 clean (100.0%), 0 with errors, 0 PsiErrorElements
|
|||||||
compiler directives, `#import`/`#load` module paths is implemented in
|
compiler directives, `#import`/`#load` module paths is implemented in
|
||||||
`dev.hgh.jai.completion.JaiCompletionContributor`. Identifier references now
|
`dev.hgh.jai.completion.JaiCompletionContributor`. Identifier references now
|
||||||
resolve procedures/types in the same file and transitively loaded/imported
|
resolve procedures/types in the same file and transitively loaded/imported
|
||||||
files, with headless fixture coverage. Rename and find-usages remain.
|
files. Declaration names now support rename and find-usages through the
|
||||||
|
standard PSI contracts; module-qualified members, parameters, and fields
|
||||||
|
remain future symbol-resolution work.
|
||||||
2. **Phases 6+** — formatter, inspections, compiler integration, and other
|
2. **Phases 6+** — formatter, inspections, compiler integration, and other
|
||||||
optional work remain. `#asm` bodies are intentionally consumed opaquely, so
|
optional work remain. `#asm` bodies are intentionally consumed opaquely, so
|
||||||
nothing inside them has PSI yet.
|
nothing inside them has PSI yet.
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
- Folding for blocks, nested comments, and multi-line here-strings.
|
- Folding for blocks, nested comments, and multi-line here-strings.
|
||||||
- Go-to-definition references for `#import` and `#load`, including project-relative
|
- Go-to-definition references for `#import` and `#load`, including project-relative
|
||||||
files and the local Jai module layout (`Name.jai` or `Name/module.jai`).
|
files and the local Jai module layout (`Name.jai` or `Name/module.jai`).
|
||||||
|
- Basic completion for Jai keywords, compiler directives, and module/file paths.
|
||||||
|
- Go-to-definition for procedure and type symbols in the same file and through
|
||||||
|
loaded or imported Jai files.
|
||||||
|
- Rename and find-usages support for declaration names and resolved references.
|
||||||
|
|
||||||
### Known gaps
|
### Known gaps
|
||||||
|
|
||||||
@@ -29,3 +33,5 @@
|
|||||||
inner PSI or structure entries.
|
inner PSI or structure entries.
|
||||||
- Import resolution uses the project/module roots and the local Jai installation;
|
- Import resolution uses the project/module roots and the local Jai installation;
|
||||||
custom compiler `-import_dir` arguments are not discovered yet.
|
custom compiler `-import_dir` arguments are not discovered yet.
|
||||||
|
- Module-qualified members, procedure parameters, and struct fields do not yet
|
||||||
|
have symbol resolution.
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -11,13 +11,20 @@ never from online material (see `AGENTS.md`).
|
|||||||
(`#import`, `#run`, …), notes (`@Cleanup`), numbers (hex / binary / hexfloat /
|
(`#import`, `#run`, …), notes (`@Cleanup`), numbers (hex / binary / hexfloat /
|
||||||
`_` separators), strings, here-strings (`#string DONE … DONE`), nesting block
|
`_` separators), strings, here-strings (`#string DONE … DONE`), nesting block
|
||||||
comments, operators, `---`, `$`/`$$`, `it` / `it_index`
|
comments, operators, `---`, `$`/`$$`, `it` / `it_index`
|
||||||
|
- Grammar-Kit parser and PSI for the full local 714-file corpus
|
||||||
|
- Structure view and folding for blocks, comments, and here-strings
|
||||||
- Colour settings page (**Settings → Editor → Color Scheme → Jai**)
|
- Colour settings page (**Settings → Editor → Color Scheme → Jai**)
|
||||||
- Comment / uncomment (`//` and nesting `/* */`)
|
- Comment / uncomment (`//` and nesting `/* */`)
|
||||||
- Brace matching, including the Jai-only `.{` struct-literal and `.[`
|
- Brace matching, including the Jai-only `.{` struct-literal and `.[`
|
||||||
array-literal openers
|
array-literal openers
|
||||||
|
- Basic completion for keywords, directives, and local Jai module/file paths
|
||||||
|
- Go-to-definition for procedures and types in the same file and through
|
||||||
|
`#import`/`#load`
|
||||||
|
- Rename and find-usages for declaration names and their resolved references
|
||||||
|
|
||||||
Not yet: parser and PSI, so no structure view, go-to-definition, completion,
|
Remaining code-insight gaps include module-qualified members, parameters, and
|
||||||
formatting, or inspections. See `docs/BUILD_PLAN.md` for the phase plan.
|
struct fields. Formatting, inspections, and compiler integration are planned
|
||||||
|
later. See `docs/BUILD_PLAN.md` for the phase plan.
|
||||||
|
|
||||||
## Install it locally
|
## Install it locally
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ Each phase has a machine-checkable gate. Do not advance without a green gate.
|
|||||||
| **2** | `SyntaxHighlighter`, color settings page, commenter, brace matcher | Tier 1 golden dumps; highlighter maps every token type |
|
| **2** | `SyntaxHighlighter`, color settings page, commenter, brace matcher | Tier 1 golden dumps; highlighter maps every token type |
|
||||||
| **3** | `.bnf` grammar, generated parser + PSI, `ParserDefinition` | Tier 2 golden trees; Tier 3 corpus parse ≥ target — **done, 100.0% (714/714)** |
|
| **3** | `.bnf` grammar, generated parser + PSI, `ParserDefinition` | Tier 2 golden trees; Tier 3 corpus parse ≥ target — **done, 100.0% (714/714)** |
|
||||||
| **4** | Structure view, folding, `#import`/`#load` reference resolution + go-to-definition | Tier 4 fixture tests — **done** |
|
| **4** | Structure view, folding, `#import`/`#load` reference resolution + go-to-definition | Tier 4 fixture tests — **done** |
|
||||||
| **5** | Completion (keywords, directives, module names), rename, find-usages | Tier 4 fixture tests |
|
| **5** | Completion (keywords, directives, module names), rename, find-usages | Tier 4 fixture tests — **done** (70-test suite) |
|
||||||
| **6** | Formatter, code style settings | Formatter round-trip: formatting the corpus is idempotent |
|
| **6** | Formatter, code style settings | Formatter round-trip: formatting the corpus is idempotent |
|
||||||
| **7** | Inspections (e.g. `#must` misuse), quick fixes, live templates | Tier 4 + `verifyPlugin` |
|
| **7** | Inspections (e.g. `#must` misuse), quick fixes, live templates | Tier 4 + `verifyPlugin` |
|
||||||
| **8** | Optional: run-configuration to invoke the `jai` compiler, parse its error output | Integration test against `~/.local/jai/bin` |
|
| **8** | Optional: run-configuration to invoke the `jai` compiler, parse its error output | Integration test against `~/.local/jai/bin` |
|
||||||
@@ -196,8 +196,9 @@ corpus-scale invariant that needs no human judgment.
|
|||||||
|
|
||||||
## 6. Open questions for you
|
## 6. Open questions for you
|
||||||
|
|
||||||
1. **Scope/ambition** — stop at solid syntax highlighting + navigation (Phases
|
1. **Scope/ambition** — stop at the current syntax, navigation, and basic
|
||||||
0–4), or go all the way to formatter and inspections (0–7)?
|
refactoring support (Phases 0–5), or go all the way to formatter and
|
||||||
|
inspections (0–7)?
|
||||||
2. **Compiler integration** (Phase 8) — worth it? It's the only phase needing
|
2. **Compiler integration** (Phase 8) — worth it? It's the only phase needing
|
||||||
the actual `jai` binary, and it's the least testable.
|
the actual `jai` binary, and it's the least testable.
|
||||||
3. **Target IDE** — IntelliJ IDEA only (current setting), or all JetBrains IDEs?
|
3. **Target IDE** — IntelliJ IDEA only (current setting), or all JetBrains IDEs?
|
||||||
@@ -206,5 +207,6 @@ corpus-scale invariant that needs no human judgment.
|
|||||||
setup, works today), or install a system JDK via Homebrew? I'd default to the
|
setup, works today), or install a system JDK via Homebrew? I'd default to the
|
||||||
wrapper.
|
wrapper.
|
||||||
|
|
||||||
My recommendation: approve Phases 0–4 now, decide on 5–8 once the parser gate is
|
Phases 0–5 are now implemented and verified headlessly. The remaining decision
|
||||||
green and we can see how the grammar behaved against real code.
|
is whether to continue with the optional formatter, inspections, and compiler
|
||||||
|
integration work in Phases 6–8.
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import com.intellij.psi.PsiElement;
|
|||||||
import com.intellij.psi.PsiElementVisitor;
|
import com.intellij.psi.PsiElementVisitor;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
import static dev.hgh.jai.psi.JaiTypes.*;
|
import static dev.hgh.jai.psi.JaiTypes.*;
|
||||||
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
import dev.hgh.jai.psi.mixin.JaiNamedElementMixin;
|
||||||
import dev.hgh.jai.psi.*;
|
import dev.hgh.jai.psi.*;
|
||||||
|
|
||||||
public class JaiDeclNameImpl extends ASTWrapperPsiElement implements JaiDeclName {
|
public class JaiDeclNameImpl extends JaiNamedElementMixin implements JaiDeclName {
|
||||||
|
|
||||||
public JaiDeclNameImpl(@NotNull ASTNode node) {
|
public JaiDeclNameImpl(ASTNode node) {
|
||||||
super(node);
|
super(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ private declModifier_ ::= <<dirWithBlock>> directiveFlag_* block
|
|||||||
private usingModifier_ ::= 'using' [directiveFlag_ [<<exprNoAssign>>]]
|
private usingModifier_ ::= 'using' [directiveFlag_ [<<exprNoAssign>>]]
|
||||||
declNames ::= declName (['='] ',' declName ['='])* ['=']
|
declNames ::= declName (['='] ',' declName ['='])* ['=']
|
||||||
declName ::= ['`'] (IDENT | keywordAsName_)
|
declName ::= ['`'] (IDENT | keywordAsName_)
|
||||||
|
{ mixin="dev.hgh.jai.psi.mixin.JaiNamedElementMixin" }
|
||||||
private declTail_ ::= ':' [<<exprNoAssign>>] procModifier* [',' assignTargets_] [('=' | ':') initializerList_]
|
private declTail_ ::= ':' [<<exprNoAssign>>] procModifier* [',' assignTargets_] [('=' | ':') initializerList_]
|
||||||
| ':=' initializerList_
|
| ':=' initializerList_
|
||||||
| '::' initializerList_
|
| '::' initializerList_
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package dev.hgh.jai.findusages
|
||||||
|
|
||||||
|
import com.intellij.lang.cacheBuilder.DefaultWordsScanner
|
||||||
|
import com.intellij.lang.cacheBuilder.WordsScanner
|
||||||
|
import com.intellij.lang.findUsages.FindUsagesProvider
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
|
import com.intellij.psi.PsiNamedElement
|
||||||
|
import com.intellij.psi.tree.TokenSet
|
||||||
|
import dev.hgh.jai.lexer.JaiLexer
|
||||||
|
import dev.hgh.jai.lexer.JaiTokenTypes
|
||||||
|
import dev.hgh.jai.psi.JaiRefExpr
|
||||||
|
|
||||||
|
/** Supplies word indexing and descriptions for Jai declaration usages. */
|
||||||
|
class JaiFindUsagesProvider : FindUsagesProvider {
|
||||||
|
override fun getWordsScanner(): WordsScanner =
|
||||||
|
DefaultWordsScanner(
|
||||||
|
JaiLexer(),
|
||||||
|
TokenSet.create(JaiTokenTypes.IDENT),
|
||||||
|
JaiTokenTypes.COMMENTS,
|
||||||
|
JaiTokenTypes.STRINGS,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun canFindUsagesFor(psiElement: PsiElement): Boolean = psiElement is PsiNameIdentifierOwner || psiElement is JaiRefExpr
|
||||||
|
|
||||||
|
override fun getHelpId(psiElement: PsiElement): String? = null
|
||||||
|
|
||||||
|
override fun getType(element: PsiElement): String =
|
||||||
|
if (element is PsiNameIdentifierOwner || element is JaiRefExpr) {
|
||||||
|
"Jai declaration"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescriptiveName(element: PsiElement): String = (element as? PsiNamedElement)?.name ?: element.text
|
||||||
|
|
||||||
|
override fun getNodeText(
|
||||||
|
element: PsiElement,
|
||||||
|
useFullName: Boolean,
|
||||||
|
): String = element.text
|
||||||
|
}
|
||||||
23
src/main/kotlin/dev/hgh/jai/psi/JaiPsiElementFactory.kt
Normal file
23
src/main/kotlin/dev/hgh/jai/psi/JaiPsiElementFactory.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package dev.hgh.jai.psi
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiFileFactory
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import dev.hgh.jai.JaiLanguage
|
||||||
|
import dev.hgh.jai.lexer.JaiTokenTypes
|
||||||
|
|
||||||
|
/** Creates small Jai PSI fragments for AST replacements used by rename refactoring. */
|
||||||
|
object JaiPsiElementFactory {
|
||||||
|
fun createIdentifier(
|
||||||
|
project: Project,
|
||||||
|
name: String,
|
||||||
|
): PsiElement {
|
||||||
|
val file = PsiFileFactory.getInstance(project).createFileFromText("rename.jai", JaiLanguage, "$name :: 0;")
|
||||||
|
val declarationName =
|
||||||
|
PsiTreeUtil.findChildOfType(file, JaiDeclName::class.java)
|
||||||
|
?: error("Could not parse a Jai declaration name for '$name'")
|
||||||
|
return declarationName.node.findChildByType(JaiTokenTypes.IDENT)?.psi
|
||||||
|
?: error("Jai name '$name' is not an identifier")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.hgh.jai.psi.mixin
|
||||||
|
|
||||||
|
import com.intellij.extapi.psi.ASTWrapperPsiElement
|
||||||
|
import com.intellij.lang.ASTNode
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
|
import dev.hgh.jai.lexer.JaiTokenTypes
|
||||||
|
import dev.hgh.jai.psi.JaiPsiElementFactory
|
||||||
|
|
||||||
|
/** Gives declaration names the standard PSI naming contract used by rename/find-usages. */
|
||||||
|
abstract class JaiNamedElementMixin(
|
||||||
|
node: ASTNode,
|
||||||
|
) : ASTWrapperPsiElement(node),
|
||||||
|
PsiNameIdentifierOwner {
|
||||||
|
override fun getNameIdentifier(): PsiElement? = node.findChildByType(JaiTokenTypes.IDENT)?.psi
|
||||||
|
|
||||||
|
override fun getName(): String? = nameIdentifier?.text
|
||||||
|
|
||||||
|
override fun setName(name: String): PsiElement {
|
||||||
|
val identifier = nameIdentifier ?: return this
|
||||||
|
identifier.replace(JaiPsiElementFactory.createIdentifier(project, name))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNavigationElement(): PsiElement = nameIdentifier ?: this
|
||||||
|
|
||||||
|
override fun getTextOffset(): Int = nameIdentifier?.textOffset ?: super.getTextOffset()
|
||||||
|
}
|
||||||
26
src/main/kotlin/dev/hgh/jai/refactoring/JaiNamesValidator.kt
Normal file
26
src/main/kotlin/dev/hgh/jai/refactoring/JaiNamesValidator.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.hgh.jai.refactoring
|
||||||
|
|
||||||
|
import com.intellij.lang.refactoring.NamesValidator
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import dev.hgh.jai.lexer.JaiTokenTypes
|
||||||
|
|
||||||
|
/** Applies Jai's ASCII identifier and keyword rules to rename input. */
|
||||||
|
class JaiNamesValidator : NamesValidator {
|
||||||
|
override fun isKeyword(
|
||||||
|
name: String,
|
||||||
|
project: Project?,
|
||||||
|
): Boolean = name in JaiTokenTypes.KEYWORD_MAP
|
||||||
|
|
||||||
|
override fun isIdentifier(
|
||||||
|
name: String,
|
||||||
|
project: Project?,
|
||||||
|
): Boolean {
|
||||||
|
if (name.isEmpty() || isKeyword(name, project)) return false
|
||||||
|
if (!isIdentifierStart(name.first())) return false
|
||||||
|
return name.drop(1).all(::isIdentifierPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isIdentifierStart(character: Char): Boolean = character in 'a'..'z' || character in 'A'..'Z' || character == '_'
|
||||||
|
|
||||||
|
private fun isIdentifierPart(character: Char): Boolean = isIdentifierStart(character) || character in '0'..'9'
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package dev.hgh.jai.refactoring
|
||||||
|
|
||||||
|
import com.intellij.lang.refactoring.RefactoringSupportProvider
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
|
import dev.hgh.jai.JaiLanguage
|
||||||
|
|
||||||
|
/** Enables standard and in-place rename for Jai declaration names. */
|
||||||
|
class JaiRefactoringSupportProvider : RefactoringSupportProvider() {
|
||||||
|
override fun isMemberInplaceRenameAvailable(
|
||||||
|
elementToRename: PsiElement,
|
||||||
|
context: PsiElement?,
|
||||||
|
): Boolean = elementToRename is PsiNameIdentifierOwner && elementToRename.language == JaiLanguage
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import dev.hgh.jai.psi.JaiDeclName
|
|||||||
import dev.hgh.jai.psi.JaiDeclaration
|
import dev.hgh.jai.psi.JaiDeclaration
|
||||||
import dev.hgh.jai.psi.JaiDirectiveExpr
|
import dev.hgh.jai.psi.JaiDirectiveExpr
|
||||||
import dev.hgh.jai.psi.JaiLiteralExpr
|
import dev.hgh.jai.psi.JaiLiteralExpr
|
||||||
|
import dev.hgh.jai.psi.JaiPsiElementFactory
|
||||||
import dev.hgh.jai.psi.JaiRefExpr
|
import dev.hgh.jai.psi.JaiRefExpr
|
||||||
|
|
||||||
/** Adds file references for directives and symbol references for Jai identifiers. */
|
/** Adds file references for directives and symbol references for Jai identifiers. */
|
||||||
@@ -106,6 +107,12 @@ private class JaiSymbolReference(
|
|||||||
) : PsiReferenceBase<JaiRefExpr>(sourceElement, TextRange(0, sourceElement.textLength), true) {
|
) : PsiReferenceBase<JaiRefExpr>(sourceElement, TextRange(0, sourceElement.textLength), true) {
|
||||||
override fun resolve(): PsiElement? = JaiSymbolResolver.resolve(sourceElement)
|
override fun resolve(): PsiElement? = JaiSymbolResolver.resolve(sourceElement)
|
||||||
|
|
||||||
|
override fun handleElementRename(newElementName: String): PsiElement {
|
||||||
|
val identifier = sourceElement.node.findChildByType(JaiTokenTypes.IDENT)?.psi ?: return sourceElement
|
||||||
|
identifier.replace(JaiPsiElementFactory.createIdentifier(sourceElement.project, newElementName))
|
||||||
|
return sourceElement
|
||||||
|
}
|
||||||
|
|
||||||
override fun getVariants(): Array<Any> = emptyArray()
|
override fun getVariants(): Array<Any> = emptyArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,15 @@
|
|||||||
<completion.contributor
|
<completion.contributor
|
||||||
language="Jai"
|
language="Jai"
|
||||||
implementationClass="dev.hgh.jai.completion.JaiCompletionContributor"/>
|
implementationClass="dev.hgh.jai.completion.JaiCompletionContributor"/>
|
||||||
|
<lang.refactoringSupport
|
||||||
|
language="Jai"
|
||||||
|
implementationClass="dev.hgh.jai.refactoring.JaiRefactoringSupportProvider"/>
|
||||||
|
<lang.namesValidator
|
||||||
|
language="Jai"
|
||||||
|
implementationClass="dev.hgh.jai.refactoring.JaiNamesValidator"/>
|
||||||
|
<lang.findUsagesProvider
|
||||||
|
language="Jai"
|
||||||
|
implementationClass="dev.hgh.jai.findusages.JaiFindUsagesProvider"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
|
|||||||
37
src/test/kotlin/dev/hgh/jai/findusages/JaiFindUsagesTest.kt
Normal file
37
src/test/kotlin/dev/hgh/jai/findusages/JaiFindUsagesTest.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.hgh.jai.findusages
|
||||||
|
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||||
|
import dev.hgh.jai.psi.JaiDeclName
|
||||||
|
|
||||||
|
class JaiFindUsagesTest : BasePlatformTestCase() {
|
||||||
|
fun testFindUsagesActionFindsProcedureReference() {
|
||||||
|
val usages = myFixture.testFindUsages("Declaration.jai", "Usage.jai")
|
||||||
|
|
||||||
|
assertEquals(1, usages.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testFindsProcedureReferencesFromDeclaration() {
|
||||||
|
val source =
|
||||||
|
myFixture.addFileToProject(
|
||||||
|
"find-usages.jai",
|
||||||
|
"""
|
||||||
|
helper :: () {}
|
||||||
|
first :: () { helper(); }
|
||||||
|
second :: () { helper(); }
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
val declaration =
|
||||||
|
PsiTreeUtil
|
||||||
|
.findChildrenOfType(source, JaiDeclName::class.java)
|
||||||
|
.single { it.text == "helper" }
|
||||||
|
|
||||||
|
val usages = ReferencesSearch.search(declaration).findAll()
|
||||||
|
|
||||||
|
assertEquals(2, usages.size)
|
||||||
|
assertTrue(usages.all { it.element.text == "helper" })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTestDataPath(): String = "src/test/testData/findusages"
|
||||||
|
}
|
||||||
59
src/test/kotlin/dev/hgh/jai/refactoring/JaiRenameTest.kt
Normal file
59
src/test/kotlin/dev/hgh/jai/refactoring/JaiRenameTest.kt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package dev.hgh.jai.refactoring
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
|
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||||
|
|
||||||
|
class JaiRenameTest : BasePlatformTestCase() {
|
||||||
|
fun testDeclarationNamesParticipateInRename() {
|
||||||
|
myFixture.configureByText(
|
||||||
|
"rename.jai",
|
||||||
|
"""
|
||||||
|
helper<caret> :: () {}
|
||||||
|
main :: () { helper(); }
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val declaration = myFixture.elementAtCaret
|
||||||
|
|
||||||
|
assertTrue("declaration name should be a named PSI element", declaration is PsiNameIdentifierOwner)
|
||||||
|
assertEquals("helper", (declaration as PsiNameIdentifierOwner).name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testRenameProcedureUpdatesSameFileReferences() {
|
||||||
|
myFixture.configureByText(
|
||||||
|
"rename.jai",
|
||||||
|
"""
|
||||||
|
helper<caret> :: () {}
|
||||||
|
main :: () { helper(); }
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
myFixture.renameElementAtCaret("renamed")
|
||||||
|
|
||||||
|
myFixture.checkResult(
|
||||||
|
"""
|
||||||
|
renamed :: () {}
|
||||||
|
main :: () { renamed(); }
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testRenameTypeUpdatesTypeReferences() {
|
||||||
|
myFixture.configureByText(
|
||||||
|
"rename-type.jai",
|
||||||
|
"""
|
||||||
|
Point<caret> :: struct { x: int; }
|
||||||
|
use_point :: (point: Point) -> int { return point.x; }
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
myFixture.renameElementAtCaret("Coordinate")
|
||||||
|
|
||||||
|
myFixture.checkResult(
|
||||||
|
"""
|
||||||
|
Coordinate :: struct { x: int; }
|
||||||
|
use_point :: (point: Coordinate) -> int { return point.x; }
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/test/testData/findusages/Declaration.jai
Normal file
1
src/test/testData/findusages/Declaration.jai
Normal file
@@ -0,0 +1 @@
|
|||||||
|
helper<caret> :: () {}
|
||||||
2
src/test/testData/findusages/Usage.jai
Normal file
2
src/test/testData/findusages/Usage.jai
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#load "Declaration.jai";
|
||||||
|
main :: () { helper(); }
|
||||||
Reference in New Issue
Block a user