Skip to content

Commit 00bbc1a

Browse files
authored
Merge pull request #55 from j-plugins/claude/intellij-platform-analysis-Uaqhj
Add comprehensive test suite covering indexes, references, and providers
2 parents 90bed87 + 6fec9cd commit 00bbc1a

141 files changed

Lines changed: 4579 additions & 283 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
.intellijPlatform
55
.kotlin
66
.qodana
7+
.claude/worktrees/
78
build

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,21 @@
22

33
# spiral-plugin Changelog
44

5+
## [Unreleased]
6+
7+
### Added
8+
- Test suite covering indexes, references, line markers, folding, and run-configuration settings (30 new test classes; full `gradle test` is now meaningful).
9+
- `com.intellij.modules.json` added to `platformBundledPlugins` so the PHP plugin loads in the test sandbox (was the root cause of an empty effective extension set).
10+
- `VIEWS_RENDER_SIGNATURE` and `ENV_FUNCTION` / `AUTOWIRE` constants in `SpiralFrameworkClasses` — replaces hardcoded FQNs scattered across `ViewReferenceContributor`, `EnvFoldingBuilder`, and `ContainerReferenceContributor`.
11+
12+
### Fixed
13+
- **Run-configuration settings corruption**`SpiralConsoleCommandRunConfigurationSettings` aliased `workingDirectory` and `documentRoot` onto the `binary` XML attribute. Each property now persists under its own slot; existing saved configurations migrate cleanly.
14+
- **`ObjectStreamDataExternalizer.read` NPE** on null payloads — cast widened from `as T` to `as T?` so callers can decide how to handle null.
15+
- **CQRS implicit usage over-reporting**`CqrsHandlersImplicitUsageProvider.isClassWithCustomizedInitialization` returned `true` unconditionally, marking every PHP class as having customized init.
16+
- **CQRS gutter icons missing**`CqrsHandlersLineMarkerProvider.getLineMarkerInfo` matched on `PhpClass`, but the platform calls line markers on leaf elements only; re-anchored to the class-name identifier.
17+
- **Dumb-mode crashes** at three call sites that query `PrototypedIndex` / `CqrsIndexUtil` without `DumbService` guarding — `PrototypedCompletion`, `CqrsHandlersLineMarkerProvider`, and `PrototypedPropertyReference` now early-return while indexing.
18+
19+
### Changed
20+
- `gradle.properties` `platformBundledPlugins` extended with the PHP plugin's transitive `com.intellij.modules.json` requirement.
21+
- Folding placeholder for `env('KEY')` calls uses the central `SpiralFrameworkClasses.ENV_FUNCTION` constant.
22+

