Compare commits

..

2 Commits

Author SHA1 Message Date
hgranthorner
c6b30e32d6 Add unresolved import and load inspection 2026-08-06 16:18:34 -04:00
hgranthorner
8fcd2cca05 Document formatter support 2026-08-06 15:50:54 -04:00
7 changed files with 123 additions and 11 deletions

View File

@@ -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
(78).
- 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)

View File

@@ -28,6 +28,10 @@
loaded, imported, or configured external Jai files.
- Indexed external Jai roots so find-usages can cross project/library boundaries.
- Rename and find-usages support for declaration names and resolved references.
- 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

View File

@@ -25,9 +25,13 @@ never from online material (see `AGENTS.md`).
`#import`/`#load`, including configured external roots
- Indexed external Jai roots so find-usages can cross project/library boundaries
- Rename and find-usages for declaration names and their resolved references
- 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. Formatting, inspections, and compiler
fields and other type-qualified members. Additional inspections and compiler
integration are planned later.
See `docs/BUILD_PLAN.md` for the phase plan.

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) |
| **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 06 are implemented and verified headlessly. The next decision is
whether to continue with the optional inspections and compiler integration work
in Phases 78.
Phases 06 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.

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
language="Jai"
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
language="Jai"
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()
}
}