pi-lens 3.1.2 → 3.2.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 +55 -0
- package/README.md +16 -12
- package/clients/ast-grep-client.js +8 -1
- package/clients/ast-grep-client.ts +9 -1
- package/clients/biome-client.js +51 -38
- package/clients/biome-client.ts +60 -58
- package/clients/dependency-checker.js +30 -1
- package/clients/dependency-checker.ts +35 -1
- package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
- package/clients/dispatch/bus-dispatcher.js +15 -14
- package/clients/dispatch/bus-dispatcher.ts +32 -25
- package/clients/dispatch/dispatcher.js +18 -25
- package/clients/dispatch/dispatcher.test.ts +2 -1
- package/clients/dispatch/dispatcher.ts +17 -28
- package/clients/dispatch/plan.js +77 -32
- package/clients/dispatch/plan.ts +78 -32
- package/clients/dispatch/runners/ast-grep-napi.js +36 -376
- package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
- package/clients/dispatch/runners/index.js +8 -4
- package/clients/dispatch/runners/index.ts +8 -4
- package/clients/dispatch/runners/lsp.js +65 -0
- package/clients/dispatch/runners/lsp.ts +125 -0
- package/clients/dispatch/runners/oxlint.js +2 -2
- package/clients/dispatch/runners/oxlint.ts +2 -2
- package/clients/dispatch/runners/pyright.js +24 -8
- package/clients/dispatch/runners/pyright.ts +28 -14
- package/clients/dispatch/runners/rust-clippy.js +2 -2
- package/clients/dispatch/runners/rust-clippy.ts +2 -4
- package/clients/dispatch/runners/tree-sitter.js +14 -2
- package/clients/dispatch/runners/tree-sitter.ts +15 -2
- package/clients/dispatch/runners/ts-lsp.js +3 -3
- package/clients/dispatch/runners/ts-lsp.ts +8 -5
- package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
- package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
- package/clients/dispatch/types.js +3 -0
- package/clients/dispatch/types.ts +3 -0
- package/clients/formatters.js +67 -14
- package/clients/formatters.ts +68 -15
- package/clients/installer/index.js +78 -10
- package/clients/installer/index.ts +519 -426
- package/clients/jscpd-client.js +28 -0
- package/clients/jscpd-client.ts +41 -3
- package/clients/knip-client.js +30 -1
- package/clients/knip-client.ts +34 -2
- package/clients/lsp/__tests__/client.test.ts +64 -41
- package/clients/lsp/__tests__/config.test.ts +25 -17
- package/clients/lsp/__tests__/launch.test.ts +108 -43
- package/clients/lsp/__tests__/service.test.ts +76 -48
- package/clients/lsp/client.js +87 -2
- package/clients/lsp/client.ts +150 -6
- package/clients/lsp/config.js +8 -11
- package/clients/lsp/config.ts +24 -21
- package/clients/lsp/index.js +69 -0
- package/clients/lsp/index.ts +82 -0
- package/clients/lsp/interactive-install.js +19 -8
- package/clients/lsp/interactive-install.ts +52 -27
- package/clients/lsp/launch.js +182 -32
- package/clients/lsp/launch.ts +241 -38
- package/clients/lsp/path-utils.js +3 -46
- package/clients/lsp/path-utils.ts +11 -51
- package/clients/lsp/server.js +93 -71
- package/clients/lsp/server.ts +173 -131
- package/clients/path-utils.js +142 -0
- package/clients/path-utils.ts +153 -0
- package/clients/ruff-client.js +33 -4
- package/clients/ruff-client.ts +44 -13
- package/clients/safe-spawn.js +3 -1
- package/clients/safe-spawn.ts +3 -1
- package/clients/services/effect-integration.js +11 -7
- package/clients/services/effect-integration.ts +34 -26
- package/clients/sg-runner.js +51 -9
- package/clients/sg-runner.ts +58 -15
- package/clients/tree-sitter-client.js +12 -0
- package/clients/tree-sitter-client.ts +12 -0
- package/clients/typescript-client.js +6 -2
- package/clients/typescript-client.ts +9 -2
- package/commands/booboo.js +2 -4
- package/commands/booboo.ts +2 -4
- package/index.ts +377 -93
- package/package.json +2 -1
- package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
- package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
- package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/ast-grep-client.test.js +0 -129
- package/clients/ast-grep-client.test.ts +0 -155
- package/clients/biome-client.test.js +0 -144
- package/clients/cache-manager.test.js +0 -197
- package/clients/complexity-client.test.js +0 -234
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.test.js +0 -115
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/ts-slop.test.js +0 -180
- package/clients/dispatch/runners/ts-slop.test.ts +0 -230
- package/clients/dogfood.test.js +0 -201
- package/clients/file-kinds.test.js +0 -169
- package/clients/go-client.test.js +0 -127
- package/clients/jscpd-client.test.js +0 -127
- package/clients/knip-client.test.js +0 -112
- package/clients/lsp/__tests__/client.test.js +0 -325
- package/clients/lsp/__tests__/config.test.js +0 -166
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/launch.test.js +0 -260
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/service.test.js +0 -417
- package/clients/metrics-client.test.js +0 -141
- package/clients/ruff-client.test.js +0 -132
- package/clients/rust-client.test.js +0 -108
- package/clients/sanitize.test.js +0 -177
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/services/__tests__/effect-integration.test.js +0 -86
- package/clients/test-runner-client.test.js +0 -192
- package/clients/todo-scanner.test.js +0 -301
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.test.js +0 -105
- package/commands/clients/ast-grep-client.js +0 -250
- package/commands/clients/ast-grep-parser.js +0 -86
- package/commands/clients/ast-grep-rule-manager.js +0 -91
- package/commands/clients/ast-grep-types.js +0 -9
- package/commands/clients/biome-client.js +0 -380
- package/commands/clients/complexity-client.js +0 -667
- package/commands/clients/file-kinds.js +0 -177
- package/commands/clients/file-utils.js +0 -40
- package/commands/clients/jscpd-client.js +0 -169
- package/commands/clients/knip-client.js +0 -211
- package/commands/clients/ruff-client.js +0 -297
- package/commands/clients/safe-spawn.js +0 -88
- package/commands/clients/scan-utils.js +0 -83
- package/commands/clients/sg-runner.js +0 -190
- package/commands/clients/types.js +0 -11
- package/commands/clients/typescript-client.js +0 -505
- package/commands/rate.test.js +0 -119
- package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
- package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
- package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for declarative dispatch system
|
|
3
|
-
*/
|
|
4
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { createDispatchContext, getRunner, getRunnersForKind, listRunners, registerRunner, } from "./dispatcher.js";
|
|
6
|
-
// --- Test Runners ---
|
|
7
|
-
const testRunner1 = {
|
|
8
|
-
id: "test-runner-1",
|
|
9
|
-
appliesTo: ["jsts", "python"],
|
|
10
|
-
priority: 10,
|
|
11
|
-
enabledByDefault: true,
|
|
12
|
-
async run() {
|
|
13
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
const testRunner2 = {
|
|
17
|
-
id: "test-runner-2",
|
|
18
|
-
appliesTo: ["python"],
|
|
19
|
-
priority: 20,
|
|
20
|
-
enabledByDefault: true,
|
|
21
|
-
async run() {
|
|
22
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
const testRunnerWithCondition = {
|
|
26
|
-
id: "test-runner-conditional",
|
|
27
|
-
appliesTo: ["jsts"],
|
|
28
|
-
priority: 5,
|
|
29
|
-
enabledByDefault: false,
|
|
30
|
-
when: async (ctx) => ctx.autofix,
|
|
31
|
-
async run() {
|
|
32
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
// --- Tests ---
|
|
36
|
-
describe("Runner Registry", () => {
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
// Note: In a real test suite, we'd reset the registry between tests
|
|
39
|
-
registerRunner(testRunner1);
|
|
40
|
-
registerRunner(testRunner2);
|
|
41
|
-
registerRunner(testRunnerWithCondition);
|
|
42
|
-
});
|
|
43
|
-
it("should register a runner", () => {
|
|
44
|
-
const runner = getRunner("test-runner-1");
|
|
45
|
-
expect(runner).toBeDefined();
|
|
46
|
-
expect(runner?.id).toBe("test-runner-1");
|
|
47
|
-
});
|
|
48
|
-
it("should return undefined for unknown runner", () => {
|
|
49
|
-
const runner = getRunner("unknown-runner");
|
|
50
|
-
expect(runner).toBeUndefined();
|
|
51
|
-
});
|
|
52
|
-
it("should get runners for a specific kind", () => {
|
|
53
|
-
const jstsRunners = getRunnersForKind("jsts");
|
|
54
|
-
expect(jstsRunners.length).toBeGreaterThan(0);
|
|
55
|
-
expect(jstsRunners.some((r) => r.id === "test-runner-1")).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
it("should return runners sorted by priority", () => {
|
|
58
|
-
const jstsRunners = getRunnersForKind("jsts");
|
|
59
|
-
const priorities = jstsRunners.map((r) => r.priority ?? 100);
|
|
60
|
-
for (let i = 1; i < priorities.length; i++) {
|
|
61
|
-
expect(priorities[i - 1]).toBeLessThanOrEqual(priorities[i]);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
it("should list all registered runners", () => {
|
|
65
|
-
const all = listRunners();
|
|
66
|
-
expect(all.length).toBeGreaterThanOrEqual(3);
|
|
67
|
-
});
|
|
68
|
-
it("should reject duplicate registrations", () => {
|
|
69
|
-
// This should log an error but not throw
|
|
70
|
-
expect(() => registerRunner(testRunner1)).not.toThrow();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe("Dispatch Context", () => {
|
|
74
|
-
it("should create a dispatch context", () => {
|
|
75
|
-
const mockPi = { getFlag: (flag) => flag === "autofix" };
|
|
76
|
-
const ctx = createDispatchContext("test.ts", "/project", mockPi);
|
|
77
|
-
expect(ctx.filePath).toBe("test.ts");
|
|
78
|
-
expect(ctx.cwd).toBe("/project");
|
|
79
|
-
expect(ctx.autofix).toBe(false);
|
|
80
|
-
expect(ctx.deltaMode).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
it("should detect file kind", () => {
|
|
83
|
-
const mockPi = { getFlag: () => false };
|
|
84
|
-
const ctxTs = createDispatchContext("test.ts", "/project", mockPi);
|
|
85
|
-
expect(ctxTs.kind).toBe("jsts");
|
|
86
|
-
const ctxPy = createDispatchContext("test.py", "/project", mockPi);
|
|
87
|
-
expect(ctxPy.kind).toBe("python");
|
|
88
|
-
const ctxGo = createDispatchContext("test.go", "/project", mockPi);
|
|
89
|
-
expect(ctxGo.kind).toBe("go");
|
|
90
|
-
});
|
|
91
|
-
it("should respect autofix flag", () => {
|
|
92
|
-
const mockPiNoFix = { getFlag: (_f) => false };
|
|
93
|
-
const ctx1 = createDispatchContext("test.ts", "/project", mockPiNoFix);
|
|
94
|
-
expect(ctx1.autofix).toBe(false);
|
|
95
|
-
const mockPiWithFix = { getFlag: (f) => f === "autofix-biome" };
|
|
96
|
-
const ctx2 = createDispatchContext("test.ts", "/project", mockPiWithFix);
|
|
97
|
-
expect(ctx2.autofix).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe("Conditional Runners", () => {
|
|
101
|
-
beforeEach(() => {
|
|
102
|
-
registerRunner(testRunnerWithCondition);
|
|
103
|
-
});
|
|
104
|
-
it("should respect when condition", async () => {
|
|
105
|
-
const runner = getRunner("test-runner-conditional");
|
|
106
|
-
expect(runner).toBeDefined();
|
|
107
|
-
const mockPiNoFix = { getFlag: () => false };
|
|
108
|
-
const ctxNoFix = createDispatchContext("test.ts", "/project", mockPiNoFix);
|
|
109
|
-
// When autofix is false, the condition should return false
|
|
110
|
-
if (runner?.when) {
|
|
111
|
-
const shouldRun = await runner.when(ctxNoFix);
|
|
112
|
-
expect(shouldRun).toBe(false);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { describe, expect, it, beforeAll, afterAll } from "vitest";
|
|
4
|
-
function createMockContext(filePath, kind = "jsts", cwd) {
|
|
5
|
-
return {
|
|
6
|
-
filePath,
|
|
7
|
-
cwd: cwd || process.cwd(),
|
|
8
|
-
kind,
|
|
9
|
-
autofix: false,
|
|
10
|
-
deltaMode: false,
|
|
11
|
-
baselines: { get: () => undefined, set: () => { }, clear: () => { } },
|
|
12
|
-
pi: { getFlag: () => false },
|
|
13
|
-
hasTool: async () => false,
|
|
14
|
-
log: () => { },
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
describe("architect runner", () => {
|
|
18
|
-
const testDir = path.join(process.env.TEMP || "/tmp", `architect_test_${Date.now()}`);
|
|
19
|
-
const configPath = path.join(testDir, ".pi-lens", "architect.yaml");
|
|
20
|
-
beforeAll(() => {
|
|
21
|
-
// Create test config
|
|
22
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
23
|
-
fs.writeFileSync(configPath, `version: "1.0"
|
|
24
|
-
rules:
|
|
25
|
-
- pattern: "**/*.ts"
|
|
26
|
-
max_lines: 50
|
|
27
|
-
must_not:
|
|
28
|
-
- pattern: 'hardcoded_secret_12345'
|
|
29
|
-
message: "No hardcoded secrets"
|
|
30
|
-
fix: "Use process.env.SECRET"
|
|
31
|
-
- pattern: 'console\.log'
|
|
32
|
-
message: "No console.log in production"
|
|
33
|
-
`);
|
|
34
|
-
});
|
|
35
|
-
afterAll(() => {
|
|
36
|
-
try {
|
|
37
|
-
if (fs.existsSync(testDir)) {
|
|
38
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// Ignore cleanup errors
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
it("should load default config when no user config exists", async () => {
|
|
46
|
-
const module = await import("./architect.js");
|
|
47
|
-
const runner = module.default;
|
|
48
|
-
// Use a unique temp dir with no user config (will fall back to default)
|
|
49
|
-
const noUserConfigDir = path.join(process.env.TEMP || "/tmp", `no_arch_user_config_${Date.now()}`);
|
|
50
|
-
fs.mkdirSync(noUserConfigDir, { recursive: true });
|
|
51
|
-
// Create a very large file that should trigger default max_lines rule
|
|
52
|
-
const tmpFile = path.join(noUserConfigDir, `large_${Date.now()}.ts`);
|
|
53
|
-
fs.writeFileSync(tmpFile, Array(5000).fill("// line").join("\n"));
|
|
54
|
-
try {
|
|
55
|
-
const result = await runner.run(createMockContext(tmpFile, "jsts", noUserConfigDir));
|
|
56
|
-
// Should use default config and find violations
|
|
57
|
-
expect(result.status).toBe("succeeded");
|
|
58
|
-
// Should have size violation from default config
|
|
59
|
-
expect(result.diagnostics.some((d) => d.message.includes("line limit"))).toBe(true);
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
try {
|
|
63
|
-
if (fs.existsSync(tmpFile))
|
|
64
|
-
fs.unlinkSync(tmpFile);
|
|
65
|
-
if (fs.existsSync(noUserConfigDir))
|
|
66
|
-
fs.rmdirSync(noUserConfigDir);
|
|
67
|
-
}
|
|
68
|
-
catch { }
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
it("should detect file size violations", async () => {
|
|
72
|
-
const module = await import("./architect.js");
|
|
73
|
-
const runner = module.default;
|
|
74
|
-
const tmpFile = path.join(testDir, `large_file_${Date.now()}.ts`);
|
|
75
|
-
// Create file with 100 lines (exceeds 50 line limit)
|
|
76
|
-
fs.writeFileSync(tmpFile, Array(100).fill("// line").join("\n"));
|
|
77
|
-
try {
|
|
78
|
-
const result = await runner.run(createMockContext(tmpFile, "jsts", testDir));
|
|
79
|
-
expect(result.status).toBe("succeeded");
|
|
80
|
-
expect(result.diagnostics.length).toBeGreaterThan(0);
|
|
81
|
-
expect(result.diagnostics.some((d) => d.message.includes("50 line limit"))).toBe(true);
|
|
82
|
-
}
|
|
83
|
-
finally {
|
|
84
|
-
try {
|
|
85
|
-
if (fs.existsSync(tmpFile))
|
|
86
|
-
fs.unlinkSync(tmpFile);
|
|
87
|
-
}
|
|
88
|
-
catch { }
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
it("should detect pattern violations", async () => {
|
|
92
|
-
const module = await import("./architect.js");
|
|
93
|
-
const runner = module.default;
|
|
94
|
-
const tmpFile = path.join(testDir, `bad_patterns_${Date.now()}.ts`);
|
|
95
|
-
fs.writeFileSync(tmpFile, `const x = hardcoded_secret_12345;
|
|
96
|
-
console.log(x);
|
|
97
|
-
`);
|
|
98
|
-
try {
|
|
99
|
-
const result = await runner.run(createMockContext(tmpFile, "jsts", testDir));
|
|
100
|
-
expect(result.status).toBe("succeeded");
|
|
101
|
-
expect(result.diagnostics.length).toBeGreaterThanOrEqual(2);
|
|
102
|
-
expect(result.diagnostics.some((d) => d.message.includes("hardcoded"))).toBe(true);
|
|
103
|
-
expect(result.diagnostics.some((d) => d.message.includes("console.log"))).toBe(true);
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
try {
|
|
107
|
-
if (fs.existsSync(tmpFile))
|
|
108
|
-
fs.unlinkSync(tmpFile);
|
|
109
|
-
}
|
|
110
|
-
catch { }
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
it("should return no diagnostics for clean files", async () => {
|
|
114
|
-
const module = await import("./architect.js");
|
|
115
|
-
const runner = module.default;
|
|
116
|
-
const tmpFile = path.join(testDir, `clean_${Date.now()}.ts`);
|
|
117
|
-
// Small file (20 lines) with no violations
|
|
118
|
-
fs.writeFileSync(tmpFile, Array(20).fill("// clean code").join("\n"));
|
|
119
|
-
try {
|
|
120
|
-
const result = await runner.run(createMockContext(tmpFile, "jsts", testDir));
|
|
121
|
-
expect(result.status).toBe("succeeded");
|
|
122
|
-
expect(result.diagnostics.length).toBe(0);
|
|
123
|
-
}
|
|
124
|
-
finally {
|
|
125
|
-
try {
|
|
126
|
-
if (fs.existsSync(tmpFile))
|
|
127
|
-
fs.unlinkSync(tmpFile);
|
|
128
|
-
}
|
|
129
|
-
catch { }
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
it("should skip test files", async () => {
|
|
133
|
-
const module = await import("./architect.js");
|
|
134
|
-
const runner = module.default;
|
|
135
|
-
// The runner should have skipTestFiles: true
|
|
136
|
-
expect(runner.skipTestFiles).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
function createMockContext(filePath, kind = "jsts") {
|
|
5
|
-
return {
|
|
6
|
-
filePath,
|
|
7
|
-
cwd: process.cwd(),
|
|
8
|
-
kind,
|
|
9
|
-
autofix: false,
|
|
10
|
-
deltaMode: false,
|
|
11
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
12
|
-
pi: {},
|
|
13
|
-
hasTool: async () => false,
|
|
14
|
-
log: () => { },
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
describe("ast-grep-napi vs CLI comparison", () => {
|
|
18
|
-
it("should load the napi module", async () => {
|
|
19
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
20
|
-
expect(napiModule.default.id).toBe("ast-grep-napi");
|
|
21
|
-
expect(napiModule.default.appliesTo).toEqual(["jsts"]);
|
|
22
|
-
});
|
|
23
|
-
it("should scan TypeScript file and return succeeded status", async () => {
|
|
24
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `napi_test_${Date.now()}.ts`);
|
|
25
|
-
fs.writeFileSync(tmpFile, `// Test file with various patterns
|
|
26
|
-
function test(items: string[]) {
|
|
27
|
-
for (let i = 0; i < items.length; i++) {
|
|
28
|
-
console.log(items[i]);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
riskyOperation();
|
|
33
|
-
} catch (e) {
|
|
34
|
-
// empty catch
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return await fetchData();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function fetchData() {
|
|
41
|
-
return await Promise.resolve(42);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function riskyOperation() {
|
|
45
|
-
debugger;
|
|
46
|
-
}
|
|
47
|
-
`);
|
|
48
|
-
try {
|
|
49
|
-
// Test NAPI version
|
|
50
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
51
|
-
const napiRunner = napiModule.default;
|
|
52
|
-
console.time("napi");
|
|
53
|
-
let napiResult;
|
|
54
|
-
try {
|
|
55
|
-
napiResult = await napiRunner.run(createMockContext(tmpFile));
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
console.error("NAPI runner threw error:", error);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
console.timeEnd("napi");
|
|
62
|
-
console.log("NAPI result status:", napiResult.status);
|
|
63
|
-
console.log("NAPI result semantic:", napiResult.semantic);
|
|
64
|
-
console.log("NAPI result diagnostics count:", napiResult.diagnostics?.length);
|
|
65
|
-
// Should complete successfully (not skipped, not failed)
|
|
66
|
-
expect(napiResult.status).toBe("succeeded");
|
|
67
|
-
expect(napiResult.semantic).toBe("warning"); // Has findings, so marked as warning
|
|
68
|
-
// Log findings
|
|
69
|
-
console.log("NAPI found:", napiResult.diagnostics.length, "issues");
|
|
70
|
-
console.log("\n=== NAPI FINDINGS ===");
|
|
71
|
-
napiResult.diagnostics.forEach((d, i) => {
|
|
72
|
-
console.log(`${i + 1}. Line ${d.line}: ${d.rule}`);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
finally {
|
|
76
|
-
try {
|
|
77
|
-
if (fs.existsSync(tmpFile)) {
|
|
78
|
-
fs.unlinkSync(tmpFile);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// Ignore cleanup errors
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
it("should skip non-TS/JS files", async () => {
|
|
87
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `napi_test_py_${Date.now()}.py`);
|
|
88
|
-
fs.writeFileSync(tmpFile, "# Python file\nprint('hello')");
|
|
89
|
-
try {
|
|
90
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
91
|
-
const napiRunner = napiModule.default;
|
|
92
|
-
const result = await napiRunner.run(createMockContext(tmpFile, "python"));
|
|
93
|
-
expect(result.status).toBe("skipped");
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
try {
|
|
97
|
-
if (fs.existsSync(tmpFile)) {
|
|
98
|
-
fs.unlinkSync(tmpFile);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
// Ignore cleanup errors
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for oxlint runner
|
|
3
|
-
*/
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
/**
|
|
9
|
-
* Delay helper for Windows file cleanup
|
|
10
|
-
* Windows may hold file handles briefly after process exit
|
|
11
|
-
*/
|
|
12
|
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
-
function createMockContext(filePath, overrides = {}) {
|
|
14
|
-
return {
|
|
15
|
-
filePath,
|
|
16
|
-
cwd: process.cwd(),
|
|
17
|
-
kind: "jsts",
|
|
18
|
-
autofix: false,
|
|
19
|
-
deltaMode: false,
|
|
20
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
21
|
-
pi: { getFlag: () => false, ...overrides.pi },
|
|
22
|
-
hasTool: async () => false,
|
|
23
|
-
log: () => { },
|
|
24
|
-
...overrides,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
describe("oxlint runner", () => {
|
|
28
|
-
const require = createRequire(import.meta.url);
|
|
29
|
-
it("should have correct runner definition", async () => {
|
|
30
|
-
const oxlintModule = await import("./oxlint.js");
|
|
31
|
-
const runner = oxlintModule.default;
|
|
32
|
-
expect(runner.id).toBe("oxlint");
|
|
33
|
-
expect(runner.appliesTo).toEqual(["jsts"]);
|
|
34
|
-
expect(runner.priority).toBe(12);
|
|
35
|
-
expect(runner.enabledByDefault).toBe(false); // Opt-in initially
|
|
36
|
-
expect(runner.skipTestFiles).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
it("should detect oxlint availability", () => {
|
|
39
|
-
const { spawnSync } = require("node:child_process");
|
|
40
|
-
const result = spawnSync("oxlint", ["--version"], {
|
|
41
|
-
encoding: "utf-8",
|
|
42
|
-
timeout: 10000,
|
|
43
|
-
shell: true,
|
|
44
|
-
});
|
|
45
|
-
expect(result.error || result.status !== 0 ? "not available" : "available").toBeTruthy(); // May or may not be installed
|
|
46
|
-
});
|
|
47
|
-
it("should detect common lint issues", async () => {
|
|
48
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_test_${Date.now()}.ts`);
|
|
49
|
-
fs.writeFileSync(tmpFile, `// Test file with issues
|
|
50
|
-
function test() {
|
|
51
|
-
// Double negation
|
|
52
|
-
const flag = !!value;
|
|
53
|
-
|
|
54
|
-
// Unused variable
|
|
55
|
-
const unused = 42;
|
|
56
|
-
|
|
57
|
-
// Console statement
|
|
58
|
-
console.log("test");
|
|
59
|
-
}
|
|
60
|
-
`);
|
|
61
|
-
try {
|
|
62
|
-
const oxlintModule = await import("./oxlint.js");
|
|
63
|
-
const runner = oxlintModule.default;
|
|
64
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
65
|
-
// If oxlint is installed, should detect issues
|
|
66
|
-
// If not installed, will be skipped
|
|
67
|
-
if (result.status !== "skipped") {
|
|
68
|
-
// Should detect at least some issues (console, unused vars, etc.)
|
|
69
|
-
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
70
|
-
expect(result.diagnostics.some((d) => d.tool === "oxlint" &&
|
|
71
|
-
(d.message.includes("console") ||
|
|
72
|
-
d.message.includes("unused") ||
|
|
73
|
-
d.message.includes("!!")))).toBe(true);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
// Windows may hold file handles briefly - add small delay
|
|
78
|
-
await delay(100);
|
|
79
|
-
if (fs.existsSync(tmpFile)) {
|
|
80
|
-
fs.unlinkSync(tmpFile);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
it("should respect no-oxlint flag", async () => {
|
|
85
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_flag_${Date.now()}.ts`);
|
|
86
|
-
fs.writeFileSync(tmpFile, `function test() { console.log("test"); }`);
|
|
87
|
-
try {
|
|
88
|
-
const oxlintModule = await import("./oxlint.js");
|
|
89
|
-
const runner = oxlintModule.default;
|
|
90
|
-
// Create context with no-oxlint flag set to true
|
|
91
|
-
const ctxWithFlag = createMockContext(tmpFile, {
|
|
92
|
-
pi: { getFlag: (name) => name === "no-oxlint" },
|
|
93
|
-
});
|
|
94
|
-
const result = await runner.run(ctxWithFlag);
|
|
95
|
-
expect(result.status).toBe("skipped");
|
|
96
|
-
}
|
|
97
|
-
finally {
|
|
98
|
-
// Windows may hold file handles briefly - add small delay
|
|
99
|
-
await delay(100);
|
|
100
|
-
if (fs.existsSync(tmpFile)) {
|
|
101
|
-
fs.unlinkSync(tmpFile);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
it("should provide fix suggestions when available", async () => {
|
|
106
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_fix_${Date.now()}.ts`);
|
|
107
|
-
fs.writeFileSync(tmpFile, `// File with auto-fixable issues
|
|
108
|
-
const x = !!value;
|
|
109
|
-
`);
|
|
110
|
-
try {
|
|
111
|
-
const oxlintModule = await import("./oxlint.js");
|
|
112
|
-
const runner = oxlintModule.default;
|
|
113
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
114
|
-
if (result.status !== "skipped" && result.diagnostics.length > 0) {
|
|
115
|
-
// Some issues should be fixable
|
|
116
|
-
const fixableDiags = result.diagnostics.filter((d) => d.fixable);
|
|
117
|
-
// At least some diagnostics should have fixes
|
|
118
|
-
expect(fixableDiags.length).toBeGreaterThanOrEqual(0);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
// Windows may hold file handles briefly - add small delay
|
|
123
|
-
await delay(100);
|
|
124
|
-
if (fs.existsSync(tmpFile)) {
|
|
125
|
-
fs.unlinkSync(tmpFile);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
it("should pass clean TypeScript files", async () => {
|
|
130
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_ok_${Date.now()}.ts`);
|
|
131
|
-
fs.writeFileSync(tmpFile, `// Clean TypeScript file
|
|
132
|
-
function greet(name: string): string {
|
|
133
|
-
return \`Hello, \${name}!\`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const result = greet("world");
|
|
137
|
-
export { greet };
|
|
138
|
-
`);
|
|
139
|
-
try {
|
|
140
|
-
const oxlintModule = await import("./oxlint.js");
|
|
141
|
-
const runner = oxlintModule.default;
|
|
142
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
143
|
-
if (result.status !== "skipped") {
|
|
144
|
-
// Clean files should have no issues
|
|
145
|
-
expect(result.diagnostics.length).toBe(0);
|
|
146
|
-
expect(result.status).toBe("succeeded");
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
finally {
|
|
150
|
-
// Windows may hold file handles briefly - add small delay
|
|
151
|
-
await delay(100);
|
|
152
|
-
if (fs.existsSync(tmpFile)) {
|
|
153
|
-
fs.unlinkSync(tmpFile);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
it("should handle JSON output correctly", async () => {
|
|
158
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_json_${Date.now()}.ts`);
|
|
159
|
-
fs.writeFileSync(tmpFile, `const unused = 1;`);
|
|
160
|
-
try {
|
|
161
|
-
const oxlintModule = await import("./oxlint.js");
|
|
162
|
-
const runner = oxlintModule.default;
|
|
163
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
164
|
-
if (result.status !== "skipped") {
|
|
165
|
-
// All diagnostics should have required fields
|
|
166
|
-
for (const diag of result.diagnostics) {
|
|
167
|
-
expect(diag.id).toBeDefined();
|
|
168
|
-
expect(diag.message).toBeDefined();
|
|
169
|
-
expect(diag.tool).toBe("oxlint");
|
|
170
|
-
expect(diag.line).toBeGreaterThanOrEqual(1);
|
|
171
|
-
expect(diag.severity).toMatch(/^(error|warning|info)$/);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
finally {
|
|
176
|
-
// Windows may hold file handles briefly - add small delay
|
|
177
|
-
await delay(100);
|
|
178
|
-
if (fs.existsSync(tmpFile)) {
|
|
179
|
-
fs.unlinkSync(tmpFile);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
it("should skip when oxlint is not available", async () => {
|
|
184
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_skip_${Date.now()}.ts`);
|
|
185
|
-
fs.writeFileSync(tmpFile, `const x = 1;`);
|
|
186
|
-
try {
|
|
187
|
-
const oxlintModule = await import("./oxlint.js");
|
|
188
|
-
const runner = oxlintModule.default;
|
|
189
|
-
// Check if oxlint is available
|
|
190
|
-
const { spawnSync } = require("node:child_process");
|
|
191
|
-
const checkResult = spawnSync("oxlint", ["--version"], {
|
|
192
|
-
encoding: "utf-8",
|
|
193
|
-
timeout: 5000,
|
|
194
|
-
shell: true,
|
|
195
|
-
});
|
|
196
|
-
const isAvailable = !checkResult.error && checkResult.status === 0;
|
|
197
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
198
|
-
if (!isAvailable) {
|
|
199
|
-
expect(result.status).toBe("skipped");
|
|
200
|
-
expect(result.diagnostics).toHaveLength(0);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
finally {
|
|
204
|
-
// Windows may hold file handles briefly - add small delay
|
|
205
|
-
await delay(100);
|
|
206
|
-
if (fs.existsSync(tmpFile)) {
|
|
207
|
-
fs.unlinkSync(tmpFile);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
it("should handle parsing errors gracefully", async () => {
|
|
212
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `oxlint_parse_${Date.now()}.ts`);
|
|
213
|
-
// Intentionally malformed file
|
|
214
|
-
fs.writeFileSync(tmpFile, `const x = `);
|
|
215
|
-
try {
|
|
216
|
-
const oxlintModule = await import("./oxlint.js");
|
|
217
|
-
const runner = oxlintModule.default;
|
|
218
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
219
|
-
// Should handle parse errors without crashing
|
|
220
|
-
expect(["succeeded", "failed", "skipped"]).toContain(result.status);
|
|
221
|
-
}
|
|
222
|
-
finally {
|
|
223
|
-
// Windows may hold file handles briefly - add small delay
|
|
224
|
-
await delay(100);
|
|
225
|
-
if (fs.existsSync(tmpFile)) {
|
|
226
|
-
fs.unlinkSync(tmpFile);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
function createMockContext(filePath) {
|
|
6
|
-
return {
|
|
7
|
-
filePath,
|
|
8
|
-
cwd: process.cwd(),
|
|
9
|
-
kind: "python",
|
|
10
|
-
autofix: false,
|
|
11
|
-
deltaMode: false,
|
|
12
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
13
|
-
pi: {},
|
|
14
|
-
hasTool: async () => false,
|
|
15
|
-
log: () => { },
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
describe("pyright runner", () => {
|
|
19
|
-
const require = createRequire(import.meta.url);
|
|
20
|
-
it("should have correct runner definition", async () => {
|
|
21
|
-
const pyrightModule = await import("./pyright.js");
|
|
22
|
-
const runner = pyrightModule.default;
|
|
23
|
-
expect(runner.id).toBe("pyright");
|
|
24
|
-
expect(runner.appliesTo).toEqual(["python"]);
|
|
25
|
-
expect(runner.priority).toBe(5); // Higher priority than ruff
|
|
26
|
-
expect(runner.enabledByDefault).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
it("should detect pyright availability", () => {
|
|
29
|
-
const { spawnSync } = require("node:child_process");
|
|
30
|
-
const result = spawnSync("npx", ["pyright", "--version"], {
|
|
31
|
-
encoding: "utf-8",
|
|
32
|
-
timeout: 10000,
|
|
33
|
-
shell: true,
|
|
34
|
-
});
|
|
35
|
-
expect(result.error || result.status !== 0 ? "not available" : "available").toBe("available");
|
|
36
|
-
});
|
|
37
|
-
it("should type-check Python files and find errors", async () => {
|
|
38
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `pyright_test_${Date.now()}.py`);
|
|
39
|
-
fs.writeFileSync(tmpFile, `def add(x: int, y: int) -> int:
|
|
40
|
-
return x + y
|
|
41
|
-
|
|
42
|
-
result: str = add(1, 2)
|
|
43
|
-
|
|
44
|
-
def greet(name: str) -> str:
|
|
45
|
-
return "Hello " + name
|
|
46
|
-
|
|
47
|
-
greet(123)
|
|
48
|
-
`);
|
|
49
|
-
try {
|
|
50
|
-
const pyrightModule = await import("./pyright.js");
|
|
51
|
-
const runner = pyrightModule.default;
|
|
52
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
53
|
-
expect(result.diagnostics.length).toBeGreaterThanOrEqual(2);
|
|
54
|
-
expect(result.diagnostics.some((d) => d.tool === "pyright")).toBe(true);
|
|
55
|
-
expect(result.diagnostics.some((d) => d.severity === "error")).toBe(true);
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
try {
|
|
59
|
-
if (fs.existsSync(tmpFile)) {
|
|
60
|
-
fs.unlinkSync(tmpFile);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// Ignore cleanup errors
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
it("should pass valid Python files", async () => {
|
|
69
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `pyright_test_ok_${Date.now()}.py`);
|
|
70
|
-
fs.writeFileSync(tmpFile, `def add(x: int, y: int) -> int:
|
|
71
|
-
return x + y
|
|
72
|
-
|
|
73
|
-
result: str = str(add(1, 2))
|
|
74
|
-
|
|
75
|
-
def greet(name: str) -> str:
|
|
76
|
-
return "Hello " + name
|
|
77
|
-
|
|
78
|
-
greet("world")
|
|
79
|
-
`);
|
|
80
|
-
try {
|
|
81
|
-
const pyrightModule = await import("./pyright.js");
|
|
82
|
-
const runner = pyrightModule.default;
|
|
83
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
84
|
-
expect(result.status).toBe("succeeded");
|
|
85
|
-
expect(result.diagnostics.length).toBe(0);
|
|
86
|
-
}
|
|
87
|
-
finally {
|
|
88
|
-
try {
|
|
89
|
-
if (fs.existsSync(tmpFile)) {
|
|
90
|
-
fs.unlinkSync(tmpFile);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// Ignore cleanup errors
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
});
|