analysis/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Per-file analysis of the Spiral Framework plugin
2+
3+
This directory contains a static code review of every Kotlin source file in
4+
`src/main/kotlin/com/github/xepozz/spiral/`. Each report is in a sibling file
5+
whose path mirrors the source layout.
6+
7+
Each report has the same structure (defined in [`_CONTEXT.md`](_CONTEXT.md)):
8+
short summary, then numbered issues with severity tag, file:line location,
9+
problem, why it matters, and a suggested fix.
10+
11+
## Severity tag legend
12+
13+
| Tag | Meaning |
14+
|---|---|
15+
| `[BUG]` | Behaves incorrectly under some real-world input. |
16+
| `[PERF]` | Slow / wasteful but functionally correct. |
17+
| `[API-MISUSE]` | IntelliJ Platform API used in a way the platform discourages. |
18+
| `[THREAD]` | Threading / EDT / read-action violation. |
19+
| `[DUMB-MODE]` | Index access not guarded against dumb mode. |
20+
| `[INDEX]` | `FileBasedIndexExtension` shape / versioning issue. |
21+
| `[LEAK]` | Disposer / lifecycle leak risk. |
22+
| `[I18N]` | Hardcoded user-visible string not via `SpiralBundle`. |
23+
| `[STYLE]` | Style / dead-code / commented `println`. |
24+
| `[MAINTAINABILITY]` | Readability / convention drift. |
25+
26+
## High-signal hotspots
27+
28+
These files have the most impactful findings — fix them first.
29+
30+
### `[BUG]` — observable misbehavior
31+
- [console/run/SpiralConsoleCommandRunConfigurationSettings.md](console/run/SpiralConsoleCommandRunConfigurationSettings.md)
32+
- [console/run/SpiralRunCommandAction.md](console/run/SpiralRunCommandAction.md)
33+
- [cqrs/index/CqrsCommandHandlerIndex.md](cqrs/index/CqrsCommandHandlerIndex.md)
34+
- [cqrs/index/CqrsCommandIndex.md](cqrs/index/CqrsCommandIndex.md)
35+
- [cqrs/index/CqrsQueryHandlerIndex.md](cqrs/index/CqrsQueryHandlerIndex.md)
36+
- [cqrs/index/CqrsQueryIndex.md](cqrs/index/CqrsQueryIndex.md)
37+
- [prototyped/PrototypedPropertyReference.md](prototyped/PrototypedPropertyReference.md)
38+
- [references/FunctionsReferenceContributor.md](references/FunctionsReferenceContributor.md)
39+
- [router/endpoints/SpiralEndpointsProvider.md](router/endpoints/SpiralEndpointsProvider.md)
40+
- [views/injection/PHPLanguageInjector.md](views/injection/PHPLanguageInjector.md)
41+
42+
### `[DUMB-MODE]``IndexNotReadyException` risk during indexing
43+
- [config/index/ConfigSectionIndex.md](config/index/ConfigSectionIndex.md)
44+
- [config/index/PrototypedIndex.md](config/index/PrototypedIndex.md)
45+
- [console/run/SpiralRunAnythingProvider.md](console/run/SpiralRunAnythingProvider.md)
46+
- [cqrs/CqrsHandlersLineMarkerProvider.md](cqrs/CqrsHandlersLineMarkerProvider.md)
47+
- [cqrs/CqrsIndexUtil.md](cqrs/CqrsIndexUtil.md)
48+
- [prototyped/PrototypedAttributeReferenceContributor.md](prototyped/PrototypedAttributeReferenceContributor.md)
49+
- [prototyped/PrototypedCompletion.md](prototyped/PrototypedCompletion.md)
50+
- [prototyped/PrototypedPropertyReference.md](prototyped/PrototypedPropertyReference.md)
51+
- [references/DirectoryReference.md](references/DirectoryReference.md)
52+
- [references/FunctionsReferenceContributor.md](references/FunctionsReferenceContributor.md)
53+
- [views/index/ViewsNamespaceIndexUtil.md](views/index/ViewsNamespaceIndexUtil.md)
54+
55+
### `[LEAK]` — disposer / lifecycle risk
56+
- [router/endpoints/SpiralGroup.md](router/endpoints/SpiralGroup.md)
57+
58+
## How this was produced
59+
60+
This analysis was generated by spawning a code-review subagent per file, each
61+
seeded with the IntelliJ-Platform best-practices checklist in
62+
[`_CONTEXT.md`](_CONTEXT.md). Reports are independent — there is no
63+
cross-file deduplication of recurring patterns (e.g. the same
64+
`println`-in-production finding may appear in many files). Use the section
65+
links above to triage by severity.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Analysis: src/main/kotlin/com/github/xepozz/spiral/SpellcheckingDictionaryProvider.kt
2+
3+
## Summary
4+
Implements the IntelliJ Platform's BundledDictionaryProvider to register a spellcheck dictionary for the plugin. This is a minimal extension that points to the bundled dictionary resource.
5+
6+
## Issues
7+
8+
### 1. [MAINTAINABILITY] Path to dictionary file should be a constant
9+
- Location: SpellcheckingDictionaryProvider.kt:6
10+
- Problem: The dictionary path `/META-INF/spellcheck.dic` is hardcoded as a string literal instead of being defined as a constant, making it fragile if the resource is ever relocated.
11+
- Why it matters: If the dictionary file moves, there's no single source of truth to update; the hardcoded string is easy to miss in refactoring.
12+
- Suggested fix: Extract the path as a companion object constant or object-level constant, similar to how `SpiralBundle.BUNDLE` is defined in `SpiralBundle.kt`.
13+

