pi-lens 3.8.38 → 3.8.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -5
- package/README.md +1 -1
- package/clients/biome-client.ts +5 -4
- package/clients/cache-manager.ts +4 -1
- package/clients/dispatch/fact-scheduler.ts +2 -1
- package/clients/dispatch/fact-store.ts +4 -6
- package/clients/dispatch/integration.ts +4 -5
- package/clients/dispatch/rules/quality-rules.ts +10 -15
- package/clients/dispatch/runners/biome-check.ts +1 -1
- package/clients/dispatch/runners/eslint.ts +1 -1
- package/clients/dispatch/runners/lsp.ts +1 -5
- package/clients/dispatch/runners/psscriptanalyzer.ts +1 -1
- package/clients/dispatch/runners/similarity.ts +1 -4
- package/clients/dispatch/utils/lsp-diagnostics.ts +3 -9
- package/clients/formatters.ts +12 -7
- package/clients/lsp/client.ts +62 -27
- package/clients/lsp/index.ts +20 -11
- package/clients/lsp/launch.ts +108 -38
- package/clients/lsp/server.ts +77 -58
- package/clients/pipeline.ts +20 -9
- package/clients/read-guard-tool-lines.ts +15 -2
- package/clients/read-guard.ts +51 -32
- package/clients/runtime-coordinator.ts +2 -2
- package/clients/runtime-session.ts +2 -0
- package/clients/runtime-tool-result.ts +21 -0
- package/clients/secrets-scanner.ts +1 -1
- package/clients/session-summary.ts +1 -1
- package/clients/tool-policy.ts +1982 -1936
- package/clients/tree-sitter-query-loader.ts +3 -2
- package/commands/booboo.ts +48 -15
- package/index.ts +35 -13
- package/package.json +2 -2
- package/rules/rule-catalog.json +76 -1
- package/rules/tree-sitter-queries/abap/delete-where.yml +46 -0
- package/rules/tree-sitter-queries/c/case-range-multiple-values.yml +65 -0
- package/rules/tree-sitter-queries/c/goto-into-block.yml +72 -0
- package/rules/tree-sitter-queries/c/goto-label-order.yml +65 -0
- package/rules/tree-sitter-queries/cobol/alter-statement.yml +39 -0
- package/rules/tree-sitter-queries/cobol/lock-table-cobol.yml +35 -0
- package/rules/tree-sitter-queries/cpp/no-auto-ptr.yml +49 -0
- package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +59 -0
- package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +57 -0
- package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +52 -0
- package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +58 -0
- package/rules/tree-sitter-queries/cpp/unnecessary-bit-ops.yml +58 -0
- package/rules/tree-sitter-queries/csharp/async-await-identifiers.yml +50 -0
- package/rules/tree-sitter-queries/csharp/is-with-this.yml +52 -0
- package/rules/tree-sitter-queries/csharp/no-dangerous-get-handle.yml +59 -0
- package/rules/tree-sitter-queries/csharp/no-operator-eq-reference.yml +60 -0
- package/rules/tree-sitter-queries/csharp/no-thread-resume-suspend.yml +60 -0
- package/rules/tree-sitter-queries/css/calc-spacing.yml +50 -0
- package/rules/tree-sitter-queries/java/infinite-loop.yml +58 -0
- package/rules/tree-sitter-queries/java/infinite-recursion.yml +58 -0
- package/rules/tree-sitter-queries/java/junit-call-super.yml +63 -0
- package/rules/tree-sitter-queries/java/main-should-not-throw.yml +63 -0
- package/rules/tree-sitter-queries/java/mockito-initialized.yml +66 -0
- package/rules/tree-sitter-queries/java/name-capitalization-conflict.yml +54 -0
- package/rules/tree-sitter-queries/java/no-clone-override.yml +60 -0
- package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +74 -0
- package/rules/tree-sitter-queries/java/no-exit-methods.yml +50 -0
- package/rules/tree-sitter-queries/java/no-field-shadowing.yml +66 -0
- package/rules/tree-sitter-queries/java/no-future-keywords.yml +49 -0
- package/rules/tree-sitter-queries/java/no-octal-values.yml +48 -0
- package/rules/tree-sitter-queries/java/no-threadgroup.yml +52 -0
- package/rules/tree-sitter-queries/java/no-threads-in-constructors.yml +73 -0
- package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +58 -0
- package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +55 -0
- package/rules/tree-sitter-queries/java/resources-closed.yml +57 -0
- package/rules/tree-sitter-queries/java/short-circuit-logic.yml +57 -0
- package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +75 -0
- package/rules/tree-sitter-queries/java/springboot-default-package.yml +69 -0
- package/rules/tree-sitter-queries/java/switch-fall-through.yml +70 -0
- package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +62 -0
- package/rules/tree-sitter-queries/java/tests-include-assertions.yml +60 -0
- package/rules/tree-sitter-queries/java/unnecessary-bit-ops-java.yml +57 -0
- package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +64 -0
- package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +52 -0
- package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +49 -0
- package/rules/tree-sitter-queries/php/no-exit-die.yml +52 -0
- package/rules/tree-sitter-queries/php/this-in-static-context.yml +75 -0
- package/rules/tree-sitter-queries/plsql/delete-update-where.yml +56 -0
- package/rules/tree-sitter-queries/plsql/end-loop-semicolon.yml +51 -0
- package/rules/tree-sitter-queries/plsql/fetch-bulk-collect-limit.yml +47 -0
- package/rules/tree-sitter-queries/plsql/forallsave-exceptions.yml +51 -0
- package/rules/tree-sitter-queries/plsql/lock-table.yml +42 -0
- package/rules/tree-sitter-queries/plsql/nchar-nvarchar2-bytes.yml +54 -0
- package/rules/tree-sitter-queries/plsql/no-synchronize.yml +47 -0
- package/rules/tree-sitter-queries/plsql/not-null-initialization.yml +59 -0
- package/rules/tree-sitter-queries/plsql/raise-application-error-codes.yml +50 -0
- package/rules/tree-sitter-queries/python/exit-signature-check.yml +66 -0
- package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +57 -0
- package/rules/tree-sitter-queries/python/iter-return-iterator.yml +59 -0
- package/rules/tree-sitter-queries/python/no-super-torchscript.yml +52 -0
- package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +67 -0
- package/rules/tree-sitter-queries/python/return-in-generator.yml +58 -0
- package/rules/tree-sitter-queries/python/return-in-init.yml +59 -0
- package/rules/tree-sitter-queries/python/send-file-mimetype.yml +53 -0
- package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +57 -0
- package/rules/tree-sitter-queries/typescript/default-not-last.yml +54 -0
- package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +51 -0
- package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +54 -0
- package/rules/tree-sitter-queries/typescript/infinite-loop.yml +55 -0
- package/rules/tree-sitter-queries/typescript/self-assignment.yml +46 -0
- package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +64 -0
- package/tools/lsp-navigation.js +14 -16
- package/tools/lsp-navigation.ts +14 -18
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ All notable changes to pi-lens will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.8.40] - 2026-05-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **60+ SonarCloud BLOCKER tree-sitter rules** — comprehensive BLOCKER severity rules across 13 languages:
|
|
12
|
+
- **Java (11 rules)**: no-exit-methods, no-threads-in-constructors, switch-fall-through, no-wait-notify-on-thread, no-double-checked-locking, no-future-keywords, no-field-shadowing, junit-call-super, no-octal-values, short-circuit-logic, infinite-loop, infinite-recursion, name-capitalization-conflict, mockito-initialized, resources-closed, unnecessary-bit-ops-java
|
|
13
|
+
- **TypeScript (5 rules)**: infinite-loop, self-assignment, duplicate-function-arg, empty-switch-case, default-not-last, switch-case-termination
|
|
14
|
+
- **JavaScript (1 rule)**: switch-case-termination-js (replaces switch-fall-through-js)
|
|
15
|
+
- **PL/SQL (7 rules)**: forallsave-exceptions, not-null-initialization, end-loop-semicolon, raise-application-error-codes, no-synchronize, lock-table, nchar-nvarchar2-bytes, delete-update-where, fetch-bulk-collect-limit
|
|
16
|
+
- **Python (8 rules)**: send-file-mimetype, no-super-torchscript, return-in-init, yield-return-outside-function, notimplemented-boolean-context, exit-signature-check, return-in-generator, iter-return-iterator, in-operator-unsupported
|
|
17
|
+
- **C++ (5 rules)**: unnecessary-bit-ops, noexcept-functions, no-auto-ptr, no-memset-sensitive-data, no-scoped-lock-without-args, no-confused-move-forward
|
|
18
|
+
- **PHP (2 rules)**: this-in-static-context, no-exit-die
|
|
19
|
+
- **C (3 rules)**: case-range-multiple-values, goto-label-order, goto-into-block
|
|
20
|
+
- **C# (5 rules)**: is-with-this, no-operator-eq-reference, no-dangerous-get-handle, no-thread-resume-suspend, async-await-identifiers
|
|
21
|
+
- **Kotlin (1 rule)**: prepared-statement-indices
|
|
22
|
+
- **ABAP (1 rule)**: delete-where
|
|
23
|
+
- **COBOL (2 rules)**: alter-statement, lock-table-cobol
|
|
24
|
+
- **CSS (1 rule)**: calc-spacing
|
|
25
|
+
- **rule-catalog.json** updated with all 60+ new rule registrations
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **Read-guard: false `file_modified` blocks after own edits** — `ReadGuard` was blocking the second edit to a file because the model's first write changed the file's mtime, making `FileTime.hasChanged()` return `true` on the next `checkEdit`. Added `recordWritten(filePath)` to `ReadGuard` and wired it into the `tool_result` handler (post-write, file already on disk), so the FileTime stamp stays in sync with the model's own writes. Eliminates the spurious `file_modified` blocks that appeared on every multi-edit file in a session.
|
|
30
|
+
|
|
31
|
+
- **LSP: parallel-turn root-resolution timeouts** — `NearestRoot` performed a fresh `fs.stat` directory walk on every call with no caching. When Claude Code edited multiple files simultaneously (e.g. a 4-file turn), all pipelines raced `NearestRoot` concurrently, saturating Windows filesystem I/O and triggering the 750ms `lsp_client_wait_timeout` on all but the first. `NearestRoot` now maintains per-instance result and in-flight caches keyed by resolved directory: successful roots are cached for the session lifetime; concurrent calls for the same directory share one walk promise. Only successful roots are cached so a `package.json` created mid-session is still detected on the next call.
|
|
32
|
+
|
|
33
|
+
- **Memory: `lastAnalyzedStateByFile` cleared each turn** — module-level Map in `runtime-tool-result.ts` accumulated dead entries across turns (entries from previous turns can never match the new `turnIndex`). Now cleared at `turn_start` alongside `runtime.beginTurn()`, keeping the map bounded to files touched in the current turn only. (refs #50)
|
|
34
|
+
- **Memory: `recentTouches` stale entry eviction** — `LSPService.recentTouches` grew unboundedly across a session with one entry per unique file path. Entries older than `TOUCH_DEBOUNCE_MS` are already ignored by `shouldSkipTouch`; a threshold-based sweep (triggered when size > 200) now removes them. (refs #50)
|
|
35
|
+
- **Memory: orphaned LSP child processes on Windows** — `clientShutdown` only called `process.kill()` which on Windows terminates the direct child but leaves grandchildren (e.g. `tsserver.js`) as orphaned OS processes each holding 300–600MB. Both the normal shutdown and crash paths now go through a shared `killProcessTree` helper: on Windows it runs `taskkill /F /T` via absolute `SystemRoot` path and awaits completion before returning; on other platforms it sends `SIGTERM`. The SIGKILL fallback timer is also skipped on Windows since `taskkill /F` already force-terminates. (refs #50)
|
|
36
|
+
- **Memory: file-time session state not cleared on session reset** — `clearAllSessions()` from `file-time.ts` is now called during `handleSessionStart`, clearing stale file timestamp state that previously accumulated across session switches. (refs #50)
|
|
37
|
+
- **Memory: pending ast-grep warn timers not cancelled on session reset** — `resetDispatchBaselines()` left active `astGrepWarnDebounceTimers` running into a cleared session context. Now explicitly cancelled and cleared on reset. (refs #50)
|
|
38
|
+
- **Security: `taskkill` spawned via absolute path** — both the normal shutdown and crash paths now resolve `taskkill.exe` through `process.env.SystemRoot` instead of relying on PATH, eliminating the SonarCloud PATH-injection hotspot.
|
|
39
|
+
- **LSP: shutdown cannot hang indefinitely** — `client.shutdown()` now bounds the graceful `shutdown` request and proceeds to `exit`/process-tree kill if a server stops responding.
|
|
40
|
+
- **LSP: test cleanup stop helper hardened on Windows** — `stopLSP()` now uses the absolute `taskkill.exe` path, handles already-exited processes, and avoids orphaning grandchildren by killing the process tree before the direct child on Windows.
|
|
41
|
+
|
|
42
|
+
- **booboo project root detection** — `resolveProjectRoot` now walks up to the nearest ancestor with a root marker (`package.json`, `tsconfig.json`, `.git`, etc.), then falls back to walking down one level if exactly one immediate subdirectory has a root marker. Fixes scans running against the wrong directory in nested-project layouts (e.g. `pi-models/pi-models/`).
|
|
43
|
+
|
|
44
|
+
- **Switch-case false positives eliminated** — replaced naive `switch-fall-through` rules with `switch-case-termination` rules that properly recognize `return`, `throw`, and `continue` as valid case terminators. Reduced false positive hits from 174 to 0.
|
|
45
|
+
- **Self-assignment false positives fixed** — changed from `post_filter: same_identifier` to inline `#eq?` predicate so `wave = nextWave` is no longer flagged as self-assignment
|
|
46
|
+
|
|
47
|
+
## [3.8.39] - 2026-05-02
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **Context injection now prepends guidance before the user prompt** — pi-lens previously appended session guidance after the user's message; provider bridges that treat the last message as the active user action would demote the real request. Guidance is now prepended so the user's prompt stays last. (PR #48 by @tifandotme)
|
|
52
|
+
- **jscpd no longer runs on YAML/JSON/Markdown files** — `getFilesForJscpd` now filters to source code extensions only, preventing multi-second delays at `turn_end` when editing rule YAMLs or config files.
|
|
53
|
+
- **ReDoS S5852 final (gleam/zig parsers)** — rewrote `gleamRe` and `zigRe` as line-by-line parsers, eliminating the multiline flag that SonarCloud continued to flag despite `[ \t]*` substitution.
|
|
54
|
+
- **SonarCloud MAJOR code smells (batch 1 & 2)** — `readonly` members, `void` operator removals, nested ternaries, nested template literals, optional chains, duplicate branches, and redundant type alias across 15+ files.
|
|
55
|
+
- **Type-narrow `severityMap` for `Diagnostic.severity` union** — properly satisfies the union type for diagnostic severity mapping.
|
|
56
|
+
- **9 tree-sitter query bugs in new rule files** — predicate outside outermost parens (`cpp/no-auto-ptr`); false-positive `post_filter` gate added (`cpp/no-confused-move-forward`); leaf-node child match removed (`php/this-in-static-context`); invalid node name `class_hereditary` replaced (`java/no-field-shadowing`); field order corrected (`java/no-wait-notify-on-thread`); duplicate `modifiers` blocks merged (`java/spring-session-attributes-setcomplete`); invalid anonymous-node field label removed (`csharp/is-with-this`); inline alternation replaced with two patterns (`python/in-operator-unsupported`); adjacent sibling requirement removed, delegated to `post_filter` (`python/return-in-generator`).
|
|
57
|
+
|
|
7
58
|
## [3.8.38] - 2026-05-02
|
|
8
59
|
|
|
9
60
|
### Added
|
|
@@ -1123,7 +1174,6 @@ All runtime-applicable TypeScript ast-grep rules now have JavaScript equivalents
|
|
|
1123
1174
|
- **Rust performance core (`pi-lens-core`)** — Optional Rust binary for CPU-intensive operations.
|
|
1124
1175
|
All features fall back to TypeScript automatically if the binary is not available (it is **not**
|
|
1125
1176
|
built automatically on `npm install` — run `npm run rust:build` once if you have Rust installed).
|
|
1126
|
-
|
|
1127
1177
|
- **File scanning** — ripgrep’s `ignore` crate for `.gitignore`-aware project scanning
|
|
1128
1178
|
- **Similarity detection** — parallel 57×72 state-matrix index, persisted to
|
|
1129
1179
|
`.pi-lens/rust-index.json` between invocations (fixes in-memory cache that reset on every
|
|
@@ -1177,7 +1227,6 @@ All runtime-applicable TypeScript ast-grep rules now have JavaScript equivalents
|
|
|
1177
1227
|
- Removed `clients/interviewer-templates.ts` (240 lines)
|
|
1178
1228
|
- Removed initialization from `index.ts`
|
|
1179
1229
|
- **Deleted deprecated commands** — All were superseded by `/lens-booboo`:
|
|
1180
|
-
|
|
1181
1230
|
- `/lens-booboo-fix` command (fix-from-booboo.ts, 430 lines) — showed warning to use `/lens-booboo`
|
|
1182
1231
|
- `/lens-fix-simplified` command (fix-simplified.ts, 770 lines) — never registered, unused
|
|
1183
1232
|
- `/lens-rate` command (rate.ts, 340 lines) — showed warning to use `/lens-booboo`
|
|
@@ -1196,7 +1245,6 @@ All runtime-applicable TypeScript ast-grep rules now have JavaScript equivalents
|
|
|
1196
1245
|
- Broken runner tests (7 files) — thin CLI wrappers with wrong imports
|
|
1197
1246
|
- Trivial utility tests (5 files) — file extension parsing, string sanitization
|
|
1198
1247
|
- **Added meaningful integration tests**:
|
|
1199
|
-
|
|
1200
1248
|
- `tests/clients/dispatch/dispatcher-flow.test.ts` — Runner registration, execution, delta mode, conditional runners
|
|
1201
1249
|
- `tests/extension-hooks.test.ts` — pi API: tool/command/flag registration, event handlers
|
|
1202
1250
|
- `tests/mocks/runner-factory.ts` — Mock runners for testing without real CLI tools
|
|
@@ -1532,7 +1580,6 @@ Migrated 20 critical security rules to NAPI (fast native execution):
|
|
|
1532
1580
|
Three new lint runners with full test coverage:
|
|
1533
1581
|
|
|
1534
1582
|
- **Spellcheck runner** (`clients/dispatch/runners/spellcheck.ts`): Markdown spellchecking
|
|
1535
|
-
|
|
1536
1583
|
- Uses `typos-cli` (Rust-based, fast, low false positives)
|
|
1537
1584
|
- Checks `.md` and `.mdx` files
|
|
1538
1585
|
- Priority 30, runs after code quality checks
|
|
@@ -1540,7 +1587,6 @@ Three new lint runners with full test coverage:
|
|
|
1540
1587
|
- Install: `cargo install typos-cli`
|
|
1541
1588
|
|
|
1542
1589
|
- **Oxlint runner** (`clients/dispatch/runners/oxlint.ts`): Fast JS/TS linting
|
|
1543
|
-
|
|
1544
1590
|
- Uses `oxlint` from Oxc project (Rust-based, ~100x faster than ESLint)
|
|
1545
1591
|
- Zero-config by default
|
|
1546
1592
|
- JSON output with fix suggestions
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ At `session_start`, pi-lens:
|
|
|
41
41
|
- applies language-aware startup defaults for tool preinstall
|
|
42
42
|
- warms caches and optional indexes (with overlap/session guardrails)
|
|
43
43
|
- emits missing-tool install hints for detected languages when relevant
|
|
44
|
-
-
|
|
44
|
+
- prepends session guidance before the user's prompt so provider bridges keep the real prompt active
|
|
45
45
|
- opens `warmFiles` (if configured in `.pi-lens/lsp.json`) to seed lazy-indexing language servers like clangd before the first symbol query
|
|
46
46
|
|
|
47
47
|
For one-shot print sessions (for example `pi --print ...`), pi-lens auto-uses a quick startup path that skips heavy bootstrap work to reduce startup latency. Override with `PI_LENS_STARTUP_MODE=full|minimal|quick`.
|
package/clients/biome-client.ts
CHANGED
|
@@ -263,9 +263,10 @@ export class BiomeClient {
|
|
|
263
263
|
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
264
264
|
|
|
265
265
|
try {
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
266
|
+
// lint --write applies safe lint fixes only — no formatting.
|
|
267
|
+
// Formatting is deferred to agent_end to avoid mid-turn file modifications
|
|
268
|
+
// that trigger read-guard "file modified since read" blocks.
|
|
269
|
+
const result = this.spawnBiome(["lint", "--write", absolutePath]);
|
|
269
270
|
|
|
270
271
|
if (result.error) {
|
|
271
272
|
return {
|
|
@@ -325,7 +326,7 @@ export class BiomeClient {
|
|
|
325
326
|
try {
|
|
326
327
|
const before = await fs.promises.readFile(absolutePath, "utf-8");
|
|
327
328
|
const result = await this.spawnBiomeAsync([
|
|
328
|
-
"
|
|
329
|
+
"lint",
|
|
329
330
|
"--write",
|
|
330
331
|
absolutePath,
|
|
331
332
|
]);
|
package/clients/cache-manager.ts
CHANGED
|
@@ -307,10 +307,13 @@ export class CacheManager {
|
|
|
307
307
|
|
|
308
308
|
/**
|
|
309
309
|
* Get files that need jscpd re-scan (any edit).
|
|
310
|
+
* Only returns source code files jscpd can meaningfully analyse.
|
|
310
311
|
*/
|
|
311
312
|
getFilesForJscpd(cwd: string): string[] {
|
|
312
313
|
const state = this.readTurnState(cwd);
|
|
313
|
-
return Object.keys(state.files)
|
|
314
|
+
return Object.keys(state.files).filter((f) =>
|
|
315
|
+
/\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|rb|java|cs|php|cpp|c|h|hpp|swift|kt)$/.test(f),
|
|
316
|
+
);
|
|
314
317
|
}
|
|
315
318
|
|
|
316
319
|
/**
|
|
@@ -61,7 +61,8 @@ export function scheduleProviders(providers: FactProvider[]): FactProvider[] {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
nextWave.sort((a, b) => a.id.localeCompare(b.id));
|
|
65
|
+
wave = nextWave;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
if (result.length < providers.length) {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { normalizeMapKey } from "../path-utils.js";
|
|
2
2
|
|
|
3
|
-
type FactValue = unknown;
|
|
4
|
-
|
|
5
3
|
export interface ReadonlyFactStore {
|
|
6
4
|
getFileFact<T>(filePath: string, factId: string): T | undefined;
|
|
7
5
|
hasFileFact(filePath: string, factId: string): boolean;
|
|
@@ -10,8 +8,8 @@ export interface ReadonlyFactStore {
|
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
export class FactStore implements ReadonlyFactStore {
|
|
13
|
-
private readonly fileFacts = new Map<string, Map<string,
|
|
14
|
-
private readonly sessionFacts = new Map<string,
|
|
11
|
+
private readonly fileFacts = new Map<string, Map<string, unknown>>();
|
|
12
|
+
private readonly sessionFacts = new Map<string, unknown>();
|
|
15
13
|
|
|
16
14
|
// All file-keyed methods normalize the path internally via normalizeMapKey().
|
|
17
15
|
// Callers always pass raw/resolved paths — normalization is not their concern.
|
|
@@ -20,7 +18,7 @@ export class FactStore implements ReadonlyFactStore {
|
|
|
20
18
|
return this.fileFacts.get(normalizeMapKey(filePath))?.get(factId) as T | undefined;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
setFileFact(filePath: string, factId: string, value:
|
|
21
|
+
setFileFact(filePath: string, factId: string, value: unknown): void {
|
|
24
22
|
const key = normalizeMapKey(filePath);
|
|
25
23
|
let facts = this.fileFacts.get(key);
|
|
26
24
|
if (!facts) {
|
|
@@ -51,7 +49,7 @@ export class FactStore implements ReadonlyFactStore {
|
|
|
51
49
|
return this.sessionFacts.get(factId) as T | undefined;
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
setSessionFact(factId: string, value:
|
|
52
|
+
setSessionFact(factId: string, value: unknown): void {
|
|
55
53
|
this.sessionFacts.set(factId, value);
|
|
56
54
|
}
|
|
57
55
|
|
|
@@ -358,6 +358,8 @@ export function resetDispatchBaselines(): void {
|
|
|
358
358
|
primaryFilesThisTurn.clear();
|
|
359
359
|
cascadeDiagnosticBaselines.clear();
|
|
360
360
|
cascadeSessionStats = { runs: 0, diagnosticsSurfaced: 0, coldSnapshotTouches: 0 };
|
|
361
|
+
for (const timer of astGrepWarnDebounceTimers.values()) clearTimeout(timer);
|
|
362
|
+
astGrepWarnDebounceTimers.clear();
|
|
361
363
|
}
|
|
362
364
|
|
|
363
365
|
let cascadeSessionStats = { runs: 0, diagnosticsSurfaced: 0, coldSnapshotTouches: 0 };
|
|
@@ -479,7 +481,7 @@ export async function computeCascadeForFile(
|
|
|
479
481
|
.filter((n) => !primaryFilesThisTurn.has(normalizeMapKey(n)))
|
|
480
482
|
.sort((a, b) => {
|
|
481
483
|
const rank = (p: string) =>
|
|
482
|
-
importerSet.has(p) ? 0 : callerSet.has(p) ? 1 : 2;
|
|
484
|
+
importerSet.has(p) ? 0 : (callerSet.has(p) ? 1 : 2);
|
|
483
485
|
return rank(a) - rank(b);
|
|
484
486
|
})
|
|
485
487
|
.slice(0, MAX_FILES);
|
|
@@ -584,10 +586,7 @@ export async function computeCascadeForFile(
|
|
|
584
586
|
// write sequence. A new write (higher writeSeq) invalidates the cache entry.
|
|
585
587
|
const cached =
|
|
586
588
|
writeSeq != null ? neighborTouchCache.get(cacheKey) : undefined;
|
|
587
|
-
if (
|
|
588
|
-
cached != null &&
|
|
589
|
-
cached.turnSeq === turnSeq
|
|
590
|
-
) {
|
|
589
|
+
if (cached?.turnSeq === turnSeq) {
|
|
591
590
|
producedLspData = true;
|
|
592
591
|
const durationMs = Date.now() - neighborStart;
|
|
593
592
|
logCascade({
|
|
@@ -149,21 +149,16 @@ export const noBooleanParamsRule: FactRule = {
|
|
|
149
149
|
ts.isIdentifier(param.name) ? param.name.text : "";
|
|
150
150
|
if (BOOLEAN_PREFIX_OK.test(name)) continue;
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
t.literal.kind === ts.SyntaxKind.FalseKeyword)),
|
|
163
|
-
)
|
|
164
|
-
) {
|
|
165
|
-
isBoolean = true;
|
|
166
|
-
}
|
|
152
|
+
const isBoolean =
|
|
153
|
+
param.type.kind === ts.SyntaxKind.BooleanKeyword ||
|
|
154
|
+
(ts.isUnionTypeNode(param.type) &&
|
|
155
|
+
param.type.types.every(
|
|
156
|
+
(t) =>
|
|
157
|
+
t.kind === ts.SyntaxKind.BooleanKeyword ||
|
|
158
|
+
(ts.isLiteralTypeNode(t) &&
|
|
159
|
+
(t.literal.kind === ts.SyntaxKind.TrueKeyword ||
|
|
160
|
+
t.literal.kind === ts.SyntaxKind.FalseKeyword)),
|
|
161
|
+
));
|
|
167
162
|
|
|
168
163
|
if (!isBoolean) continue;
|
|
169
164
|
const { line, character } = sf.getLineAndCharacterOfPosition(param.getStart(sf));
|
|
@@ -134,7 +134,7 @@ const biomeCheckJsonRunner: RunnerDefinition = {
|
|
|
134
134
|
diagnostics: [
|
|
135
135
|
{
|
|
136
136
|
id: "biome:parse-error:1",
|
|
137
|
-
message:
|
|
137
|
+
message: "Biome JSON parse failed: " + parsed.parseError + (preview ? " (output preview: " + preview + ")" : ""),
|
|
138
138
|
filePath: ctx.filePath,
|
|
139
139
|
line: 1,
|
|
140
140
|
column: 1,
|
|
@@ -125,7 +125,7 @@ const eslintRunner: RunnerDefinition = {
|
|
|
125
125
|
diagnostics: [
|
|
126
126
|
{
|
|
127
127
|
id: "eslint:parse-error:1",
|
|
128
|
-
message:
|
|
128
|
+
message: "ESLint JSON parse failed: " + parsed.parseError + (preview ? " (output preview: " + preview + ")" : ""),
|
|
129
129
|
filePath: ctx.filePath,
|
|
130
130
|
line: 1,
|
|
131
131
|
column: 1,
|
|
@@ -212,11 +212,7 @@ const lspRunner: RunnerDefinition = {
|
|
|
212
212
|
);
|
|
213
213
|
|
|
214
214
|
const hasErrors = diagnostics.some((d) => d.semantic === "blocking");
|
|
215
|
-
const resultSemantic = hasErrors
|
|
216
|
-
? "blocking"
|
|
217
|
-
: diagnostics.length > 0
|
|
218
|
-
? "warning"
|
|
219
|
-
: "none";
|
|
215
|
+
const resultSemantic = hasErrors ? "blocking" : (diagnostics.length > 0 ? "warning" : "none");
|
|
220
216
|
|
|
221
217
|
return {
|
|
222
218
|
status: hasErrors ? "failed" : "succeeded",
|
|
@@ -86,7 +86,7 @@ function parsePSAnalyzerOutput(raw: string, filePath: string): Diagnostic[] {
|
|
|
86
86
|
.map((item) => {
|
|
87
87
|
const sev = (item.Severity ?? "Warning").toLowerCase();
|
|
88
88
|
const severity: "error" | "warning" | "info" =
|
|
89
|
-
sev === "error" || sev === "parseerror" ? "error" : sev === "information" ? "info" : "warning";
|
|
89
|
+
(sev === "error" || sev === "parseerror") ? "error" : (sev === "information" ? "info" : "warning");
|
|
90
90
|
const rule = item.RuleName ?? "PSScriptAnalyzer";
|
|
91
91
|
return {
|
|
92
92
|
id: `psscriptanalyzer-${rule}-${item.Line}`,
|
|
@@ -394,10 +394,7 @@ function getArrowSignature(
|
|
|
394
394
|
| import("typescript").ArrowFunction
|
|
395
395
|
| import("typescript").FunctionExpression,
|
|
396
396
|
): string {
|
|
397
|
-
|
|
398
|
-
.map((p) => (tsModule.isIdentifier(p.name) ? p.name.text : "param"))
|
|
399
|
-
.join(", ");
|
|
400
|
-
return `(${params})`;
|
|
397
|
+
return getSignature(tsModule, node as unknown as import("typescript").FunctionDeclaration);
|
|
401
398
|
}
|
|
402
399
|
|
|
403
400
|
// ============================================================================
|
|
@@ -16,16 +16,10 @@ export function convertLspDiagnostics(
|
|
|
16
16
|
return diags
|
|
17
17
|
.filter((d) => d.range?.start?.line !== undefined)
|
|
18
18
|
.map((d, idx) => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
? "error"
|
|
22
|
-
: d.severity === 2
|
|
23
|
-
? "warning"
|
|
24
|
-
: d.severity === 4
|
|
25
|
-
? "hint"
|
|
26
|
-
: "info";
|
|
19
|
+
const severityMap: Record<number, "error" | "warning" | "hint"> = { 1: "error", 2: "warning", 4: "hint" };
|
|
20
|
+
const severity: "error" | "warning" | "info" | "hint" = severityMap[d.severity] ?? "info";
|
|
27
21
|
const semantic =
|
|
28
|
-
d.severity === 1 ? "blocking" : d.severity === 2 ? "warning" : "none";
|
|
22
|
+
d.severity === 1 ? "blocking" : (d.severity === 2 ? "warning" : "none");
|
|
29
23
|
const code = String(d.code ?? "unknown");
|
|
30
24
|
const source = options.source ?? d.source ?? tool;
|
|
31
25
|
const hasSuggestion = options.fixSuggestionByIndex?.has(idx) ?? false;
|
package/clients/formatters.ts
CHANGED
|
@@ -54,7 +54,7 @@ async function tryLazyInstallFormatterTool(
|
|
|
54
54
|
const ok = !res.error && res.status === 0;
|
|
55
55
|
if (!ok) {
|
|
56
56
|
console.error(
|
|
57
|
-
`[format] lazy-install rubocop failed: ${res.error?.message ?? res.stderr ??
|
|
57
|
+
`[format] lazy-install rubocop failed: ${res.error?.message ?? res.stderr ?? "exit " + res.status}`,
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
return ok;
|
|
@@ -67,7 +67,7 @@ async function tryLazyInstallFormatterTool(
|
|
|
67
67
|
const ok = !res.error && res.status === 0;
|
|
68
68
|
if (!ok) {
|
|
69
69
|
console.error(
|
|
70
|
-
`[format] lazy-install rustfmt failed: ${res.error?.message ?? res.stderr ??
|
|
70
|
+
`[format] lazy-install rustfmt failed: ${res.error?.message ?? res.stderr ?? "exit " + res.status}`,
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
return ok;
|
|
@@ -919,11 +919,16 @@ export async function getFormattersForFile(
|
|
|
919
919
|
|
|
920
920
|
const enabled = selected ? [selected] : [];
|
|
921
921
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
922
|
+
let selectionReason: string;
|
|
923
|
+
if (!selected) {
|
|
924
|
+
selectionReason = "none";
|
|
925
|
+
} else if (!formatterPolicy) {
|
|
926
|
+
selectionReason = "detect";
|
|
927
|
+
} else {
|
|
928
|
+
selectionReason = candidateFormatters.some((f) => hasExplicitFormatterConfig(f.name, cwd))
|
|
929
|
+
? "explicit-config"
|
|
930
|
+
: "smart-default";
|
|
931
|
+
}
|
|
927
932
|
logLatency({
|
|
928
933
|
type: "phase",
|
|
929
934
|
phase: "formatter_selected",
|
package/clients/lsp/client.ts
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* - Request/response handling
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
12
11
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
13
12
|
import { EventEmitter } from "node:events";
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
15
|
import type { MessageConnection } from "vscode-jsonrpc";
|
|
16
16
|
import {
|
|
@@ -252,6 +252,10 @@ const PULL_DIAGNOSTICS_RETRY_INTERVAL_MS = positiveIntFromEnv(
|
|
|
252
252
|
"PI_LENS_LSP_PULL_RETRY_INTERVAL_MS",
|
|
253
253
|
250,
|
|
254
254
|
);
|
|
255
|
+
const SHUTDOWN_REQUEST_TIMEOUT_MS = positiveIntFromEnv(
|
|
256
|
+
"PI_LENS_LSP_SHUTDOWN_TIMEOUT_MS",
|
|
257
|
+
1000,
|
|
258
|
+
);
|
|
255
259
|
|
|
256
260
|
const LSP_CRASH_CODES = new Set([
|
|
257
261
|
"ERR_STREAM_DESTROYED",
|
|
@@ -340,6 +344,33 @@ function disposeClientConnection(state: LSPClientState): void {
|
|
|
340
344
|
}
|
|
341
345
|
}
|
|
342
346
|
|
|
347
|
+
async function killProcessTree(
|
|
348
|
+
proc: { kill(signal?: NodeJS.Signals | number): boolean },
|
|
349
|
+
pid: number,
|
|
350
|
+
): Promise<void> {
|
|
351
|
+
if (process.platform === "win32" && pid > 0) {
|
|
352
|
+
await new Promise<void>((resolve) => {
|
|
353
|
+
try {
|
|
354
|
+
// Absolute path avoids PATH-resolution: SystemRoot is set by Windows itself.
|
|
355
|
+
const taskkill = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\taskkill.exe`;
|
|
356
|
+
const killer = nodeSpawn(taskkill, ["/F", "/T", "/PID", String(pid)], {
|
|
357
|
+
shell: false,
|
|
358
|
+
windowsHide: true,
|
|
359
|
+
});
|
|
360
|
+
killer.once("close", () => resolve());
|
|
361
|
+
killer.once("error", () => resolve());
|
|
362
|
+
} catch {
|
|
363
|
+
resolve();
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
proc.kill("SIGTERM");
|
|
371
|
+
} catch {}
|
|
372
|
+
}
|
|
373
|
+
|
|
343
374
|
function mergeDiagnosticLists(
|
|
344
375
|
push: LSPDiagnostic[] | undefined,
|
|
345
376
|
pull: LSPDiagnostic[] | undefined,
|
|
@@ -483,9 +514,7 @@ function setupIncomingHandlers(
|
|
|
483
514
|
);
|
|
484
515
|
state.connection.onRequest(
|
|
485
516
|
"client/unregisterCapability",
|
|
486
|
-
async (params: {
|
|
487
|
-
unregisterations?: Array<{ id: string }>;
|
|
488
|
-
}) => {
|
|
517
|
+
async (params: { unregisterations?: Array<{ id: string }> }) => {
|
|
489
518
|
for (const unreg of params?.unregisterations ?? []) {
|
|
490
519
|
if (unreg.id) {
|
|
491
520
|
state.dynamicRegistrations.delete(unreg.id);
|
|
@@ -715,9 +744,12 @@ async function clientShutdown(state: LSPClientState): Promise<void> {
|
|
|
715
744
|
state.openDocuments.clear();
|
|
716
745
|
state.diagnosticEmitter.removeAllListeners();
|
|
717
746
|
try {
|
|
718
|
-
await
|
|
747
|
+
await withTimeout(
|
|
748
|
+
safeSendRequest(state.connection, "shutdown", {}),
|
|
749
|
+
SHUTDOWN_REQUEST_TIMEOUT_MS,
|
|
750
|
+
);
|
|
719
751
|
} catch {
|
|
720
|
-
/* ignore */
|
|
752
|
+
/* ignore — proceed to exit/kill so shutdown cannot hang the session */
|
|
721
753
|
}
|
|
722
754
|
try {
|
|
723
755
|
await safeSendNotification(state.connection, "exit", {});
|
|
@@ -725,7 +757,10 @@ async function clientShutdown(state: LSPClientState): Promise<void> {
|
|
|
725
757
|
/* ignore */
|
|
726
758
|
}
|
|
727
759
|
disposeClientConnection(state);
|
|
728
|
-
state.lspProcess.
|
|
760
|
+
const pid = state.lspProcess.pid;
|
|
761
|
+
// On Windows, killing the direct child first can orphan grandchildren before
|
|
762
|
+
// taskkill can traverse the tree. Kill the full tree first and wait briefly.
|
|
763
|
+
await killProcessTree(state.lspProcess.process, pid);
|
|
729
764
|
}
|
|
730
765
|
|
|
731
766
|
async function navRequest<T>(
|
|
@@ -902,17 +937,11 @@ export async function createLSPClient(options: {
|
|
|
902
937
|
// Hard-kill the hung process so it doesn't become a zombie.
|
|
903
938
|
// SIGTERM alone is unreliable on Windows for cmd.exe/PowerShell trees.
|
|
904
939
|
const pid = lspProcess.pid;
|
|
905
|
-
lspProcess.process
|
|
906
|
-
if (process.platform === "win32" && pid > 0) {
|
|
907
|
-
try {
|
|
908
|
-
nodeSpawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
909
|
-
shell: false,
|
|
910
|
-
windowsHide: true,
|
|
911
|
-
});
|
|
912
|
-
} catch {}
|
|
913
|
-
}
|
|
940
|
+
void killProcessTree(lspProcess.process, pid);
|
|
914
941
|
setTimeout(() => {
|
|
915
|
-
if (!lspProcess.process.killed
|
|
942
|
+
if (!lspProcess.process.killed && process.platform !== "win32") {
|
|
943
|
+
lspProcess.process.kill("SIGKILL");
|
|
944
|
+
}
|
|
916
945
|
}, 2000);
|
|
917
946
|
throw err;
|
|
918
947
|
} finally {
|
|
@@ -940,7 +969,8 @@ export async function createLSPClient(options: {
|
|
|
940
969
|
);
|
|
941
970
|
}
|
|
942
971
|
|
|
943
|
-
state.workspaceDiagnosticsSupport =
|
|
972
|
+
state.workspaceDiagnosticsSupport =
|
|
973
|
+
detectWorkspaceDiagnosticsSupport(initResult);
|
|
944
974
|
state.operationSupport = detectOperationSupport(initResult);
|
|
945
975
|
state.staticDiagnosticsMode = state.workspaceDiagnosticsSupport.mode;
|
|
946
976
|
|
|
@@ -1247,19 +1277,24 @@ async function withTimeout<T>(
|
|
|
1247
1277
|
promise: Promise<T>,
|
|
1248
1278
|
timeoutMs: number,
|
|
1249
1279
|
): Promise<T> {
|
|
1280
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
1250
1281
|
// Suppress unhandled rejection if `promise` rejects AFTER the timeout
|
|
1251
1282
|
// wins the race — Promise.race settles on the first result but the
|
|
1252
1283
|
// losing promises still run, and any later rejection would be uncaught.
|
|
1253
1284
|
promise.catch(() => {});
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1285
|
+
try {
|
|
1286
|
+
return await Promise.race([
|
|
1287
|
+
promise,
|
|
1288
|
+
new Promise<T>((_, reject) => {
|
|
1289
|
+
timeout = setTimeout(
|
|
1290
|
+
() => reject(new Error(`Timeout after ${timeoutMs}ms`)),
|
|
1291
|
+
timeoutMs,
|
|
1292
|
+
);
|
|
1293
|
+
}),
|
|
1294
|
+
]);
|
|
1295
|
+
} finally {
|
|
1296
|
+
if (timeout) clearTimeout(timeout);
|
|
1297
|
+
}
|
|
1263
1298
|
}
|
|
1264
1299
|
|
|
1265
1300
|
function positiveIntFromEnv(name: string, fallback: number): number {
|
package/clients/lsp/index.ts
CHANGED
|
@@ -135,13 +135,13 @@ export interface LSPTouchFileOptions {
|
|
|
135
135
|
|
|
136
136
|
export class LSPService {
|
|
137
137
|
private state: LSPState;
|
|
138
|
-
private workspaceProbeLogged = new Set<string>();
|
|
139
|
-
private warmStartLogged = new Set<string>();
|
|
140
|
-
private optionalFailureLogged = new Set<string>();
|
|
141
|
-
private optionalDisabled = new Set<string>();
|
|
138
|
+
private readonly workspaceProbeLogged = new Set<string>();
|
|
139
|
+
private readonly warmStartLogged = new Set<string>();
|
|
140
|
+
private readonly optionalFailureLogged = new Set<string>();
|
|
141
|
+
private readonly optionalDisabled = new Set<string>();
|
|
142
142
|
/** Consecutive failure counts for exponential backoff circuit breaker */
|
|
143
|
-
private failureCounts = new Map<string, number>();
|
|
144
|
-
private recentTouches = new Map<
|
|
143
|
+
private readonly failureCounts = new Map<string, number>();
|
|
144
|
+
private readonly recentTouches = new Map<
|
|
145
145
|
string,
|
|
146
146
|
{ fingerprint: string; touchedAt: number; clientScope: "primary" | "all" }
|
|
147
147
|
>();
|
|
@@ -197,11 +197,22 @@ export class LSPService {
|
|
|
197
197
|
clientScope: "primary" | "all",
|
|
198
198
|
): void {
|
|
199
199
|
const key = `${normalizeMapKey(filePath)}:${clientScope}`;
|
|
200
|
+
const now = Date.now();
|
|
200
201
|
this.recentTouches.set(key, {
|
|
201
202
|
fingerprint: this.fingerprintContent(content),
|
|
202
|
-
touchedAt:
|
|
203
|
+
touchedAt: now,
|
|
203
204
|
clientScope,
|
|
204
205
|
});
|
|
206
|
+
// Trim entries that are already past the debounce window — shouldSkipTouch
|
|
207
|
+
// ignores them anyway, so they serve no purpose. Only sweep when the map
|
|
208
|
+
// exceeds the threshold to avoid iterating on every call.
|
|
209
|
+
if (this.recentTouches.size > 200) {
|
|
210
|
+
for (const [k, v] of this.recentTouches) {
|
|
211
|
+
if (now - v.touchedAt > TOUCH_DEBOUNCE_MS) {
|
|
212
|
+
this.recentTouches.delete(k);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
205
216
|
}
|
|
206
217
|
|
|
207
218
|
/**
|
|
@@ -309,7 +320,7 @@ export class LSPService {
|
|
|
309
320
|
if (!root) continue;
|
|
310
321
|
const key = `${server.id}:${normalizeMapKey(root)}`;
|
|
311
322
|
const existing = this.state.clients.get(key);
|
|
312
|
-
if (existing
|
|
323
|
+
if (existing?.isAlive()) {
|
|
313
324
|
return { client: existing, info: server };
|
|
314
325
|
}
|
|
315
326
|
}
|
|
@@ -392,9 +403,7 @@ export class LSPService {
|
|
|
392
403
|
}
|
|
393
404
|
}
|
|
394
405
|
|
|
395
|
-
private shouldAllowInstall(
|
|
396
|
-
void filePath;
|
|
397
|
-
void root;
|
|
406
|
+
private shouldAllowInstall(_filePath: string, _root: string): boolean {
|
|
398
407
|
return process.env.PI_LENS_DISABLE_LSP_INSTALL !== "1";
|
|
399
408
|
}
|
|
400
409
|
|