From c6b30e32d62685ce33c457f141d12416cf238fe5 Mon Sep 17 00:00:00 2001 From: hgranthorner <37941012+hgranthorner@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:18:34 -0400 Subject: [PATCH] Add unresolved import and load inspection --- AGENTS.md | 17 ++++--- CHANGELOG.md | 1 + README.md | 5 +- docs/BUILD_PLAN.md | 8 ++-- .../JaiUnresolvedModuleInspection.kt | 45 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 7 +++ .../JaiUnresolvedModuleInspectionTest.kt | 47 +++++++++++++++++++ 7 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt create mode 100644 src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt diff --git a/AGENTS.md b/AGENTS.md index 904ce06..9a0a160 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,6 +66,7 @@ dev.hgh.jai.editor.JaiEditorSupportTest tests=7 dev.hgh.jai.editor.JaiFoldingBuilderTest tests=3 dev.hgh.jai.findusages.JaiFindUsagesTest tests=2 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.JaiLexerTest tests=13 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.settings.JaiProjectSettingsTest tests=3 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: @@ -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 gate. Headless formatter tests cover registration, representative formatting, 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 @@ -202,14 +207,14 @@ Tier 3: parsed 714 files, 714 clean (100.0%), 0 with errors, 0 PsiErrorElements ### In progress — pick up here -1. **Phases 7+** — inspections, compiler integration, and other optional work - remain. `#asm` bodies are intentionally consumed opaquely, so nothing inside - them has PSI yet. +1. **Phases 7+** — additional inspections, quick fixes, live templates, and + compiler integration remain. `#asm` bodies are intentionally consumed + opaquely, so nothing inside them has PSI yet. ### Next planned increment -- Decide whether to continue with inspections and compiler integration phases - (7–8). +- Expand Phase 7 with additional high-confidence inspections and quick fixes; + struct-field resolution and completion remain a separate known gap. ### Open questions for the user (unanswered) diff --git a/CHANGELOG.md b/CHANGELOG.md index 166cc42..0d682fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - PSI-aware formatter with operator and punctuation spacing, block indentation, directive-flag handling, and preservation of opaque `#asm` bodies; formatting is idempotent across all 714 corpus files. +- Inspection warnings for unresolved `#import` and `#load` module/file paths. ### Known gaps diff --git a/README.md b/README.md index 03ec3ff..2d1fdab 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,11 @@ never from online material (see `AGENTS.md`). - PSI-aware formatting for operator and punctuation spacing, block indentation, directive flags, and opaque `#asm` bodies; formatting is idempotent across the full local corpus +- Inspection warnings for unresolved `#import` and `#load` module/file paths Remaining code-insight gaps include completion and symbol resolution for struct -fields and other type-qualified members. Inspections and compiler integration -are planned later. +fields and other type-qualified members. Additional inspections and compiler +integration are planned later. See `docs/BUILD_PLAN.md` for the phase plan. ## Install it locally diff --git a/docs/BUILD_PLAN.md b/docs/BUILD_PLAN.md index f06f539..feb2761 100644 --- a/docs/BUILD_PLAN.md +++ b/docs/BUILD_PLAN.md @@ -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) | | **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 | -| **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` | 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 wrapper. -Phases 0–6 are implemented and verified headlessly. The next decision is -whether to continue with the optional inspections and compiler integration work -in Phases 7–8. +Phases 0–6 are implemented and verified headlessly. Phase 7 has an initial +unresolved `#import`/`#load` path inspection; additional inspections and +compiler integration remain optional follow-up work. diff --git a/src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt b/src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt new file mode 100644 index 0000000..eb8accd --- /dev/null +++ b/src/main/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspection.kt @@ -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, + ) + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 1dc18eb..f5b8220 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -49,6 +49,13 @@ + diff --git a/src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt b/src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt new file mode 100644 index 0000000..5140b82 --- /dev/null +++ b/src/test/kotlin/dev/hgh/jai/inspection/JaiUnresolvedModuleInspectionTest.kt @@ -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 \"missing.jai\";", + ) + + myFixture.checkHighlighting() + } + + fun testUnresolvedImportPathIsReported() { + myFixture.configureByText( + "main.jai", + "#import \"missing-module\";", + ) + + 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() + } +}