analysis/SpiralBundle.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Analysis: src/main/kotlin/com/github/xepozz/spiral/SpiralBundle.kt
2+
3+
## Summary
4+
Provides centralized access to i18n messages via DynamicBundle, with both eager and lazy message retrieval methods. All user-facing strings in the plugin route through this singleton.
5+
6+
## Issues
7+
8+
### 1. [STYLE] Suppress("unused") on messagePointer without suppression scope
9+
- Location: SpiralBundle.kt:16
10+
- Problem: The `@Suppress("unused")` annotation is applied to the `messagePointer` method, but it appears to suppress the warning for the entire method definition rather than a specific element. The method itself may be unused from static analysis, but the suppression should clarify what is suppressed.
11+
- Why it matters: Overly broad suppressions can mask genuine unused code. If the method is indeed used elsewhere or reserved for future use, a comment explaining the suppression would improve maintainability.
12+
- Suggested fix: Either add a comment explaining why this method is kept despite being unused, or verify it is actually used in the codebase. If truly unused, consider removing it or documenting it as a utility for plugin consumers.
13+
14+
No other significant issues found beyond this minor documentation enhancement.
15+

analysis/SpiralFrameworkClasses.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Analysis: src/main/kotlin/com/github/xepozz/spiral/SpiralFrameworkClasses.kt
2+
3+
## Summary
4+
Central registry of Spiral framework FQN constants with leading backslash notation (`\Spiral\...`). All framework-specific class references throughout the plugin should use these constants rather than hardcoded strings.
5+
6+
No significant issues found.
7+

analysis/SpiralIcons.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Analysis: src/main/kotlin/com/github/xepozz/spiral/SpiralIcons.kt
2+
3+
## Summary
4+
Provides centralized icon access via `IconLoader.getIcon()`, serving as the single source of truth for the plugin's Spiral framework icon. Used throughout the plugin for completion items, gutter markers, and UI presentations.
5+
6+
## Issues
7+
8+
### 1. [API-MISUSE] IconLoader.getIcon() can return null; no null safety
9+
- Location: SpiralIcons.kt:7
10+
- Problem: `IconLoader.getIcon()` may return `null` if the resource is not found or unavailable, but the result is assigned directly to `val SPIRAL` with no null check. The field type is inferred as `Icon?` (nullable), which means all usages must handle potential null values.
11+
- Why it matters: Any code that accesses `SpiralIcons.SPIRAL` without null-safety could crash at runtime if the icon fails to load. Completion items, gutter markers, and other UI elements expecting a non-null Icon would throw NPE.
12+
- Suggested fix: Either (1) assert the result is non-null with `!!` and a comment if the icon is guaranteed to exist, (2) provide a fallback icon using `?:`, or (3) wrap access in a utility function that provides a safe default. Option 2 is preferred: `val SPIRAL = IconLoader.getIcon("/icons/spiral/icon.svg", javaClass) ?: AllIcons.General.Information` or similar.
13+

analysis/SpiralViewUtil.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Analysis: src/main/kotlin/com/github/xepozz/spiral/SpiralViewUtil.kt
2+
3+
## Summary
4+
Utility object defining view-related constants: the `.dark.php` file suffix for Spiral views and a map of predefined directory aliases (public, vendor, runtime, etc.) used for resolving directory references in completion and navigation.
5+
6+
## Issues
7+
8+
### 1. [STYLE] PREDEFINED_DIRS should be immutable (val with unmodifiable Map)
9+
- Location: SpiralViewUtil.kt:6
10+
- Problem: `PREDEFINED_DIRS` is declared as a mutable `mapOf(...)` result, but `mapOf()` returns an immutable `Map`. However, the declaration reads as if it could be modified. The type is implicitly `Map<String, String>`, which is correct, but clarity could be improved.
11+
- Why it matters: While technically immutable, the absence of an explicit immutability annotation (like `val PREDEFINED_DIRS: Map<String, String> =`) leaves reader uncertainty about mutability intent.
12+
- Suggested fix: Add an explicit type annotation to clarify immutability: `val PREDEFINED_DIRS: Map<String, String> = mapOf(...)`. This makes the immutability contract explicit and aids IDE inference.
13+
14+
No other significant issues found.
15+

