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,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter Query Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads tree-sitter queries from YAML files in rules/tree-sitter-queries/
|
|
5
|
+
* and provides them to the TreeSitterClient.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
export interface TreeSitterQuery {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
severity: "error" | "warning" | "info";
|
|
18
|
+
category: string;
|
|
19
|
+
language: string;
|
|
20
|
+
message: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
query: string;
|
|
23
|
+
metavars: string[];
|
|
24
|
+
post_filter?: string;
|
|
25
|
+
// biome-ignore lint/suspicious/noExplicitAny: Flexible filter params
|
|
26
|
+
post_filter_params?: Record<string, any>;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
has_fix: boolean;
|
|
29
|
+
fix_action?: string;
|
|
30
|
+
examples?: {
|
|
31
|
+
bad?: string;
|
|
32
|
+
good?: string;
|
|
33
|
+
};
|
|
34
|
+
filePath: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class TreeSitterQueryLoader {
|
|
38
|
+
private queries: Map<string, TreeSitterQuery[]> = new Map();
|
|
39
|
+
private loaded = false;
|
|
40
|
+
private verbose: boolean;
|
|
41
|
+
|
|
42
|
+
constructor(verbose = false) {
|
|
43
|
+
this.verbose = verbose;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Debug logging helper */
|
|
47
|
+
private dbg(msg: string): void {
|
|
48
|
+
if (this.verbose) {
|
|
49
|
+
console.error(`[query-loader] ${msg}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load all queries from the rules/tree-sitter-queries directory
|
|
55
|
+
*/
|
|
56
|
+
async loadQueries(): Promise<Map<string, TreeSitterQuery[]>> {
|
|
57
|
+
if (this.loaded) return this.queries;
|
|
58
|
+
|
|
59
|
+
const queriesDir = path.join(process.cwd(), "rules", "tree-sitter-queries");
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(queriesDir)) {
|
|
62
|
+
this.dbg(`Queries directory not found: ${queriesDir}`);
|
|
63
|
+
return this.queries;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Load queries from each language subdirectory
|
|
67
|
+
const languageDirs = fs
|
|
68
|
+
.readdirSync(queriesDir, { withFileTypes: true })
|
|
69
|
+
.filter((d) => d.isDirectory())
|
|
70
|
+
.map((d) => d.name);
|
|
71
|
+
|
|
72
|
+
for (const lang of languageDirs) {
|
|
73
|
+
const langDir = path.join(queriesDir, lang);
|
|
74
|
+
const queryFiles = fs
|
|
75
|
+
.readdirSync(langDir)
|
|
76
|
+
.filter((f) => f.endsWith(".yml"));
|
|
77
|
+
|
|
78
|
+
const langQueries: TreeSitterQuery[] = [];
|
|
79
|
+
|
|
80
|
+
for (const file of queryFiles) {
|
|
81
|
+
const filePath = path.join(langDir, file);
|
|
82
|
+
const query = this.parseQueryFile(filePath, lang);
|
|
83
|
+
if (query) {
|
|
84
|
+
langQueries.push(query);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (langQueries.length > 0) {
|
|
89
|
+
this.queries.set(lang, langQueries);
|
|
90
|
+
this.dbg(`Loaded ${langQueries.length} queries for ${lang}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.loaded = true;
|
|
95
|
+
return this.queries;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse a single YAML query file
|
|
100
|
+
*/
|
|
101
|
+
private parseQueryFile(
|
|
102
|
+
filePath: string,
|
|
103
|
+
language: string,
|
|
104
|
+
): TreeSitterQuery | null {
|
|
105
|
+
try {
|
|
106
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
107
|
+
|
|
108
|
+
// Simple YAML parsing (extract key: value pairs)
|
|
109
|
+
const parsed = this.parseYaml(content);
|
|
110
|
+
|
|
111
|
+
if (!parsed.id || !parsed.query) {
|
|
112
|
+
this.dbg(`Invalid query file: ${filePath}`);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
id: String(parsed.id),
|
|
118
|
+
name: String(parsed.name || parsed.id),
|
|
119
|
+
severity: this.parseSeverity(parsed.severity),
|
|
120
|
+
category: String(parsed.category || "general"),
|
|
121
|
+
language: String(parsed.language || language),
|
|
122
|
+
message: String(parsed.message || `Pattern: ${parsed.id}`),
|
|
123
|
+
description: parsed.description
|
|
124
|
+
? String(parsed.description)
|
|
125
|
+
: undefined,
|
|
126
|
+
query:
|
|
127
|
+
this.extractMultilineValue(content, "query") || String(parsed.query),
|
|
128
|
+
metavars: Array.isArray(parsed.metavars)
|
|
129
|
+
? parsed.metavars.map(String)
|
|
130
|
+
: this.extractMetavars(String(parsed.query)),
|
|
131
|
+
post_filter: parsed.post_filter
|
|
132
|
+
? String(parsed.post_filter)
|
|
133
|
+
: undefined,
|
|
134
|
+
// biome-ignore lint/suspicious/noExplicitAny: Post filter params
|
|
135
|
+
post_filter_params: parsed.post_filter_params as any,
|
|
136
|
+
tags: Array.isArray(parsed.tags) ? parsed.tags.map(String) : undefined,
|
|
137
|
+
has_fix: parsed.has_fix === true || parsed.has_fix === "true",
|
|
138
|
+
fix_action: parsed.fix_action ? String(parsed.fix_action) : undefined,
|
|
139
|
+
filePath,
|
|
140
|
+
};
|
|
141
|
+
} catch (err) {
|
|
142
|
+
this.dbg(`Failed to parse ${filePath}: ${err}`);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Simple YAML parser for our query files
|
|
149
|
+
*/
|
|
150
|
+
private parseYaml(
|
|
151
|
+
content: string,
|
|
152
|
+
): Record<string, string | string[] | boolean> {
|
|
153
|
+
const result: Record<string, string | string[] | boolean> = {};
|
|
154
|
+
const lines = content.split("\n");
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < lines.length; i++) {
|
|
157
|
+
const line = lines[i];
|
|
158
|
+
const match = line.match(/^([a-z_]+):\s*(.*)$/);
|
|
159
|
+
if (match) {
|
|
160
|
+
const key = match[1];
|
|
161
|
+
let value: string | string[] | boolean = match[2].trim();
|
|
162
|
+
|
|
163
|
+
// Handle arrays inline: metavars: [A, B, C]
|
|
164
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
165
|
+
value = value
|
|
166
|
+
.slice(1, -1)
|
|
167
|
+
.split(",")
|
|
168
|
+
.map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
169
|
+
}
|
|
170
|
+
// Handle multi-line arrays: metavars:\n - A\n - B
|
|
171
|
+
else if (value === "") {
|
|
172
|
+
// Check if next lines are array items ( - item)
|
|
173
|
+
const arrayItems: string[] = [];
|
|
174
|
+
const baseIndent = line.match(/^(\s*)/)?.[0].length || 0;
|
|
175
|
+
|
|
176
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
177
|
+
const nextLine = lines[j];
|
|
178
|
+
const nextIndent = nextLine.match(/^(\s*)/)?.[0].length || 0;
|
|
179
|
+
|
|
180
|
+
// Stop if we hit a line with same or less indent (new key)
|
|
181
|
+
if (nextIndent <= baseIndent && nextLine.match(/^[a-z_]+:/)) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check if it's an array item
|
|
186
|
+
const itemMatch = nextLine.match(/^\s+-\s*(.+)$/);
|
|
187
|
+
if (itemMatch) {
|
|
188
|
+
// Strip inline comments and trim
|
|
189
|
+
const item = itemMatch[1].trim().replace(/\s*#.*$/, "");
|
|
190
|
+
if (item) arrayItems.push(item);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (arrayItems.length > 0) {
|
|
195
|
+
value = arrayItems;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Handle booleans
|
|
199
|
+
else if (value === "true") value = true;
|
|
200
|
+
else if (value === "false") value = false;
|
|
201
|
+
// Strip quotes from strings
|
|
202
|
+
else if (value.startsWith('"') && value.endsWith('"')) {
|
|
203
|
+
value = value.slice(1, -1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
result[key] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract a multiline value (like query) from YAML
|
|
215
|
+
*/
|
|
216
|
+
private extractMultilineValue(content: string, key: string): string | null {
|
|
217
|
+
const lines = content.split("\n");
|
|
218
|
+
let startLine = -1;
|
|
219
|
+
let startIndent = 0;
|
|
220
|
+
|
|
221
|
+
const keyPrefix = `${key}:`;
|
|
222
|
+
|
|
223
|
+
// Find the key line
|
|
224
|
+
for (let i = 0; i < lines.length; i++) {
|
|
225
|
+
const trimmed = lines[i].trimStart();
|
|
226
|
+
if (trimmed.startsWith(keyPrefix)) {
|
|
227
|
+
startLine = i;
|
|
228
|
+
startIndent = lines[i].length - trimmed.length;
|
|
229
|
+
const afterKey = trimmed.slice(keyPrefix.length).trim();
|
|
230
|
+
// If there's content on the same line (not just |), return it
|
|
231
|
+
if (afterKey && afterKey !== "|") return afterKey;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (startLine === -1) return null;
|
|
237
|
+
|
|
238
|
+
// Collect all lines until we hit a new key with same or less indent
|
|
239
|
+
const valueLines: string[] = [];
|
|
240
|
+
for (let i = startLine + 1; i < lines.length; i++) {
|
|
241
|
+
const line = lines[i];
|
|
242
|
+
|
|
243
|
+
// Track empty lines
|
|
244
|
+
if (!line.trim()) {
|
|
245
|
+
valueLines.push("");
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check indent
|
|
250
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
251
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
252
|
+
const trimmed = line.trim();
|
|
253
|
+
|
|
254
|
+
// Stop at new key with same or less indent (but not at comments)
|
|
255
|
+
if (
|
|
256
|
+
indent <= startIndent &&
|
|
257
|
+
trimmed.match(/^[a-z_]+:/) &&
|
|
258
|
+
!trimmed.startsWith("#")
|
|
259
|
+
) {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Skip comment lines (they're not part of the value)
|
|
264
|
+
if (trimmed.startsWith("#")) continue;
|
|
265
|
+
|
|
266
|
+
// This is part of the multiline value
|
|
267
|
+
valueLines.push(line.slice(startIndent));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Clean up - remove trailing empty lines
|
|
271
|
+
while (valueLines.length > 0 && !valueLines[valueLines.length - 1].trim()) {
|
|
272
|
+
valueLines.pop();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return valueLines.length > 0 ? valueLines.join("\n") : null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Parse severity string to valid type
|
|
280
|
+
*/
|
|
281
|
+
private parseSeverity(value: unknown): "error" | "warning" | "info" {
|
|
282
|
+
if (value === "error") return "error";
|
|
283
|
+
if (value === "warning") return "warning";
|
|
284
|
+
if (value === "info") return "info";
|
|
285
|
+
return "warning"; // default
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extract @VAR patterns from query string
|
|
290
|
+
*/
|
|
291
|
+
private extractMetavars(query: string): string[] {
|
|
292
|
+
const matches = query.match(/@([A-Z_][A-Z0-9_]*)/g);
|
|
293
|
+
if (!matches) return [];
|
|
294
|
+
return [...new Set(matches.map((m) => m.slice(1)))];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get queries for a specific language
|
|
299
|
+
*/
|
|
300
|
+
getQueriesForLanguage(language: string): TreeSitterQuery[] {
|
|
301
|
+
return this.queries.get(language) || [];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Get a specific query by ID
|
|
306
|
+
*/
|
|
307
|
+
getQueryById(id: string): TreeSitterQuery | undefined {
|
|
308
|
+
for (const langQueries of this.queries.values()) {
|
|
309
|
+
const query = langQueries.find((q) => q.id === id);
|
|
310
|
+
if (query) return query;
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Find matching query for a pattern string
|
|
317
|
+
*/
|
|
318
|
+
findMatchingQuery(
|
|
319
|
+
pattern: string,
|
|
320
|
+
language: string,
|
|
321
|
+
): TreeSitterQuery | undefined {
|
|
322
|
+
const langQueries = this.getQueriesForLanguage(language);
|
|
323
|
+
|
|
324
|
+
// Check for pattern keywords
|
|
325
|
+
for (const query of langQueries) {
|
|
326
|
+
// Match by ID
|
|
327
|
+
if (pattern.includes(query.id)) return query;
|
|
328
|
+
|
|
329
|
+
// Match by keywords in pattern
|
|
330
|
+
switch (query.id) {
|
|
331
|
+
case "empty-catch":
|
|
332
|
+
if (pattern.includes("empty-catch") || pattern.includes("catch {}"))
|
|
333
|
+
return query;
|
|
334
|
+
break;
|
|
335
|
+
case "debugger-statement":
|
|
336
|
+
if (pattern.includes("debugger")) return query;
|
|
337
|
+
break;
|
|
338
|
+
case "await-in-loop":
|
|
339
|
+
if (pattern.includes("await-in-loop") || pattern.includes("await"))
|
|
340
|
+
return query;
|
|
341
|
+
break;
|
|
342
|
+
case "hardcoded-secrets":
|
|
343
|
+
if (
|
|
344
|
+
pattern.includes("hardcoded") ||
|
|
345
|
+
pattern.includes("api_key") ||
|
|
346
|
+
pattern.includes("password")
|
|
347
|
+
)
|
|
348
|
+
return query;
|
|
349
|
+
break;
|
|
350
|
+
case "dangerously-set-inner-html":
|
|
351
|
+
if (pattern.includes("dangerously") || pattern.includes("innerHTML"))
|
|
352
|
+
return query;
|
|
353
|
+
break;
|
|
354
|
+
case "nested-ternary":
|
|
355
|
+
if (pattern.includes("ternary") || pattern.includes("? :"))
|
|
356
|
+
return query;
|
|
357
|
+
break;
|
|
358
|
+
case "no-eval":
|
|
359
|
+
if (pattern.includes("eval") && !pattern.includes("console"))
|
|
360
|
+
return query;
|
|
361
|
+
break;
|
|
362
|
+
case "deep-promise-chain":
|
|
363
|
+
if (pattern.includes(".then") && pattern.includes(".catch"))
|
|
364
|
+
return query;
|
|
365
|
+
break;
|
|
366
|
+
case "console-statement":
|
|
367
|
+
if (pattern.includes("console")) return query;
|
|
368
|
+
break;
|
|
369
|
+
case "long-parameter-list":
|
|
370
|
+
if (pattern.includes("PARAMS")) return query;
|
|
371
|
+
break;
|
|
372
|
+
// Python queries
|
|
373
|
+
case "bare-except":
|
|
374
|
+
if (pattern.includes("bare-except") || pattern.includes("except:"))
|
|
375
|
+
return query;
|
|
376
|
+
break;
|
|
377
|
+
case "mutable-default-arg":
|
|
378
|
+
if (pattern.includes("mutable") || pattern.includes("default"))
|
|
379
|
+
return query;
|
|
380
|
+
break;
|
|
381
|
+
case "wildcard-import":
|
|
382
|
+
if (pattern.includes("wildcard") || pattern.includes("import *"))
|
|
383
|
+
return query;
|
|
384
|
+
break;
|
|
385
|
+
case "eval-exec":
|
|
386
|
+
if (pattern.includes("eval") || pattern.includes("exec"))
|
|
387
|
+
return query;
|
|
388
|
+
break;
|
|
389
|
+
case "is-vs-equals":
|
|
390
|
+
if (pattern.includes("is") || pattern.includes("equals"))
|
|
391
|
+
return query;
|
|
392
|
+
break;
|
|
393
|
+
case "unreachable-except":
|
|
394
|
+
if (pattern.includes("unreachable") || pattern.includes("except"))
|
|
395
|
+
return query;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get all loaded queries
|
|
405
|
+
*/
|
|
406
|
+
getAllQueries(): TreeSitterQuery[] {
|
|
407
|
+
const all: TreeSitterQuery[] = [];
|
|
408
|
+
for (const queries of this.queries.values()) {
|
|
409
|
+
all.push(...queries);
|
|
410
|
+
}
|
|
411
|
+
return all;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Reload queries from disk
|
|
416
|
+
*/
|
|
417
|
+
async reload(): Promise<void> {
|
|
418
|
+
this.queries.clear();
|
|
419
|
+
this.loaded = false;
|
|
420
|
+
await this.loadQueries();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Singleton instance
|
|
425
|
+
export const queryLoader = new TreeSitterQueryLoader();
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Requires: npm install -D type-coverage
|
|
9
9
|
* Docs: https://github.com/plantain-00/type-coverage
|
|
10
10
|
*/
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
12
11
|
import * as path from "node:path";
|
|
12
|
+
import { safeSpawn } from "./safe-spawn.js";
|
|
13
13
|
// --- Client ---
|
|
14
14
|
export class TypeCoverageClient {
|
|
15
15
|
constructor(verbose = false) {
|
|
@@ -21,10 +21,8 @@ export class TypeCoverageClient {
|
|
|
21
21
|
isAvailable() {
|
|
22
22
|
if (this.available !== null)
|
|
23
23
|
return this.available;
|
|
24
|
-
const result =
|
|
25
|
-
encoding: "utf-8",
|
|
24
|
+
const result = safeSpawn("npx", ["type-coverage", "--version"], {
|
|
26
25
|
timeout: 10000,
|
|
27
|
-
shell: true,
|
|
28
26
|
});
|
|
29
27
|
this.available = !result.error && result.status === 0;
|
|
30
28
|
return this.available;
|
|
@@ -45,17 +43,15 @@ export class TypeCoverageClient {
|
|
|
45
43
|
};
|
|
46
44
|
}
|
|
47
45
|
try {
|
|
48
|
-
const result =
|
|
46
|
+
const result = safeSpawn("npx", [
|
|
49
47
|
"type-coverage",
|
|
50
48
|
"--detail",
|
|
51
49
|
"--strict",
|
|
52
50
|
"--ignore-files",
|
|
53
51
|
"**/*.d.ts",
|
|
54
52
|
], {
|
|
55
|
-
encoding: "utf-8",
|
|
56
53
|
timeout: 30000,
|
|
57
54
|
cwd,
|
|
58
|
-
shell: true,
|
|
59
55
|
});
|
|
60
56
|
const output = (result.stdout ?? "") + (result.stderr ?? "");
|
|
61
57
|
return this.parseOutput(output, cwd);
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
13
|
import * as path from "node:path";
|
|
14
|
+
import { safeSpawn } from "./safe-spawn.js";
|
|
14
15
|
|
|
15
16
|
// --- Types ---
|
|
16
17
|
|
|
@@ -43,10 +44,8 @@ export class TypeCoverageClient {
|
|
|
43
44
|
|
|
44
45
|
isAvailable(): boolean {
|
|
45
46
|
if (this.available !== null) return this.available;
|
|
46
|
-
const result =
|
|
47
|
-
encoding: "utf-8",
|
|
47
|
+
const result = safeSpawn("npx", ["type-coverage", "--version"], {
|
|
48
48
|
timeout: 10000,
|
|
49
|
-
shell: true,
|
|
50
49
|
});
|
|
51
50
|
this.available = !result.error && result.status === 0;
|
|
52
51
|
return this.available;
|
|
@@ -69,7 +68,7 @@ export class TypeCoverageClient {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
try {
|
|
72
|
-
const result =
|
|
71
|
+
const result = safeSpawn(
|
|
73
72
|
"npx",
|
|
74
73
|
[
|
|
75
74
|
"type-coverage",
|
|
@@ -79,10 +78,8 @@ export class TypeCoverageClient {
|
|
|
79
78
|
"**/*.d.ts",
|
|
80
79
|
],
|
|
81
80
|
{
|
|
82
|
-
encoding: "utf-8",
|
|
83
81
|
timeout: 30000,
|
|
84
82
|
cwd,
|
|
85
|
-
shell: true,
|
|
86
83
|
},
|
|
87
84
|
);
|
|
88
85
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { createTempFile, setupTestEnvironment } from "./test-utils.js";
|
|
3
|
+
import { TypeScriptClient } from "./typescript-client.js";
|
|
4
|
+
describe("TypeScriptClient - Code Fixes", () => {
|
|
5
|
+
let client;
|
|
6
|
+
let tmpDir;
|
|
7
|
+
let cleanup;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
client = new TypeScriptClient();
|
|
10
|
+
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-codefix-test-"));
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
cleanup();
|
|
14
|
+
});
|
|
15
|
+
describe("getCodeFixes", () => {
|
|
16
|
+
it("should provide fix for missing property on object literal", () => {
|
|
17
|
+
// Real-world case: Missing required property in object literal
|
|
18
|
+
const content = `
|
|
19
|
+
interface Config {
|
|
20
|
+
name: string;
|
|
21
|
+
port: number;
|
|
22
|
+
debug: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const config: Config = {
|
|
26
|
+
name: "my-app",
|
|
27
|
+
port: 3000
|
|
28
|
+
// Missing 'debug' property - TS2345
|
|
29
|
+
};
|
|
30
|
+
`;
|
|
31
|
+
const filePath = createTempFile(tmpDir, "missing-property.ts", content);
|
|
32
|
+
client.addFile(filePath, content);
|
|
33
|
+
const diags = client.getDiagnostics(filePath);
|
|
34
|
+
const missingPropError = diags.find((d) => d.code === 2345 || d.message.includes("missing"));
|
|
35
|
+
if (missingPropError) {
|
|
36
|
+
const line = missingPropError.range.start.line;
|
|
37
|
+
const char = missingPropError.range.start.character;
|
|
38
|
+
const fixes = client.getCodeFixes(filePath, line, char, [
|
|
39
|
+
missingPropError.code,
|
|
40
|
+
]);
|
|
41
|
+
// TypeScript should suggest adding the missing property
|
|
42
|
+
expect(fixes.length).toBeGreaterThan(0);
|
|
43
|
+
const hasAddPropertyFix = fixes.some((f) => f.description.toLowerCase().includes("add") ||
|
|
44
|
+
f.description.toLowerCase().includes("property") ||
|
|
45
|
+
f.description.toLowerCase().includes("declare"));
|
|
46
|
+
expect(hasAddPropertyFix).toBe(true);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
it("should provide fix for missing await in async function", () => {
|
|
50
|
+
// Real-world case: Forgetting await on a Promise-returning function
|
|
51
|
+
const content = `
|
|
52
|
+
async function fetchUser(id: string): Promise<{ name: string }> {
|
|
53
|
+
return { name: "John" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function getUserName(id: string): Promise<string> {
|
|
57
|
+
const user = fetchUser(id); // Missing await
|
|
58
|
+
return user.name; // Type 'Promise<{ name: string; }>' has no property 'name'
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
const filePath = createTempFile(tmpDir, "missing-await.ts", content);
|
|
62
|
+
client.addFile(filePath, content);
|
|
63
|
+
const diags = client.getDiagnostics(filePath);
|
|
64
|
+
// TS2739: Type 'Promise<{ name: string; }>' is missing 'name'
|
|
65
|
+
const propertyError = diags.find((d) => d.code === 2739 || d.message.includes("is missing"));
|
|
66
|
+
if (propertyError) {
|
|
67
|
+
const fixes = client.getAllCodeFixes(filePath);
|
|
68
|
+
// If there's an error, check if we have fixes for it
|
|
69
|
+
const lineFixes = fixes.get(propertyError.range.start.line);
|
|
70
|
+
if (lineFixes) {
|
|
71
|
+
expect(lineFixes.length).toBeGreaterThan(0);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Test passes if we get here - not all TS versions provide fixes for this
|
|
75
|
+
expect(true).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it("should provide fix for incorrect type assignment", () => {
|
|
78
|
+
// Real-world case: String instead of number
|
|
79
|
+
const content = `
|
|
80
|
+
function calculateTotal(price: number, tax: number): number {
|
|
81
|
+
return price + tax;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = calculateTotal("100", 10); // TS2345: Argument of type 'string' is not assignable to parameter of type 'number'
|
|
85
|
+
`;
|
|
86
|
+
const filePath = createTempFile(tmpDir, "type-mismatch.ts", content);
|
|
87
|
+
client.addFile(filePath, content);
|
|
88
|
+
const diags = client.getDiagnostics(filePath);
|
|
89
|
+
const typeError = diags.find((d) => d.code === 2345);
|
|
90
|
+
if (typeError) {
|
|
91
|
+
const line = typeError.range.start.line;
|
|
92
|
+
const char = typeError.range.start.character;
|
|
93
|
+
const fixes = client.getCodeFixes(filePath, line, char, [2345]);
|
|
94
|
+
// TypeScript often suggests fixes for type mismatches
|
|
95
|
+
expect(fixes).toBeDefined();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
it("should collect all fixes via getAllCodeFixes", () => {
|
|
99
|
+
// Multiple errors in one file
|
|
100
|
+
const content = `
|
|
101
|
+
interface Person {
|
|
102
|
+
name: string;
|
|
103
|
+
age: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const person: Person = {
|
|
107
|
+
name: "Alice"
|
|
108
|
+
// Missing age
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function greet(p: Person): string {
|
|
112
|
+
return "Hello " + p.name;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
greet({ name: "Bob" }); // Missing age in argument
|
|
116
|
+
`;
|
|
117
|
+
const filePath = createTempFile(tmpDir, "multiple-errors.ts", content);
|
|
118
|
+
client.addFile(filePath, content);
|
|
119
|
+
const allFixes = client.getAllCodeFixes(filePath);
|
|
120
|
+
// Should have fixes mapped by line number
|
|
121
|
+
expect(allFixes).toBeInstanceOf(Map);
|
|
122
|
+
// Each fix entry should have a description and changes
|
|
123
|
+
for (const [line, fixes] of allFixes.entries()) {
|
|
124
|
+
expect(typeof line).toBe("number");
|
|
125
|
+
expect(fixes.length).toBeGreaterThan(0);
|
|
126
|
+
for (const fix of fixes) {
|
|
127
|
+
expect(fix.description).toBeTruthy();
|
|
128
|
+
expect(fix.changes).toBeDefined();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe("Integration with diagnostic messages", () => {
|
|
134
|
+
it("should include fix suggestions in getAllCodeFixes output", () => {
|
|
135
|
+
const content = `
|
|
136
|
+
class User {
|
|
137
|
+
constructor(public name: string) {}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const user = new User(); // TS2554: Expected 1 arguments, but got 0
|
|
141
|
+
`;
|
|
142
|
+
const filePath = createTempFile(tmpDir, "constructor-args.ts", content);
|
|
143
|
+
client.addFile(filePath, content);
|
|
144
|
+
const diags = client.getDiagnostics(filePath);
|
|
145
|
+
const argError = diags.find((d) => d.code === 2554);
|
|
146
|
+
if (argError) {
|
|
147
|
+
const fixes = client.getAllCodeFixes(filePath);
|
|
148
|
+
const lineFixes = fixes.get(argError.range.start.line);
|
|
149
|
+
if (lineFixes && lineFixes.length > 0) {
|
|
150
|
+
// The runner would append this to the message
|
|
151
|
+
const suggestion = lineFixes[0].description;
|
|
152
|
+
expect(suggestion).toBeTruthy();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|