pi-lens 2.2.9 ā 3.0.0
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 +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ast-grep NAPI runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Uses @ast-grep/napi for programmatic parsing instead of CLI.
|
|
5
|
+
* Handles TypeScript/JavaScript/CSS/HTML files with YAML rule support.
|
|
6
|
+
*
|
|
7
|
+
* Replaces CLI-based runners for faster performance (100x speedup).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import type {
|
|
14
|
+
Diagnostic,
|
|
15
|
+
DispatchContext,
|
|
16
|
+
RunnerDefinition,
|
|
17
|
+
RunnerResult,
|
|
18
|
+
} from "../types.js";
|
|
19
|
+
|
|
20
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
|
|
22
|
+
// Lazy load the napi package
|
|
23
|
+
let sg: typeof import("@ast-grep/napi") | undefined;
|
|
24
|
+
|
|
25
|
+
async function loadSg(): Promise<typeof import("@ast-grep/napi") | undefined> {
|
|
26
|
+
if (sg) return sg;
|
|
27
|
+
try {
|
|
28
|
+
sg = await import("@ast-grep/napi");
|
|
29
|
+
return sg;
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Supported extensions for NAPI
|
|
36
|
+
const SUPPORTED_EXTS = [".ts", ".tsx", ".js", ".jsx", ".css", ".html", ".htm"];
|
|
37
|
+
|
|
38
|
+
function canHandle(filePath: string): boolean {
|
|
39
|
+
return SUPPORTED_EXTS.includes(path.extname(filePath).toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getLang(filePath: string, sgModule: typeof import("@ast-grep/napi")): any {
|
|
43
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
44
|
+
switch (ext) {
|
|
45
|
+
case ".ts": return sgModule.Lang.TypeScript;
|
|
46
|
+
case ".tsx": return sgModule.Lang.Tsx;
|
|
47
|
+
case ".js":
|
|
48
|
+
case ".jsx": return sgModule.Lang.JavaScript;
|
|
49
|
+
case ".css": return sgModule.Lang.Css;
|
|
50
|
+
case ".html":
|
|
51
|
+
case ".htm": return sgModule.Lang.Html;
|
|
52
|
+
default: return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// YAML rule types
|
|
57
|
+
interface YamlRuleCondition {
|
|
58
|
+
kind?: string;
|
|
59
|
+
pattern?: string;
|
|
60
|
+
regex?: string;
|
|
61
|
+
has?: YamlRuleCondition;
|
|
62
|
+
any?: YamlRuleCondition[];
|
|
63
|
+
all?: YamlRuleCondition[];
|
|
64
|
+
not?: YamlRuleCondition;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface YamlRule {
|
|
68
|
+
id: string;
|
|
69
|
+
language?: string;
|
|
70
|
+
severity?: string;
|
|
71
|
+
message?: string;
|
|
72
|
+
metadata?: { weight?: number; category?: string };
|
|
73
|
+
rule?: YamlRuleCondition;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadYamlRules(ruleDir: string): YamlRule[] {
|
|
77
|
+
const rules: YamlRule[] = [];
|
|
78
|
+
if (!fs.existsSync(ruleDir)) return rules;
|
|
79
|
+
|
|
80
|
+
const files = fs.readdirSync(ruleDir).filter(f => f.endsWith(".yml"));
|
|
81
|
+
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
try {
|
|
84
|
+
const content = fs.readFileSync(path.join(ruleDir, file), "utf-8");
|
|
85
|
+
// Split by --- to handle multiple YAML documents in one file
|
|
86
|
+
const documents = content.split(/^---$/m).filter(d => d.trim());
|
|
87
|
+
|
|
88
|
+
for (const doc of documents) {
|
|
89
|
+
const rule = parseSimpleYaml(doc.trim());
|
|
90
|
+
if (rule && rule.id) {
|
|
91
|
+
rules.push(rule);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Skip invalid files
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return rules;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseSimpleYaml(content: string): YamlRule | null {
|
|
103
|
+
const lines = content.split("\n");
|
|
104
|
+
const rule: YamlRule = { id: "", metadata: {} };
|
|
105
|
+
let currentSection: "root" | "rule" | "metadata" = "root";
|
|
106
|
+
let sectionStack: Array<{ name: string; indent: number; obj: any }> = [];
|
|
107
|
+
let multilineBuffer: string[] = [];
|
|
108
|
+
let multilineKey = "";
|
|
109
|
+
|
|
110
|
+
function getCurrentObj(): any {
|
|
111
|
+
if (sectionStack.length === 0) return rule;
|
|
112
|
+
return sectionStack[sectionStack.length - 1].obj;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getIndent(line: string): number {
|
|
116
|
+
let count = 0;
|
|
117
|
+
for (const char of line) {
|
|
118
|
+
if (char === " ") count++;
|
|
119
|
+
else if (char === "\t") count += 2;
|
|
120
|
+
else break;
|
|
121
|
+
}
|
|
122
|
+
return count;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
const line = lines[i];
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
129
|
+
|
|
130
|
+
if (trimmed === "---") continue;
|
|
131
|
+
|
|
132
|
+
const indent = getIndent(line);
|
|
133
|
+
|
|
134
|
+
// Pop stack if indent decreased
|
|
135
|
+
while (sectionStack.length > 0 && indent <= sectionStack[sectionStack.length - 1].indent) {
|
|
136
|
+
sectionStack.pop();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check for multiline continuation
|
|
140
|
+
if (line.startsWith(" ") && !trimmed.includes(":") && multilineKey) {
|
|
141
|
+
multilineBuffer.push(trimmed);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Flush multiline buffer
|
|
146
|
+
if (multilineKey && multilineBuffer.length > 0) {
|
|
147
|
+
const value = multilineBuffer.join("\n");
|
|
148
|
+
const current = getCurrentObj();
|
|
149
|
+
if (multilineKey === "pattern" && current) {
|
|
150
|
+
current.pattern = value;
|
|
151
|
+
}
|
|
152
|
+
multilineKey = "";
|
|
153
|
+
multilineBuffer = [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const colonIndex = trimmed.indexOf(":");
|
|
157
|
+
const key = colonIndex > 0 ? trimmed.substring(0, colonIndex).trim() : trimmed;
|
|
158
|
+
const value = colonIndex > 0 ? trimmed.substring(colonIndex + 1).trim() : "";
|
|
159
|
+
|
|
160
|
+
if (key === "id") {
|
|
161
|
+
rule.id = value.replace(/^["']|["']$/g, "");
|
|
162
|
+
} else if (key === "language") {
|
|
163
|
+
rule.language = value;
|
|
164
|
+
} else if (key === "severity") {
|
|
165
|
+
rule.severity = value;
|
|
166
|
+
} else if (key === "message") {
|
|
167
|
+
if (value === "|") {
|
|
168
|
+
multilineKey = "message";
|
|
169
|
+
} else {
|
|
170
|
+
rule.message = value.replace(/^["']|["']$/g, "");
|
|
171
|
+
}
|
|
172
|
+
} else if (key === "metadata") {
|
|
173
|
+
currentSection = "metadata";
|
|
174
|
+
const newObj = {};
|
|
175
|
+
rule.metadata = newObj;
|
|
176
|
+
sectionStack.push({ name: "metadata", indent, obj: newObj });
|
|
177
|
+
} else if (key === "rule") {
|
|
178
|
+
currentSection = "rule";
|
|
179
|
+
const newObj: YamlRuleCondition = {};
|
|
180
|
+
rule.rule = newObj;
|
|
181
|
+
sectionStack.push({ name: "rule", indent, obj: newObj });
|
|
182
|
+
} else if (sectionStack.length > 0) {
|
|
183
|
+
const current = getCurrentObj();
|
|
184
|
+
const currentSectionName = sectionStack[sectionStack.length - 1]?.name;
|
|
185
|
+
|
|
186
|
+
if (key === "weight" && currentSectionName === "metadata") {
|
|
187
|
+
if (!rule.metadata) rule.metadata = {};
|
|
188
|
+
rule.metadata.weight = parseInt(value, 10) || 3;
|
|
189
|
+
} else if (key === "category" && currentSectionName === "metadata") {
|
|
190
|
+
if (!rule.metadata) rule.metadata = {};
|
|
191
|
+
rule.metadata.category = value.replace(/^["']|["']$/g, "");
|
|
192
|
+
} else if (key === "pattern") {
|
|
193
|
+
if (value === "|") {
|
|
194
|
+
multilineKey = "pattern";
|
|
195
|
+
} else {
|
|
196
|
+
// Strip all surrounding quotes (handle nested quotes from YAML)
|
|
197
|
+
let stripped = value;
|
|
198
|
+
while (stripped.startsWith('"') && stripped.endsWith('"') && stripped.length > 1) {
|
|
199
|
+
stripped = stripped.slice(1, -1);
|
|
200
|
+
}
|
|
201
|
+
while (stripped.startsWith("'") && stripped.endsWith("'") && stripped.length > 1) {
|
|
202
|
+
stripped = stripped.slice(1, -1);
|
|
203
|
+
}
|
|
204
|
+
current.pattern = stripped;
|
|
205
|
+
}
|
|
206
|
+
} else if (key === "kind") {
|
|
207
|
+
current.kind = value;
|
|
208
|
+
} else if (key === "regex") {
|
|
209
|
+
// Strip all surrounding quotes
|
|
210
|
+
let stripped = value;
|
|
211
|
+
while (stripped.startsWith('"') && stripped.endsWith('"') && stripped.length > 1) {
|
|
212
|
+
stripped = stripped.slice(1, -1);
|
|
213
|
+
}
|
|
214
|
+
while (stripped.startsWith("'") && stripped.endsWith("'") && stripped.length > 1) {
|
|
215
|
+
stripped = stripped.slice(1, -1);
|
|
216
|
+
}
|
|
217
|
+
current.regex = stripped;
|
|
218
|
+
} else if (key === "has" || key === "not") {
|
|
219
|
+
const newObj: YamlRuleCondition = {};
|
|
220
|
+
current[key] = newObj;
|
|
221
|
+
sectionStack.push({ name: key, indent, obj: newObj });
|
|
222
|
+
} else if (key === "any" || key === "all") {
|
|
223
|
+
if (!current[key]) current[key] = [];
|
|
224
|
+
// Check if next lines with more indent are list items
|
|
225
|
+
let j = i + 1;
|
|
226
|
+
while (j < lines.length) {
|
|
227
|
+
const nextLine = lines[j];
|
|
228
|
+
const nextTrimmed = nextLine.trim();
|
|
229
|
+
if (!nextTrimmed || nextTrimmed.startsWith("#")) {
|
|
230
|
+
j++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const nextIndent = getIndent(nextLine);
|
|
234
|
+
if (nextIndent <= indent) break;
|
|
235
|
+
|
|
236
|
+
if (nextTrimmed.startsWith("- ")) {
|
|
237
|
+
// New list item
|
|
238
|
+
const itemObj: YamlRuleCondition = {};
|
|
239
|
+
current[key].push(itemObj);
|
|
240
|
+
sectionStack.push({ name: key, indent: nextIndent, obj: itemObj });
|
|
241
|
+
// Parse the item content after "- "
|
|
242
|
+
const itemContent = nextTrimmed.substring(2);
|
|
243
|
+
if (itemContent.includes(":")) {
|
|
244
|
+
const [itemKey, itemVal] = itemContent.split(":", 2);
|
|
245
|
+
if (itemKey.trim() === "pattern") {
|
|
246
|
+
itemObj.pattern = itemVal.trim().replace(/^["']|["']$/g, "");
|
|
247
|
+
} else if (itemKey.trim() === "kind") {
|
|
248
|
+
itemObj.kind = itemVal.trim();
|
|
249
|
+
}
|
|
250
|
+
} else if (itemContent) {
|
|
251
|
+
// Assume it's a pattern
|
|
252
|
+
itemObj.pattern = itemContent.replace(/^["']|["']$/g, "");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
j++;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Flush remaining multiline buffer
|
|
262
|
+
if (multilineKey && multilineBuffer.length > 0) {
|
|
263
|
+
const value = multilineBuffer.join("\n");
|
|
264
|
+
const current = getCurrentObj();
|
|
265
|
+
if (multilineKey === "pattern" && current) {
|
|
266
|
+
current.pattern = value;
|
|
267
|
+
} else if (multilineKey === "message") {
|
|
268
|
+
rule.message = value;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return rule.id ? rule : null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Check if a rule uses structured conditions (has/any/all/not/regex)
|
|
277
|
+
*/
|
|
278
|
+
function isStructuredRule(rule: YamlRule): boolean {
|
|
279
|
+
if (!rule.rule) return false;
|
|
280
|
+
return !!(rule.rule.has || rule.rule.any || rule.rule.all || rule.rule.not || rule.rule.regex);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Execute a structured rule using manual AST traversal
|
|
285
|
+
*/
|
|
286
|
+
function executeStructuredRule(
|
|
287
|
+
rootNode: any,
|
|
288
|
+
condition: YamlRuleCondition,
|
|
289
|
+
matches: any[] = []
|
|
290
|
+
): any[] {
|
|
291
|
+
// Start with finding nodes by kind or pattern
|
|
292
|
+
let candidates: any[] = [];
|
|
293
|
+
|
|
294
|
+
if (condition.pattern) {
|
|
295
|
+
// Use pattern matching via findAll
|
|
296
|
+
try {
|
|
297
|
+
candidates = rootNode.findAll(condition.pattern);
|
|
298
|
+
} catch {
|
|
299
|
+
return matches;
|
|
300
|
+
}
|
|
301
|
+
} else if (condition.kind) {
|
|
302
|
+
// Manual traversal for kind matching
|
|
303
|
+
function findByKind(node: any, kind: string): any[] {
|
|
304
|
+
const results: any[] = [];
|
|
305
|
+
if (node.kind() === kind) {
|
|
306
|
+
results.push(node);
|
|
307
|
+
}
|
|
308
|
+
for (const child of node.children()) {
|
|
309
|
+
results.push(...findByKind(child, kind));
|
|
310
|
+
}
|
|
311
|
+
return results;
|
|
312
|
+
}
|
|
313
|
+
candidates = findByKind(rootNode, condition.kind);
|
|
314
|
+
} else {
|
|
315
|
+
// No kind or pattern, search all nodes
|
|
316
|
+
function getAllNodes(node: any): any[] {
|
|
317
|
+
const results = [node];
|
|
318
|
+
for (const child of node.children()) {
|
|
319
|
+
results.push(...getAllNodes(child));
|
|
320
|
+
}
|
|
321
|
+
return results;
|
|
322
|
+
}
|
|
323
|
+
candidates = getAllNodes(rootNode);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Filter candidates by conditions
|
|
327
|
+
for (const candidate of candidates) {
|
|
328
|
+
let matchesCondition = true;
|
|
329
|
+
|
|
330
|
+
// Check 'has' condition
|
|
331
|
+
if (condition.has && matchesCondition) {
|
|
332
|
+
const subMatches = executeStructuredRule(candidate, condition.has, []);
|
|
333
|
+
if (subMatches.length === 0) matchesCondition = false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check 'not' condition
|
|
337
|
+
if (condition.not && matchesCondition) {
|
|
338
|
+
const subMatches = executeStructuredRule(candidate, condition.not, []);
|
|
339
|
+
if (subMatches.length > 0) matchesCondition = false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check 'any' condition (at least one must match)
|
|
343
|
+
if (condition.any && matchesCondition) {
|
|
344
|
+
let anyMatches = false;
|
|
345
|
+
for (const subCondition of condition.any) {
|
|
346
|
+
const subMatches = executeStructuredRule(candidate, subCondition, []);
|
|
347
|
+
if (subMatches.length > 0) {
|
|
348
|
+
anyMatches = true;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!anyMatches) matchesCondition = false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Check 'all' condition (all must match)
|
|
356
|
+
if (condition.all && matchesCondition) {
|
|
357
|
+
for (const subCondition of condition.all) {
|
|
358
|
+
const subMatches = executeStructuredRule(candidate, subCondition, []);
|
|
359
|
+
if (subMatches.length === 0) {
|
|
360
|
+
matchesCondition = false;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check 'regex' condition
|
|
367
|
+
if (condition.regex && matchesCondition) {
|
|
368
|
+
const text = candidate.text();
|
|
369
|
+
const regex = new RegExp(condition.regex);
|
|
370
|
+
if (!regex.test(text)) matchesCondition = false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (matchesCondition) {
|
|
374
|
+
matches.push(candidate);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return matches;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const astGrepNapiRunner: RunnerDefinition = {
|
|
382
|
+
id: "ast-grep-napi",
|
|
383
|
+
appliesTo: ["jsts"], // TypeScript/JavaScript only
|
|
384
|
+
priority: 15, // Run early (after type checkers, before other linters)
|
|
385
|
+
enabledByDefault: true,
|
|
386
|
+
skipTestFiles: true,
|
|
387
|
+
|
|
388
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
389
|
+
const startTime = Date.now();
|
|
390
|
+
|
|
391
|
+
if (!canHandle(ctx.filePath)) {
|
|
392
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const sgModule = await loadSg();
|
|
396
|
+
if (!sgModule) {
|
|
397
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!fs.existsSync(ctx.filePath)) {
|
|
401
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const lang = getLang(ctx.filePath, sgModule);
|
|
405
|
+
if (!lang) {
|
|
406
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const content = fs.readFileSync(ctx.filePath, "utf-8");
|
|
410
|
+
|
|
411
|
+
let root: import("@ast-grep/napi").SgRoot;
|
|
412
|
+
try {
|
|
413
|
+
root = sgModule.parse(lang, content);
|
|
414
|
+
} catch {
|
|
415
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const diagnostics: Diagnostic[] = [];
|
|
419
|
+
const rootNode = root.root();
|
|
420
|
+
|
|
421
|
+
// Load rules from ts-slop-rules only (complementary to ast-grep CLI)
|
|
422
|
+
const ruleDirs = [
|
|
423
|
+
path.join(process.cwd(), "rules/ts-slop-rules/rules"),
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
for (const ruleDir of ruleDirs) {
|
|
427
|
+
const rules = loadYamlRules(ruleDir);
|
|
428
|
+
|
|
429
|
+
for (const rule of rules) {
|
|
430
|
+
// Skip rules for different languages (case-insensitive)
|
|
431
|
+
const lang = rule.language?.toLowerCase();
|
|
432
|
+
if (lang && lang !== "typescript" && lang !== "javascript") {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
let matches: any[] = [];
|
|
438
|
+
|
|
439
|
+
if (isStructuredRule(rule) && rule.rule) {
|
|
440
|
+
// Use structured rule execution
|
|
441
|
+
matches = executeStructuredRule(rootNode, rule.rule, []);
|
|
442
|
+
} else if (rule.rule?.pattern || rule.rule?.kind) {
|
|
443
|
+
// Use simple pattern matching
|
|
444
|
+
const pattern = rule.rule.pattern || rule.rule.kind;
|
|
445
|
+
if (pattern) {
|
|
446
|
+
try {
|
|
447
|
+
matches = rootNode.findAll(pattern);
|
|
448
|
+
} catch {
|
|
449
|
+
// Pattern failed, try manual traversal for kind
|
|
450
|
+
if (rule.rule.kind) {
|
|
451
|
+
function findByKind(node: any, kind: string): any[] {
|
|
452
|
+
const results: any[] = [];
|
|
453
|
+
if (node.kind() === kind) results.push(node);
|
|
454
|
+
for (const child of node.children()) {
|
|
455
|
+
results.push(...findByKind(child, kind));
|
|
456
|
+
}
|
|
457
|
+
return results;
|
|
458
|
+
}
|
|
459
|
+
matches = findByKind(rootNode, rule.rule.kind);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
for (const match of matches) {
|
|
466
|
+
const range = match.range();
|
|
467
|
+
const weight = rule.metadata?.weight || 3;
|
|
468
|
+
const severity = weight >= 4 ? "error" : "warning";
|
|
469
|
+
|
|
470
|
+
diagnostics.push({
|
|
471
|
+
id: `ast-grep-napi-${range.start.line}-${rule.id}`,
|
|
472
|
+
message: `[${rule.metadata?.category || "slop"}] ${rule.message || rule.id}`,
|
|
473
|
+
filePath: ctx.filePath,
|
|
474
|
+
line: range.start.line + 1,
|
|
475
|
+
column: range.start.column + 1,
|
|
476
|
+
severity,
|
|
477
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
478
|
+
tool: "ast-grep-napi",
|
|
479
|
+
rule: rule.id,
|
|
480
|
+
fixable: false,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
// Rule failed, skip
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const elapsed = Date.now() - startTime;
|
|
490
|
+
if (diagnostics.length > 0 || elapsed > 50) {
|
|
491
|
+
console.error(`[ast-grep-napi] ${ctx.filePath}: ${elapsed}ms, ${diagnostics.length} issues`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (diagnostics.length === 0) {
|
|
495
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
status: "failed",
|
|
500
|
+
diagnostics,
|
|
501
|
+
semantic: "warning",
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
export default astGrepNapiRunner;
|
|
@@ -6,8 +6,30 @@
|
|
|
6
6
|
* - async/await issues
|
|
7
7
|
* - security anti-patterns
|
|
8
8
|
*/
|
|
9
|
-
import { spawnSync } from "node:child_process";
|
|
10
9
|
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
12
|
+
// Simple YAML fix: field extractor
|
|
13
|
+
function extractFixFromRule(ruleId, ruleDir) {
|
|
14
|
+
try {
|
|
15
|
+
const rulePath = `${ruleDir}/${ruleId}.yml`;
|
|
16
|
+
if (!fs.existsSync(rulePath))
|
|
17
|
+
return undefined;
|
|
18
|
+
const content = fs.readFileSync(rulePath, "utf-8");
|
|
19
|
+
const fixMatch = content.match(/^fix:\s*\|?([\s\S]*?)(?=^\w|^rule:|\Z)/m);
|
|
20
|
+
if (fixMatch) {
|
|
21
|
+
return fixMatch[1]
|
|
22
|
+
.split("\n")
|
|
23
|
+
.map((line) => line.replace(/^\s*\|?\s*/, ""))
|
|
24
|
+
.filter((line) => line.length > 0)
|
|
25
|
+
.join("\n");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore errors
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
11
33
|
const astGrepRunner = {
|
|
12
34
|
id: "ast-grep",
|
|
13
35
|
appliesTo: ["jsts", "python", "go", "rust", "cxx"],
|
|
@@ -15,11 +37,9 @@ const astGrepRunner = {
|
|
|
15
37
|
enabledByDefault: false,
|
|
16
38
|
skipTestFiles: true, // Many rules are noisy in tests
|
|
17
39
|
async run(ctx) {
|
|
18
|
-
// Check if ast-grep is available
|
|
19
|
-
const check =
|
|
20
|
-
encoding: "utf-8",
|
|
40
|
+
// Check if ast-grep is available (use npx for local installs)
|
|
41
|
+
const check = safeSpawn("npx", ["sg", "--version"], {
|
|
21
42
|
timeout: 5000,
|
|
22
|
-
shell: true,
|
|
23
43
|
});
|
|
24
44
|
if (check.error || check.status !== 0) {
|
|
25
45
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
@@ -29,19 +49,17 @@ const astGrepRunner = {
|
|
|
29
49
|
if (!configPath) {
|
|
30
50
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
31
51
|
}
|
|
32
|
-
// Run ast-grep scan on the file
|
|
33
|
-
const args = ["scan", "--config", configPath, "--json", ctx.filePath];
|
|
34
|
-
const result =
|
|
35
|
-
encoding: "utf-8",
|
|
52
|
+
// Run ast-grep scan on the file (use npx for local installs)
|
|
53
|
+
const args = ["sg", "scan", "--config", configPath, "--json", ctx.filePath];
|
|
54
|
+
const result = safeSpawn("npx", args, {
|
|
36
55
|
timeout: 30000,
|
|
37
|
-
shell: true,
|
|
38
56
|
});
|
|
39
57
|
const raw = result.stdout + result.stderr;
|
|
40
58
|
if (result.status === 0 && !raw.trim()) {
|
|
41
59
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
42
60
|
}
|
|
43
61
|
// Parse results
|
|
44
|
-
const diagnostics = parseAstGrepOutput(raw, ctx.filePath);
|
|
62
|
+
const diagnostics = parseAstGrepOutput(raw, ctx.filePath, configPath);
|
|
45
63
|
if (diagnostics.length === 0) {
|
|
46
64
|
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
47
65
|
}
|
|
@@ -66,27 +84,52 @@ function findAstGrepConfig(cwd) {
|
|
|
66
84
|
}
|
|
67
85
|
return undefined;
|
|
68
86
|
}
|
|
69
|
-
function parseAstGrepOutput(raw, filePath) {
|
|
87
|
+
function parseAstGrepOutput(raw, filePath, _configPath) {
|
|
70
88
|
const diagnostics = [];
|
|
71
89
|
// Try to parse as JSON
|
|
90
|
+
// Determine rule directory for fix: extraction
|
|
91
|
+
const ruleDir = _configPath
|
|
92
|
+
? path.dirname(_configPath).replace("/.sgconfig.yml", "/rules")
|
|
93
|
+
: path.join(process.cwd(), "rules", "ast-grep-rules", "rules");
|
|
72
94
|
try {
|
|
73
95
|
const parsed = JSON.parse(raw);
|
|
74
96
|
if (Array.isArray(parsed)) {
|
|
75
97
|
for (const item of parsed) {
|
|
76
98
|
const line = item.range?.start?.line || 1;
|
|
99
|
+
const ruleId = item.rule || "unknown";
|
|
100
|
+
// Build message with inline fix suggestion
|
|
101
|
+
let message = item.message || item.lines || "";
|
|
102
|
+
let fixSuggestion;
|
|
103
|
+
if (item.replacement) {
|
|
104
|
+
// Show the actual code change inline in the message
|
|
105
|
+
const replacementPreview = item.replacement.length > 40
|
|
106
|
+
? `${item.replacement.substring(0, 40)}...`
|
|
107
|
+
: item.replacement;
|
|
108
|
+
message += `\nš” Suggested fix: ā "${replacementPreview}"`;
|
|
109
|
+
fixSuggestion = `Replace with: ${item.replacement}`;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Try to get fix: from rule YAML
|
|
113
|
+
const ruleFix = extractFixFromRule(ruleId, ruleDir);
|
|
114
|
+
if (ruleFix) {
|
|
115
|
+
const fixPreview = ruleFix.length > 60
|
|
116
|
+
? `${ruleFix.substring(0, 60)}...`
|
|
117
|
+
: ruleFix;
|
|
118
|
+
message += `\nš” Suggested fix:\n${fixPreview}`;
|
|
119
|
+
fixSuggestion = ruleFix;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
77
122
|
diagnostics.push({
|
|
78
|
-
id: `ast-grep-${line}-${
|
|
79
|
-
message
|
|
123
|
+
id: `ast-grep-${line}-${ruleId}`,
|
|
124
|
+
message,
|
|
80
125
|
filePath,
|
|
81
126
|
line,
|
|
82
127
|
severity: item.severity === "error" ? "error" : "warning",
|
|
83
128
|
semantic: item.severity === "error" ? "blocking" : "warning",
|
|
84
129
|
tool: "ast-grep",
|
|
85
|
-
rule:
|
|
86
|
-
fixable: !!item.replacement,
|
|
87
|
-
fixSuggestion
|
|
88
|
-
? "Run `sg fix` to auto-fix"
|
|
89
|
-
: undefined,
|
|
130
|
+
rule: ruleId,
|
|
131
|
+
fixable: !!item.replacement || !!fixSuggestion,
|
|
132
|
+
fixSuggestion,
|
|
90
133
|
});
|
|
91
134
|
}
|
|
92
135
|
}
|