analysis/_CONTEXT.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Shared analysis context — Spiral Framework plugin
2+
3+
This file is shared context for per-file analysis subagents.
4+
5+
## Project facts (from CLAUDE.md)
6+
- Language: Kotlin (JVM toolchain 21). IntelliJ Platform Gradle Plugin 2.x.
7+
- Plugin ID: `com.github.xepozz.spiral`. Mandatory deps: `com.intellij.modules.platform`, `com.jetbrains.php`.
8+
- Since-build: 251 (IDE 2025.1+). `platformType = IU`. PhpStorm-only APIs from `com.jetbrains.php` are available.
9+
- Tests: JUnit 4 + `BasePlatformTestCase`.
10+
- All Spiral FQNs MUST live in `SpiralFrameworkClasses.kt` with leading backslash (`\Spiral\...`).
11+
- All user-facing strings MUST go through `SpiralBundle.message(...)`.
12+
- Icons via `SpiralIcons` only.
13+
- No `println` in new code — use `com.intellij.openapi.diagnostic.Logger`.
14+
- Indexes extend `common/index/AbstractIndex.kt`, must bump `getVersion()` when output shape changes.
15+
- `ObjectStreamDataExternalizer` uses Java serialization — value classes must stay `Serializable`.
16+
- Configuration cache enabled — don't break it.
17+
18+
## IntelliJ Platform best-practices checklist (use as a lens when reviewing)
19+
20+
### Threading & locks
21+
- Long PSI traversal, VFS scans, index queries must NOT run on EDT.
22+
- Reading PSI/VFS requires a read action (or smart-mode read access). Writing requires a write action on EDT.
23+
- Re-validate PSI / VirtualFile (`isValid()`) when re-entering a read action after suspension.
24+
- `ProcessCanceledException` must NEVER be swallowed — always rethrow. Catch only specific exceptions.
25+
- Avoid `runReadAction` on EDT for heavy work — prefer `ReadAction.nonBlocking(...).submit(...)` or background actions.
26+
27+
### Dumb mode & indexes
28+
- `FileBasedIndex.getInstance().getValues(...)` / `getAllKeys(...)` is forbidden in dumb mode — guard with `DumbService.isDumb` or `runReadActionInSmartMode`.
29+
- `LineMarkerProvider`, `ReferenceContributor`, `CompletionContributor` may be invoked during indexing — handle dumb mode.
30+
- Concrete `FileBasedIndexExtension` must bump `getVersion()` if the indexed shape changes (keys, value class, input filter, key descriptor).
31+
- `dependsOnFileContent()` defaults to true in `AbstractIndex` — fine for content indexes, but path-only indexes should override.
32+
- Value externalizer using `ObjectStreamDataExternalizer` (Java serialization) is fragile across IDE/plugin versions — prefer `DataExternalizer<T>` with explicit `save`/`read`.
33+
34+
### PSI references
35+
- `PsiReference.resolve()` must be fast — cache where possible, use `CachedValuesManager` for repeated resolves.
36+
- `PsiReferenceContributor` patterns should be specific to avoid invocation on every PSI element.
37+
- `getRangeInElement()` must be relative to the host element (not absolute).
38+
- Multi-resolve references should implement `PsiPolyVariantReference`.
39+
- `getVariants()` returning many items: use `LookupElementBuilder` and consider `withIcon`, `withTypeText`, `withTailText` for UX.
40+
41+
### Completion contributors
42+
- `addCompletions` must be fast; avoid blocking I/O.
43+
- Always check `result.isStopped`/`ProgressManager.checkCanceled()` in long loops.
44+
- `extend(...)` patterns should be as specific as possible (use `psiElement().withParent(...)`).
45+
- Don't call `result.stopHere()` unless intentionally suppressing other contributors.
46+
47+
### LineMarkerProvider
48+
- `LineMarkerProvider.getLineMarkerInfo` is invoked on the leaf element (not the whole class/function) — anchor to identifier, not container.
49+
- `RelatedItemLineMarkerProvider` is the modern alternative.
50+
- Marker must be cheap — heavy resolution should be deferred via `NavigationGutterIconBuilder`.
51+
52+
### Run configurations
53+
- `ConfigurationFactory.getId()` must be stable across versions — changing it breaks user-saved configs.
54+
- `RunConfiguration.checkConfiguration()` should throw `RuntimeConfigurationError` / `RuntimeConfigurationWarning`.
55+
- `RunProfileState.execute()` should not block EDT.
56+
- Read/write external state via `JDOMExternalizer` / explicit XML; `Element` content is persisted to workspace.xml.
57+
58+
### Service lifecycle / Disposers
59+
- Never use `Application` or `Project` directly as a Disposable parent inside plugin code — use the plugin's service.
60+
- Listeners registered without a `Disposable` parent leak across dynamic-plugin reloads.
61+
- Project services should be `@Service(Service.Level.PROJECT)`; app services `@Service(Service.Level.APP)`.
62+
63+
### Other quality concerns
64+
- Hard-coded user-visible strings (must be in `SpiralBundle`).
65+
- Hard-coded FQNs scattered around (must be in `SpiralFrameworkClasses`).
66+
- `println` / `System.out` — replace with `Logger.getInstance(...)`.
67+
- `!!` on platform-returned nullables — risky; use `?:` or graceful fallback.
68+
- Companion-object constants should be `const val` when possible.
69+
- Unused imports / unused parameters / dead code.
70+
- Public visibility on classes that should be `internal`.
71+
- `var` instead of `val` where mutation isn't needed.
72+
73+
## Output format expected from each subagent
74+
75+
Each subagent writes ONE markdown file at the absolute path it is given, with this structure:
76+
77+
```
78+
# Analysis: <relative source path>
79+
80+
## Summary
81+
1-3 sentences describing the file's responsibility.
82+
83+
## Issues
84+
For each issue: a numbered heading with severity tag.
85+
86+
### 1. [SEVERITY] Short title
87+
- Location: <relative path>:<line range>
88+
- Problem: <what's wrong>
89+
- Why it matters: <impact>
90+
- Suggested fix: <concrete remediation>
91+
92+
Severity tags: [BUG], [PERF], [API-MISUSE], [STYLE], [MAINTAINABILITY], [I18N], [THREAD], [DUMB-MODE], [INDEX], [LEAK].
93+
94+
## No-issue note
95+
If nothing meaningful is found, write "No significant issues found." and stop.
96+
```
97+
98+
Keep findings concrete, with file:line references. Don't speculate about issues that aren't present.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Analysis: common/index/AbstractIndex.kt
2+
3+
## Summary
4+
This is a minimal abstract base class for file-based indexes used across the plugin. It provides default implementations for key descriptor, content dependency, and version handling, requiring concrete subclasses to implement name, input filter, and the indexer itself.
5+
6+
## Issues
7+
8+
### 1. [INDEX] Missing version override guidance in subclasses
9+
- Location: AbstractIndex.kt:21
10+
- Problem: The class defines `getVersion() = 1` as a static default, but the CLAUDE.md documentation explicitly states "Bump `getVersion()` in the concrete index whenever you change the indexer output shape." This base implementation provides no mechanism or comment to enforce version bumping, and subclasses may inherit the version value without realizing it needs to change.
11+
- Why it matters: File-based index version mismatches cause stale caches to corrupt results. Silently inheriting version 1 across all subclasses means any index logic change risks silent data corruption.
12+
- Suggested fix: Mark `getVersion()` as `open` and add a KDoc comment above it warning subclasses to override and increment when the indexer output shape changes (keys, value type, input filter).
13+
14+
### 2. [MAINTAINABILITY] Ambiguous default for dependsOnFileContent
15+
- Location: AbstractIndex.kt:15
16+
- Problem: `dependsOnFileContent() = true` is hardcoded as the default. Per the CLAUDE.md conventions, "path-only indexes should override" this. However, there is no documentation at the class or method level indicating which indexes should or should not depend on file content.
17+
- Why it matters: Path-only indexes (e.g., detecting file names without examining content) will inefficiently rebuild on every file content change. Developers may not realize they need to override.
18+
- Suggested fix: Add a KDoc comment explaining that content-dependent indexes can use the default, but path-only indexes must override with `override fun dependsOnFileContent() = false`.
19+
20+
## No-issue note
21+
The core design is sound for a minimal base class. Both issues are about improving guidance rather than structural flaws.

0 commit comments

Comments
 (0)