Add unresolved import and load inspection

This commit is contained in:
hgranthorner
2026-08-06 16:18:34 -04:00
parent 8fcd2cca05
commit c6b30e32d6
7 changed files with 118 additions and 12 deletions

View File

@@ -66,6 +66,7 @@ dev.hgh.jai.editor.JaiEditorSupportTest tests=7
dev.hgh.jai.editor.JaiFoldingBuilderTest tests=3 dev.hgh.jai.editor.JaiFoldingBuilderTest tests=3
dev.hgh.jai.findusages.JaiFindUsagesTest tests=2 dev.hgh.jai.findusages.JaiFindUsagesTest tests=2
dev.hgh.jai.highlighting.* tests=8 dev.hgh.jai.highlighting.* tests=8
dev.hgh.jai.inspection.JaiUnresolvedModuleInspectionTest tests=5
dev.hgh.jai.lexer.JaiCorpusLexerTest tests=2 <- the Tier 0 gate dev.hgh.jai.lexer.JaiCorpusLexerTest tests=2 <- the Tier 0 gate
dev.hgh.jai.lexer.JaiLexerTest tests=13 dev.hgh.jai.lexer.JaiLexerTest tests=13
dev.hgh.jai.parser.JaiCorpusParserTest tests=1 <- the Tier 3 gate dev.hgh.jai.parser.JaiCorpusParserTest tests=1 <- the Tier 3 gate
@@ -78,7 +79,7 @@ dev.hgh.jai.structure.JaiStructureViewTest tests=3
dev.hgh.jai.formatter.JaiFormatterTest tests=4 dev.hgh.jai.formatter.JaiFormatterTest tests=4
dev.hgh.jai.settings.JaiProjectSettingsTest tests=3 dev.hgh.jai.settings.JaiProjectSettingsTest tests=3
dev.hgh.jai.settings.JaiConfiguredRootTest tests=6 dev.hgh.jai.settings.JaiConfiguredRootTest tests=6
-> total 99, failures+errors 0 -> total 104, 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:
@@ -134,6 +135,10 @@ Tier 3: parsed 714 files, 714 clean (100.0%), 0 with errors, 0 PsiErrorElements
block indentation, opaque `#asm` preservation, and a corpus-wide idempotence block indentation, opaque `#asm` preservation, and a corpus-wide idempotence
gate. Headless formatter tests cover registration, representative formatting, gate. Headless formatter tests cover registration, representative formatting,
opaque/directive token preservation, and all 714 corpus files. opaque/directive token preservation, and all 714 corpus files.
- **Phase 7a** — unresolved `#import`/`#load` module and file path inspection,
reusing the existing reference resolver. Headless tests cover unresolved
imports and loads, resolved project/local modules and files, and
`#import,string` exclusions.
### Lexer design facts worth knowing before touching it ### Lexer design facts worth knowing before touching it
@@ -202,14 +207,14 @@ Tier 3: parsed 714 files, 714 clean (100.0%), 0 with errors, 0 PsiErrorElements
### In progress — pick up here ### In progress — pick up here
1. **Phases 7+** — inspections, compiler integration, and other optional work 1. **Phases 7+** additional inspections, quick fixes, live templates, and
remain. `#asm` bodies are intentionally consumed opaquely, so nothing inside compiler integration remain. `#asm` bodies are intentionally consumed
them has PSI yet. opaquely, so nothing inside them has PSI yet.
### Next planned increment ### Next planned increment
- Decide whether to continue with inspections and compiler integration phases - Expand Phase 7 with additional high-confidence inspections and quick fixes;
(78). struct-field resolution and completion remain a separate known gap.
### Open questions for the user (unanswered) ### Open questions for the user (unanswered)

View File

@@ -31,6 +31,7 @@
- PSI-aware formatter with operator and punctuation spacing, block indentation, - PSI-aware formatter with operator and punctuation spacing, block indentation,
directive-flag handling, and preservation of opaque `#asm` bodies; formatting directive-flag handling, and preservation of opaque `#asm` bodies; formatting
is idempotent across all 714 corpus files. is idempotent across all 714 corpus files.
- Inspection warnings for unresolved `#import` and `#load` module/file paths.
### Known gaps ### Known gaps

View File

@@ -28,10 +28,11 @@ never from online material (see `AGENTS.md`).
- PSI-aware formatting for operator and punctuation spacing, block indentation, - PSI-aware formatting for operator and punctuation spacing, block indentation,
directive flags, and opaque `#asm` bodies; formatting is idempotent across the directive flags, and opaque `#asm` bodies; formatting is idempotent across the
full local corpus full local corpus
- Inspection warnings for unresolved `#import` and `#load` module/file paths
Remaining code-insight gaps include completion and symbol resolution for struct Remaining code-insight gaps include completion and symbol resolution for struct
fields and other type-qualified members. Inspections and compiler integration fields and other type-qualified members. Additional inspections and compiler
are planned later. integration are planned later.
See `docs/BUILD_PLAN.md` for the phase plan. See `docs/BUILD_PLAN.md` for the phase plan.
## Install it locally ## Install it locally

View File

