pi-lens 3.8.65 → 3.8.67
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 +59 -0
- package/README.md +0 -1
- package/dist/clients/actionable-warnings-logger.js +11 -36
- package/dist/clients/ast-grep-tool-logger.js +11 -36
- package/dist/clients/biome-client.js +10 -2
- package/dist/clients/bootstrap.js +1 -3
- package/dist/clients/cascade-logger.js +7 -12
- package/dist/clients/complexity-client.js +397 -623
- package/dist/clients/dead-code-logger.js +11 -22
- package/dist/clients/dependency-checker.js +15 -2
- package/dist/clients/deps/vscode-jsonrpc.js +1 -1
- package/dist/clients/diagnostic-logger.js +7 -31
- package/dist/clients/dispatch/auxiliary-lsp.js +38 -0
- package/dist/clients/dispatch/dispatcher.js +4 -39
- package/dist/clients/dispatch/fact-runner.js +9 -57
- package/dist/clients/dispatch/facts/comment-facts.js +12 -25
- package/dist/clients/dispatch/facts/function-facts.js +161 -132
- package/dist/clients/dispatch/facts/import-facts.js +164 -117
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +34 -0
- package/dist/clients/dispatch/facts/try-catch-facts.js +62 -68
- package/dist/clients/dispatch/inline-suppressions.js +61 -0
- package/dist/clients/dispatch/integration.js +94 -32
- package/dist/clients/dispatch/plan.js +0 -49
- package/dist/clients/dispatch/priorities.js +0 -1
- package/dist/clients/dispatch/rules/cors-wildcard.js +54 -0
- package/dist/clients/dispatch/rules/high-import-coupling.js +37 -0
- package/dist/clients/dispatch/rules/no-commented-credentials.js +52 -0
- package/dist/clients/dispatch/runners/index.js +2 -7
- package/dist/clients/dispatch/runners/lsp.js +18 -6
- package/dist/clients/dispatch/runners/tree-sitter.js +11 -64
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +28 -33
- package/dist/clients/dispatch/tool-profile.js +0 -1
- package/dist/clients/dispatch/types.js +1 -1
- package/dist/clients/formatters.js +10 -0
- package/dist/clients/git-guard.js +1 -1
- package/dist/clients/grammar-source.js +65 -2
- package/dist/clients/jscpd-client.js +8 -2
- package/dist/clients/language-policy.js +2 -2
- package/dist/clients/latency-logger.js +10 -15
- package/dist/clients/lsp/client.js +74 -11
- package/dist/clients/lsp/config.js +61 -3
- package/dist/clients/lsp/index.js +43 -3
- package/dist/clients/lsp/launch.js +3 -32
- package/dist/clients/mcp/session.js +0 -1
- package/dist/clients/module-report.js +16 -5
- package/dist/clients/ndjson-logger.js +150 -0
- package/dist/clients/package-manager.js +65 -0
- package/dist/clients/pipeline.js +25 -41
- package/dist/clients/project-diagnostics/extractors.js +85 -0
- package/dist/clients/project-diagnostics/runner-adapters/dead-code.js +44 -0
- package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +25 -0
- package/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +34 -0
- package/dist/clients/project-diagnostics/runner-adapters/jscpd.js +38 -0
- package/dist/clients/project-diagnostics/runner-adapters/madge.js +47 -0
- package/dist/clients/project-diagnostics/runner-adapters/trivy.js +31 -0
- package/dist/clients/project-diagnostics/scanner.js +11 -10
- package/dist/clients/read-guard-logger.js +11 -36
- package/dist/clients/review-graph/builder.js +337 -27
- package/dist/clients/review-graph/git-identity.js +150 -0
- package/dist/clients/review-graph/service.js +3 -3
- package/dist/clients/runtime-coordinator.js +83 -0
- package/dist/clients/runtime-session.js +28 -2
- package/dist/clients/runtime-tool-result.js +10 -2
- package/dist/clients/runtime-turn.js +25 -7
- package/dist/clients/sg-runner.js +15 -0
- package/dist/clients/test-runner-client.js +12 -6
- package/dist/clients/tree-sitter-cache.js +47 -14
- package/dist/clients/tree-sitter-client.js +9 -1
- package/dist/clients/tree-sitter-logger.js +7 -12
- package/dist/clients/tree-sitter-query-loader.js +1 -0
- package/dist/clients/tree-sitter-shared.js +113 -0
- package/dist/clients/tree-sitter-symbol-extractor.js +19 -12
- package/dist/index.js +4 -22
- package/dist/tools/lens-diagnostics.js +254 -20
- package/grammars/tree-sitter-yaml.wasm +0 -0
- package/grammars/tree-sitter-yaml.wasm.json +3 -3
- package/package.json +4 -3
- package/rules/ast-grep-rules/rule-tests/no-init-return-test.yml +15 -0
- package/rules/ast-grep-rules/rules/no-init-return.yml +11 -5
- package/rules/tree-sitter-queries/python/python-assert-production.yml +4 -0
- package/scripts/download-grammars.js +29 -4
- package/scripts/grammars.lock.json +14 -2
- package/scripts/install-selftest.mjs +8 -7
- package/dist/clients/deps/typescript.js +0 -15
- package/dist/clients/dispatch/rules/quality-rules.js +0 -297
- package/dist/clients/dispatch/rules/sonar-rules.js +0 -493
- package/dist/clients/dispatch/runners/biome.js +0 -69
- package/dist/clients/dispatch/runners/python-slop.js +0 -106
- package/dist/clients/dispatch/runners/ts-lsp.js +0 -109
- package/dist/clients/runner-tracker.js +0 -153
- package/dist/clients/ts-service.js +0 -130
- package/dist/clients/type-coverage-client.js +0 -128
- package/dist/clients/typescript-client.js +0 -509
- package/dist/commands/booboo.js +0 -1412
- package/rules/python-slop-rules/.sgconfig.yml +0 -4
- package/rules/python-slop-rules/rules/slop-rules.yml +0 -647
|
@@ -5,26 +5,20 @@
|
|
|
5
5
|
* input to phasing decisions in the issue). Mirrors `ast-grep-tool-logger.ts`
|
|
6
6
|
* for shape + size-based rotation.
|
|
7
7
|
*/
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
8
|
import * as path from "node:path";
|
|
10
9
|
import { isTestMode } from "./env-utils.js";
|
|
11
10
|
import { getGlobalPiLensDir } from "./file-utils.js";
|
|
11
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
12
12
|
const LOG_DIR = getGlobalPiLensDir();
|
|
13
13
|
const LOG_FILE = path.join(LOG_DIR, "dead-code.log");
|
|
14
14
|
const LOG_BACKUP_FILE = path.join(LOG_DIR, "dead-code.log.1");
|
|
15
15
|
const MAX_LOG_BYTES = Math.max(128 * 1024, Number.parseInt(process.env.PI_LENS_DEAD_CODE_LOG_MAX_BYTES ?? "1048576", 10) ||
|
|
16
16
|
1048576);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
// no file yet, or rename raced — nothing to rotate
|
|
26
|
-
}
|
|
27
|
-
}
|
|
17
|
+
const writer = createNdjsonLogger({
|
|
18
|
+
filePath: LOG_FILE,
|
|
19
|
+
maxBytes: MAX_LOG_BYTES,
|
|
20
|
+
backupPath: LOG_BACKUP_FILE,
|
|
21
|
+
});
|
|
28
22
|
/**
|
|
29
23
|
* Append one scan event. Fire-and-forget: telemetry must never break a scan, so
|
|
30
24
|
* every fs error is swallowed. Skipped under test mode to keep the suite from
|
|
@@ -33,14 +27,9 @@ function rotateIfNeeded() {
|
|
|
33
27
|
export function logDeadCodeScan(event) {
|
|
34
28
|
if (isTestMode())
|
|
35
29
|
return;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
fs.appendFileSync(LOG_FILE, row + "\n");
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// telemetry is best-effort
|
|
45
|
-
}
|
|
30
|
+
writer.log({ ts: new Date().toISOString(), ...event });
|
|
31
|
+
}
|
|
32
|
+
/** Resolve once all enqueued dead-code writes are on disk (tests/shutdown). */
|
|
33
|
+
export function flushDeadCodeLog() {
|
|
34
|
+
return writer.flush();
|
|
46
35
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
12
|
import * as path from "node:path";
|
|
13
|
+
import { findNodeToolBinary } from "./package-manager.js";
|
|
13
14
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
14
15
|
/**
|
|
15
16
|
* Build madge's argv for a circular-dependency scan.
|
|
@@ -77,6 +78,16 @@ export class DependencyChecker {
|
|
|
77
78
|
? (msg) => console.error(`[deps] ${msg}`)
|
|
78
79
|
: () => { };
|
|
79
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolve how to invoke madge for `cwd`: a local/global-installed binary
|
|
83
|
+
* (npm/pnpm/yarn/bun) if found, else `npx madge` (#375). `prefix` is prepended
|
|
84
|
+
* to the madge args (empty for a resolved binary, `["madge"]` for the npx
|
|
85
|
+
* fallback).
|
|
86
|
+
*/
|
|
87
|
+
async resolveMadge(cwd) {
|
|
88
|
+
const bin = await findNodeToolBinary("madge", cwd);
|
|
89
|
+
return bin ? { cmd: bin, prefix: [] } : { cmd: "npx", prefix: ["madge"] };
|
|
90
|
+
}
|
|
80
91
|
/**
|
|
81
92
|
* Check if madge is available, auto-install if not
|
|
82
93
|
*/
|
|
@@ -250,7 +261,8 @@ export class DependencyChecker {
|
|
|
250
261
|
this.log(`Imports changed for ${path.basename(normalized)}, checking dependencies...`);
|
|
251
262
|
// Run madge on the specific file (fast)
|
|
252
263
|
try {
|
|
253
|
-
const
|
|
264
|
+
const { cmd, prefix } = await this.resolveMadge(projectRoot);
|
|
265
|
+
const result = await safeSpawnAsync(cmd, [...prefix, ...buildMadgeArgs(normalized, projectRoot)], {
|
|
254
266
|
timeout: 15000,
|
|
255
267
|
cwd: projectRoot,
|
|
256
268
|
});
|
|
@@ -344,7 +356,8 @@ export class DependencyChecker {
|
|
|
344
356
|
}
|
|
345
357
|
async runScanProject(projectRoot) {
|
|
346
358
|
try {
|
|
347
|
-
const
|
|
359
|
+
const { cmd, prefix } = await this.resolveMadge(projectRoot);
|
|
360
|
+
const result = await safeSpawnAsync(cmd, [...prefix, ...buildMadgeArgs(projectRoot, projectRoot)], {
|
|
348
361
|
timeout: 30000,
|
|
349
362
|
cwd: projectRoot,
|
|
350
363
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createMessageConnection, StreamMessageReader, StreamMessageWriter, } from "vscode-jsonrpc/node";
|
|
1
|
+
export { CancellationTokenSource, createMessageConnection, StreamMessageReader, StreamMessageWriter, } from "vscode-jsonrpc/node";
|
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Log file: ~/.pi-lens/logs/{date}.jsonl
|
|
5
5
|
*/
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
6
|
import * as os from "node:os";
|
|
8
7
|
import * as path from "node:path";
|
|
9
8
|
import { isTestMode } from "./env-utils.js";
|
|
9
|
+
import { createNdjsonLogger } from "./ndjson-logger.js";
|
|
10
10
|
function getLogDir() {
|
|
11
|
-
|
|
12
|
-
const logDir = path.join(home, ".pi-lens", "logs");
|
|
13
|
-
if (!fs.existsSync(logDir)) {
|
|
14
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
return logDir;
|
|
11
|
+
return path.join(os.homedir(), ".pi-lens", "logs");
|
|
17
12
|
}
|
|
18
13
|
function getLogFile() {
|
|
19
14
|
const date = new Date().toISOString().split("T")[0];
|
|
@@ -28,30 +23,15 @@ export function getDiagnosticLogger() {
|
|
|
28
23
|
return _logger;
|
|
29
24
|
}
|
|
30
25
|
export function createDiagnosticLogger() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
if (writing || pending.length === 0)
|
|
35
|
-
return;
|
|
36
|
-
writing = true;
|
|
37
|
-
const toWrite = pending.splice(0, pending.length);
|
|
38
|
-
const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
39
|
-
try {
|
|
40
|
-
await fs.promises.appendFile(getLogFile(), lines);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
// pi-lens-ignore: missing-error-propagation — fire-and-forget log write, must not throw
|
|
44
|
-
console.error("Failed to write diagnostic log:", err);
|
|
45
|
-
}
|
|
46
|
-
writing = false;
|
|
47
|
-
};
|
|
26
|
+
// Lazy filePath: the log file is keyed on the current date, resolved per
|
|
27
|
+
// drain so a long-lived logger rolls over at midnight.
|
|
28
|
+
const writer = createNdjsonLogger({ filePath: () => getLogFile() });
|
|
48
29
|
return {
|
|
49
30
|
log(entry) {
|
|
50
31
|
if (isTestMode()) {
|
|
51
32
|
return;
|
|
52
33
|
}
|
|
53
|
-
|
|
54
|
-
writePending(); // async, non-blocking
|
|
34
|
+
writer.log(entry); // async, non-blocking
|
|
55
35
|
},
|
|
56
36
|
logCaught(d, context, shownInline = false) {
|
|
57
37
|
this.log({
|
|
@@ -77,11 +57,7 @@ export function createDiagnosticLogger() {
|
|
|
77
57
|
});
|
|
78
58
|
},
|
|
79
59
|
async flush() {
|
|
80
|
-
|
|
81
|
-
await writePending();
|
|
82
|
-
while (writing) {
|
|
83
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
84
|
-
}
|
|
60
|
+
await writer.flush();
|
|
85
61
|
},
|
|
86
62
|
};
|
|
87
63
|
}
|
|
@@ -20,6 +20,42 @@ import { findLocalOpengrepConfig } from "../opengrep-config.js";
|
|
|
20
20
|
import { findLocalTyposConfig } from "../typos-config.js";
|
|
21
21
|
import { findLocalZizmorConfig } from "../zizmor-config.js";
|
|
22
22
|
import { classifyDefect } from "./diagnostic-taxonomy.js";
|
|
23
|
+
/**
|
|
24
|
+
* Semgrep/opengrep `# nosemgrep` / `# nosemgrep: <rule-id>[,<rule-id>]` inline
|
|
25
|
+
* suppression (#441). A bare `# nosemgrep` drops every finding on its line; the
|
|
26
|
+
* `: <ids>` form drops only the listed rule ids. `d.code` is the semgrep rule id.
|
|
27
|
+
* Also accepts the `//` comment form.
|
|
28
|
+
*
|
|
29
|
+
* Matches Semgrep placement: honored on the finding's OWN line (inline or not),
|
|
30
|
+
* and on the line ABOVE only when that line is a STANDALONE comment (no code before
|
|
31
|
+
* it) — so `a() # nosemgrep` suppresses a finding on `a()` but not the next line.
|
|
32
|
+
*/
|
|
33
|
+
const NOSEMGREP_RE = /(?:#|\/\/)\s*nosemgrep(?::\s*(.+))?/i;
|
|
34
|
+
const NOSEMGREP_STANDALONE_RE = /^\s*(?:#|\/\/)\s*nosemgrep(?::\s*(.+))?\s*$/i;
|
|
35
|
+
export function isNosemgrepSuppressed(d, content) {
|
|
36
|
+
const startLine = d.range?.start?.line; // 0-based
|
|
37
|
+
if (startLine == null)
|
|
38
|
+
return false;
|
|
39
|
+
const lines = content.split("\n");
|
|
40
|
+
const ruleId = String(d.code ?? "");
|
|
41
|
+
const checkLine = (text, standaloneOnly) => {
|
|
42
|
+
if (!text)
|
|
43
|
+
return false;
|
|
44
|
+
const m = (standaloneOnly ? NOSEMGREP_STANDALONE_RE : NOSEMGREP_RE).exec(text);
|
|
45
|
+
if (!m)
|
|
46
|
+
return false;
|
|
47
|
+
if (m[1] === undefined)
|
|
48
|
+
return true; // bare nosemgrep → suppress the line
|
|
49
|
+
return m[1]
|
|
50
|
+
.split(",")
|
|
51
|
+
.map((s) => s.trim())
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.includes(ruleId);
|
|
54
|
+
};
|
|
55
|
+
// The finding's own line (inline OK), then the line above (standalone comment only).
|
|
56
|
+
return (checkLine(lines[startLine], false) ||
|
|
57
|
+
checkLine(lines[startLine - 1], true));
|
|
58
|
+
}
|
|
23
59
|
export const AUXILIARY_LSP_PROFILES = [
|
|
24
60
|
{
|
|
25
61
|
serverId: "opengrep",
|
|
@@ -37,6 +73,8 @@ export const AUXILIARY_LSP_PROFILES = [
|
|
|
37
73
|
allowBlocking: (cwd) => Boolean(findLocalOpengrepConfig(cwd)),
|
|
38
74
|
semantic: (d, { blockingAllowed }) => blockingAllowed && d.severity === 1 ? "blocking" : "warning",
|
|
39
75
|
defectClass: (d) => classifyDefect(String(d.code ?? ""), "opengrep", d.message ?? ""),
|
|
76
|
+
// Honor the canonical Semgrep suppression the user already knows (#441).
|
|
77
|
+
isSuppressed: isNosemgrepSuppressed,
|
|
40
78
|
},
|
|
41
79
|
{
|
|
42
80
|
serverId: "ast-grep",
|
|
@@ -27,6 +27,7 @@ import { loadPiLensProjectConfig } from "../project-lens-config.js";
|
|
|
27
27
|
import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
|
|
28
28
|
import { safeSpawnAsync } from "../safe-spawn.js";
|
|
29
29
|
import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
|
|
30
|
+
import { applyInlineSuppressions } from "./inline-suppressions.js";
|
|
30
31
|
import { getToolPlan } from "./plan.js";
|
|
31
32
|
import { resolveRunnerPath } from "./runner-context.js";
|
|
32
33
|
import { getToolProfile } from "./tool-profile.js";
|
|
@@ -187,42 +188,6 @@ function dedupeOverlappingDiagnostics(diagnostics) {
|
|
|
187
188
|
}
|
|
188
189
|
return [...byKey.values()];
|
|
189
190
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Apply inline suppression comments.
|
|
192
|
-
* Syntax: `// pi-lens-ignore: rule-id` (JS/TS) or `# pi-lens-ignore: rule-id` (Python/Ruby/etc.)
|
|
193
|
-
* Place on the same line as the diagnostic or the line immediately above it.
|
|
194
|
-
*/
|
|
195
|
-
function applyInlineSuppressions(diagnostics, content) {
|
|
196
|
-
if (!content || !diagnostics.length)
|
|
197
|
-
return diagnostics;
|
|
198
|
-
// Build a set of (line, ruleId) pairs that are suppressed.
|
|
199
|
-
// Line numbers are 1-based to match diagnostic line numbers.
|
|
200
|
-
const suppressed = new Set();
|
|
201
|
-
const lines = content.split("\n");
|
|
202
|
-
const SUPPRESS_RE = /(?:\/\/|#)\s*pi-lens-ignore:\s*(.+)/;
|
|
203
|
-
for (let i = 0; i < lines.length; i++) {
|
|
204
|
-
const m = SUPPRESS_RE.exec(lines[i]);
|
|
205
|
-
if (!m)
|
|
206
|
-
continue;
|
|
207
|
-
const rules = m[1]
|
|
208
|
-
.split(",")
|
|
209
|
-
.map((r) => r.trim())
|
|
210
|
-
.filter(Boolean);
|
|
211
|
-
const suppressedLine = i + 1; // same line (1-based)
|
|
212
|
-
const nextLine = i + 2; // next line (1-based)
|
|
213
|
-
for (const ruleId of rules) {
|
|
214
|
-
suppressed.add(`${suppressedLine}:${ruleId}`);
|
|
215
|
-
suppressed.add(`${nextLine}:${ruleId}`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (suppressed.size === 0)
|
|
219
|
-
return diagnostics;
|
|
220
|
-
return diagnostics.filter((d) => {
|
|
221
|
-
const ruleId = d.rule ?? d.id ?? "";
|
|
222
|
-
const line = d.line ?? 1;
|
|
223
|
-
return !suppressed.has(`${line}:${ruleId}`);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
191
|
function suppressLintOverlapsWithLsp(diagnostics) {
|
|
227
192
|
const lspBySpanClass = new Set();
|
|
228
193
|
const lspByLine = new Set();
|
|
@@ -230,7 +195,7 @@ function suppressLintOverlapsWithLsp(diagnostics) {
|
|
|
230
195
|
return getToolProfile(tool).lintLike;
|
|
231
196
|
};
|
|
232
197
|
for (const d of diagnostics) {
|
|
233
|
-
if (d.tool !== "lsp"
|
|
198
|
+
if (d.tool !== "lsp")
|
|
234
199
|
continue;
|
|
235
200
|
const line = d.line ?? 1;
|
|
236
201
|
const defectClass = d.defectClass ?? classifyDiagnostic(d);
|
|
@@ -240,7 +205,7 @@ function suppressLintOverlapsWithLsp(diagnostics) {
|
|
|
240
205
|
if (lspByLine.size === 0)
|
|
241
206
|
return diagnostics;
|
|
242
207
|
return diagnostics.filter((d) => {
|
|
243
|
-
if (d.tool === "lsp"
|
|
208
|
+
if (d.tool === "lsp")
|
|
244
209
|
return true;
|
|
245
210
|
if (!isLintTool(d.tool))
|
|
246
211
|
return true;
|
|
@@ -628,7 +593,7 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
628
593
|
const inlineFixed = fixedItems;
|
|
629
594
|
const coverageNotice = buildCoverageNotice(ctx, runnerLatencies);
|
|
630
595
|
// Format output — only blocking issues shown inline
|
|
631
|
-
// Warnings tracked but not shown (noise) — surfaced via
|
|
596
|
+
// Warnings tracked but not shown (noise) — surfaced via lens_diagnostics
|
|
632
597
|
const blockerOutput = formatDiagnostics(inlineBlockers, "blocking");
|
|
633
598
|
let output = blockerOutput;
|
|
634
599
|
output += formatDiagnostics(inlineFixed, "fixed");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { registerRule } from "./fact-rule-runner.js";
|
|
2
1
|
import { scheduleProviders } from "./fact-scheduler.js";
|
|
3
2
|
const providers = [];
|
|
4
3
|
export function registerProvider(p) {
|
|
@@ -8,64 +7,17 @@ export function clearProviders() {
|
|
|
8
7
|
providers.length = 0;
|
|
9
8
|
}
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
10
|
+
* Run the registered fact providers for `ctx`, in dependency order.
|
|
11
|
+
*
|
|
12
|
+
* Providers + fact rules are registered eagerly at `integration.ts` import (the
|
|
13
|
+
* dispatch entry) — including the tree-sitter-backed providers. That's safe for
|
|
14
|
+
* pi-lens's eager graph because the parsing stack loads `web-tree-sitter` (an
|
|
15
|
+
* optional dep) via a dynamic `import()` inside `client.init()`, not at module
|
|
16
|
+
* import, so an unavailable grammar/runtime degrades there rather than crashing
|
|
17
|
+
* the extension at load. As of #402 nothing here uses the `typescript` compiler,
|
|
18
|
+
* so the old lazy-import-with-degrade indirection (#285/#335) is gone.
|
|
19
19
|
*/
|
|
20
|
-
let tsUnitsEnsured = null;
|
|
21
|
-
export function ensureTypeScriptDispatchUnits() {
|
|
22
|
-
tsUnitsEnsured ??= (async () => {
|
|
23
|
-
try {
|
|
24
|
-
const [tryCatch, fn, comment, imp, quality, sonar] = await Promise.all([
|
|
25
|
-
import("./facts/try-catch-facts.js"),
|
|
26
|
-
import("./facts/function-facts.js"),
|
|
27
|
-
import("./facts/comment-facts.js"),
|
|
28
|
-
import("./facts/import-facts.js"),
|
|
29
|
-
import("./rules/quality-rules.js"),
|
|
30
|
-
import("./rules/sonar-rules.js"),
|
|
31
|
-
]);
|
|
32
|
-
registerProvider(tryCatch.tryCatchFactProvider);
|
|
33
|
-
registerProvider(fn.functionFactProvider);
|
|
34
|
-
registerProvider(comment.commentFactProvider);
|
|
35
|
-
registerProvider(imp.importFactProvider);
|
|
36
|
-
registerRule(quality.highImportCouplingRule);
|
|
37
|
-
registerRule(quality.noBooleanParamsRule);
|
|
38
|
-
registerRule(quality.noComplexConditionalsRule);
|
|
39
|
-
registerRule(sonar.commentedCredentialsRule);
|
|
40
|
-
registerRule(sonar.commentedOutCodeRule);
|
|
41
|
-
registerRule(sonar.corsWildcardRule);
|
|
42
|
-
registerRule(sonar.duplicateStringLiteralRule);
|
|
43
|
-
registerRule(sonar.dynamicRegexpRule);
|
|
44
|
-
registerRule(sonar.functionInLoopRule);
|
|
45
|
-
registerRule(sonar.maxSwitchCasesRule);
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
// Degrade, don't crash: name the failure + emit the install fingerprint
|
|
49
|
-
// so a reporter has the full picture. tsUnitsEnsured stays resolved, so we
|
|
50
|
-
// don't re-attempt (and re-log) on every subsequent dispatch.
|
|
51
|
-
try {
|
|
52
|
-
const { collectInstallDiagnostics, formatInstallDiagnostics } = await import("../install-diagnostics.js");
|
|
53
|
-
console.error(`[pi-lens] TypeScript-based dispatch analysis disabled (degraded mode): ${err?.message ?? String(err)}`);
|
|
54
|
-
console.error(formatInstallDiagnostics(collectInstallDiagnostics(), err));
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
console.error(`[pi-lens] TypeScript-based dispatch analysis disabled: ${err?.message ?? String(err)}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
})();
|
|
61
|
-
return tsUnitsEnsured;
|
|
62
|
-
}
|
|
63
|
-
/** Test-only: drop the memoized lazy-registration so it can be re-exercised. */
|
|
64
|
-
export function _resetTypeScriptDispatchUnitsForTests() {
|
|
65
|
-
tsUnitsEnsured = null;
|
|
66
|
-
}
|
|
67
20
|
export async function runProviders(ctx) {
|
|
68
|
-
await ensureTypeScriptDispatchUnits();
|
|
69
21
|
const applicable = providers.filter((p) => p.appliesTo(ctx));
|
|
70
22
|
const ordered = scheduleProviders(applicable);
|
|
71
23
|
for (const provider of ordered) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { extractFactsFromTree, walk } from "./tree-sitter-facts.js";
|
|
2
2
|
export const commentFactProvider = {
|
|
3
3
|
id: "fact.file.comments",
|
|
4
4
|
provides: ["file.comments"],
|
|
@@ -6,30 +6,17 @@ export const commentFactProvider = {
|
|
|
6
6
|
appliesTo(ctx) {
|
|
7
7
|
return /\.tsx?$/.test(ctx.filePath);
|
|
8
8
|
},
|
|
9
|
-
run(ctx, store) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const lc = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
19
|
-
comments.push({
|
|
20
|
-
line: lc.line + 1,
|
|
21
|
-
text: content.slice(pos, end),
|
|
9
|
+
async run(ctx, store) {
|
|
10
|
+
await extractFactsFromTree(ctx, store, { "file.comments": [] }, (root) => {
|
|
11
|
+
// Tree-sitter attaches comments as `comment` nodes wherever they occur; a
|
|
12
|
+
// pre-order walk yields them in source order (matching the old scanner pass).
|
|
13
|
+
const comments = [];
|
|
14
|
+
walk(root, (node) => {
|
|
15
|
+
if (node.type === "comment") {
|
|
16
|
+
comments.push({ line: node.startPosition.row + 1, text: node.text });
|
|
17
|
+
}
|
|
22
18
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let token = scan.scan();
|
|
26
|
-
while (token !== ts.SyntaxKind.EndOfFileToken) {
|
|
27
|
-
if (token === ts.SyntaxKind.SingleLineCommentTrivia ||
|
|
28
|
-
token === ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
29
|
-
pushComment(scan.getTokenPos(), scan.getTextPos());
|
|
30
|
-
}
|
|
31
|
-
token = scan.scan();
|
|
32
|
-
}
|
|
33
|
-
store.setFileFact(ctx.filePath, "file.comments", comments);
|
|
19
|
+
return { "file.comments": comments };
|
|
20
|
+
});
|
|
34
21
|
},
|
|
35
22
|
};
|