@@ -172,7 +172,7 @@ Each phase has a machine-checkable gate. Do not advance without a green gate.
| **5** | Completion (keywords, directives, module names, visible declarations, parameters, module aliases, incomplete-expression recovery), rename, find-usages | Tier 4 fixture tests — **done** (84-test suite) | | **5** | Completion (keywords, directives, module names, visible declarations, parameters, module aliases, incomplete-expression recovery), rename, find-usages | Tier 4 fixture tests — **done** (84-test suite) |
| **5a** | Configurable Jai module/import roots and indexed external search scope | Tier 4 settings, resolution, completion, navigation, and find-usages tests — **done** (93-test suite) | | **5a** | Configurable Jai module/import roots and indexed external search scope | Tier 4 settings, resolution, completion, navigation, and find-usages tests — **done** (93-test suite) |
| **6** | Formatter, code style settings | Formatter round-trip — **done**, formatting all 714 corpus files is idempotent | | **6** | Formatter, code style settings | Formatter round-trip — **done**, formatting all 714 corpus files is idempotent |
| **7** | Inspections (e.g. `#must` misuse), quick fixes, live templates | Tier 4 + `verifyPlugin` | | **7** | Inspections, quick fixes, live templates | Tier 4 + `verifyPlugin` — initial unresolved `#import`/`#load` path inspection implemented |
| **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` |
Phase 6's idempotence check (format twice, assert no change) is another Phase 6's idempotence check (format twice, assert no change) is another
@@ -218,6 +218,6 @@ currently a committed milestone.
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.
Phases 06 are implemented and verified headlessly. The next decision is Phases 06 are implemented and verified headlessly. Phase 7 has an initial
whether to continue with the optional inspections and compiler integration work unresolved `#import`/`#load` path inspection; additional inspections and
in Phases 78. compiler integration remain optional follow-up work.

View File

@@ -0,0 +1,45 @@
package dev.hgh.jai.inspection
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiReferenceService
import dev.hgh.jai.lexer.JaiTokenTypes
import dev.hgh.jai.psi.JaiDirectiveExpr
import dev.hgh.jai.psi.JaiLiteralExpr
/** Reports file and module paths in #import/#load directives that do not resolve. */
class JaiUnresolvedModuleInspection : LocalInspectionTool() {
override fun getDisplayName(): String = "Unresolved Jai import or load"
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
): PsiElementVisitor =
object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
val literal = element as? JaiLiteralExpr ?: return
val directive = literal.parent as? JaiDirectiveExpr ?: return
val directiveName = directive.node.findChildByType(JaiTokenTypes.DIRECTIVE)?.text ?: return
if (directiveName != "#import" && directiveName != "#load") return
val reference =
PsiReferenceService
.getService()
.getContributedReferences(literal)
.firstOrNull()
?: return
if (reference.resolve() != null) return
val path = literal.text.removeSurrounding("\"")
holder.registerProblem(
reference.element,
"Cannot resolve $directiveName path '$path'",
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL,
reference.rangeInElement,
)
}
}
}

View File

@@ -49,6 +49,13 @@
<psi.referenceContributor <psi.referenceContributor
language="Jai" language="Jai"
implementation="dev.hgh.jai.reference.JaiReferenceContributor"/> implementation="dev.hgh.jai.reference.JaiReferenceContributor"/>
<localInspection
language="Jai"
displayName="Unresolved Jai import or load"
groupName="Jai"
enabledByDefault="true"
level="WARNING"
implementationClass="dev.hgh.jai.inspection.JaiUnresolvedModuleInspection"/>
<completion.contributor <completion.contributor
language="Jai" language="Jai"
implementationClass="dev.hgh.jai.completion.JaiCompletionContributor"/> implementationClass="dev.hgh.jai.completion.JaiCompletionContributor"/>

View File

@@ -0,0 +1,47 @@
package dev.hgh.jai.inspection
import com.intellij.testFramework.fixtures.BasePlatformTestCase
class JaiUnresolvedModuleInspectionTest : BasePlatformTestCase() {
override fun setUp() {
super.setUp()
myFixture.enableInspections(JaiUnresolvedModuleInspection::class.java)
}
fun testUnresolvedLoadPathIsReported() {
myFixture.configureByText(
"main.jai",
"#load \"<warning descr=\"Cannot resolve #load path 'missing.jai'\">missing.jai</warning>\";",
)
myFixture.checkHighlighting()
}
fun testUnresolvedImportPathIsReported() {
myFixture.configureByText(
"main.jai",
"#import \"<warning descr=\"Cannot resolve #import path 'missing-module'\">missing-module</warning>\";",
)
myFixture.checkHighlighting()
}
fun testResolvedLoadPathIsNotReported() {
myFixture.addFileToProject("other.jai", "value := 1;")
myFixture.configureByText("main.jai", "#load \"other.jai\";")
myFixture.checkHighlighting()
}
fun testResolvedImportPathIsNotReported() {
myFixture.configureByText("main.jai", "#import \"Basic\";")
myFixture.checkHighlighting()
}
fun testImportStringIsNotReportedAsAnUnresolvedPath() {
myFixture.configureByText("main.jai", "#import,string \"inline code\";")
myFixture.checkHighlighting()
}
}