pi-lens 2.0.37 → 2.0.39
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 +16 -0
- package/README.md +1 -1
- package/clients/architect-client.js +50 -20
- package/clients/architect-client.ts +61 -22
- package/clients/ast-grep-client.js +32 -127
- package/clients/ast-grep-client.test.js +2 -14
- package/clients/ast-grep-client.test.ts +2 -16
- package/clients/ast-grep-client.ts +34 -150
- package/clients/auto-loop.js +117 -0
- package/clients/auto-loop.ts +171 -0
- package/clients/biome-client.js +25 -22
- package/clients/biome-client.ts +28 -25
- package/clients/complexity-client.js +111 -74
- package/clients/complexity-client.ts +149 -105
- package/clients/dependency-checker.js +16 -30
- package/clients/dependency-checker.ts +15 -35
- package/clients/fix-scanners.js +195 -0
- package/clients/fix-scanners.ts +297 -0
- package/clients/interviewer-templates.js +75 -0
- package/clients/interviewer-templates.ts +90 -0
- package/clients/interviewer.js +73 -101
- package/clients/interviewer.ts +195 -140
- package/clients/knip-client.js +12 -4
- package/clients/knip-client.ts +21 -16
- package/clients/metrics-history.js +215 -0
- package/clients/metrics-history.ts +300 -0
- package/clients/scan-architectural-debt.js +62 -72
- package/clients/scan-architectural-debt.ts +79 -64
- package/clients/scan-utils.js +98 -0
- package/clients/scan-utils.ts +112 -0
- package/clients/sg-runner.js +138 -0
- package/clients/sg-runner.ts +168 -0
- package/clients/subprocess-client.js +0 -37
- package/clients/subprocess-client.ts +0 -60
- package/clients/ts-service.ts +1 -7
- package/clients/type-safety-client.js +3 -8
- package/clients/type-safety-client.ts +10 -14
- package/clients/typescript-client.js +55 -56
- package/clients/typescript-client.ts +94 -52
- package/default-architect.yaml +87 -0
- package/index.ts +143 -1165
- package/package.json +2 -1
- package/rules/ast-grep-rules/rules/large-class.yml +6 -2
- package/rules/ast-grep-rules/rules/long-method.yml +1 -1
- package/rules/ast-grep-rules/rules/no-single-char-var.yml +2 -2
- package/rules/ast-grep-rules/rules/switch-without-default.yml +0 -12
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SgRunner - encapsulates ast-grep subprocess management
|
|
3
|
+
*
|
|
4
|
+
* Extracted from AstGrepClient to simplify the main client.
|
|
5
|
+
* Handles: spawn, spawnSync, temp dir management, JSON parsing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
|
|
13
|
+
export interface SgMatch {
|
|
14
|
+
file: string;
|
|
15
|
+
range: {
|
|
16
|
+
start: { line: number; column: number };
|
|
17
|
+
end: { line: number; column: number };
|
|
18
|
+
};
|
|
19
|
+
text: string;
|
|
20
|
+
replacement?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SgResult {
|
|
24
|
+
matches: SgMatch[];
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class SgRunner {
|
|
29
|
+
private log: (msg: string) => void;
|
|
30
|
+
|
|
31
|
+
constructor(verbose = false) {
|
|
32
|
+
this.log = verbose ? (msg: string) => console.error(`[sg-runner] ${msg}`) : () => {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if ast-grep CLI is available
|
|
37
|
+
*/
|
|
38
|
+
isAvailable(): boolean {
|
|
39
|
+
const result = spawnSync("npx", ["sg", "--version"], {
|
|
40
|
+
encoding: "utf-8",
|
|
41
|
+
timeout: 10000,
|
|
42
|
+
shell: true,
|
|
43
|
+
});
|
|
44
|
+
return !result.error && result.status === 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Run ast-grep asynchronously, return parsed matches
|
|
49
|
+
*/
|
|
50
|
+
async exec(args: string[]): Promise<SgResult> {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const proc = spawn("npx", ["sg", ...args], {
|
|
53
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
54
|
+
shell: true,
|
|
55
|
+
});
|
|
56
|
+
let stdout = "";
|
|
57
|
+
let stderr = "";
|
|
58
|
+
|
|
59
|
+
proc.stdout.on("data", (data: Buffer) => (stdout += data.toString()));
|
|
60
|
+
proc.stderr.on("data", (data: Buffer) => (stderr += data.toString()));
|
|
61
|
+
|
|
62
|
+
proc.on("error", (err: Error) => {
|
|
63
|
+
if (err.message.includes("ENOENT")) {
|
|
64
|
+
resolve({ matches: [], error: "ast-grep CLI not found. Install: npm i -D @ast-grep/cli" });
|
|
65
|
+
} else {
|
|
66
|
+
resolve({ matches: [], error: err.message });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
proc.on("close", (code: number | null) => {
|
|
71
|
+
if (code !== 0 && !stdout.trim()) {
|
|
72
|
+
resolve({
|
|
73
|
+
matches: [],
|
|
74
|
+
error: stderr.includes("No files found") ? undefined : stderr.trim() || `Exit code ${code}`,
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!stdout.trim()) {
|
|
79
|
+
resolve({ matches: [] });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(stdout);
|
|
84
|
+
const matches = Array.isArray(parsed) ? parsed : [parsed];
|
|
85
|
+
resolve({ matches });
|
|
86
|
+
} catch {
|
|
87
|
+
resolve({ matches: [], error: "Failed to parse output" });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run ast-grep synchronously (for simple scans)
|
|
95
|
+
*/
|
|
96
|
+
execSync(args: string[]): { output: string; error?: string } {
|
|
97
|
+
const result = spawnSync("npx", ["sg", ...args], {
|
|
98
|
+
encoding: "utf-8",
|
|
99
|
+
timeout: 30000,
|
|
100
|
+
shell: true,
|
|
101
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (result.error) {
|
|
105
|
+
return { output: "", error: result.error.message };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const output = result.stdout || result.stderr || "";
|
|
109
|
+
return { output };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Run a temporary rule scan (creates temp dir with rule file)
|
|
114
|
+
*/
|
|
115
|
+
tempScan(dir: string, ruleId: string, ruleYaml: string, timeout = 30000): SgMatch[] {
|
|
116
|
+
const tmpDir = os.tmpdir();
|
|
117
|
+
const ts = Date.now();
|
|
118
|
+
const sessionDir = path.join(tmpDir, `pi-lens-temp-${ruleId}-${ts}`);
|
|
119
|
+
const rulesSubdir = path.join(sessionDir, "rules");
|
|
120
|
+
const ruleFile = path.join(rulesSubdir, `${ruleId}.yml`);
|
|
121
|
+
const configFile = path.join(sessionDir, ".sgconfig.yml");
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
fs.mkdirSync(rulesSubdir, { recursive: true });
|
|
125
|
+
fs.writeFileSync(configFile, `ruleDirs:\n - ./rules\n`);
|
|
126
|
+
fs.writeFileSync(ruleFile, ruleYaml);
|
|
127
|
+
|
|
128
|
+
const result = spawnSync(
|
|
129
|
+
"npx",
|
|
130
|
+
["sg", "scan", "--config", configFile, "--json", dir],
|
|
131
|
+
{ encoding: "utf-8", timeout, shell: true },
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const output = result.stdout || result.stderr || "";
|
|
135
|
+
if (!output.trim()) return [];
|
|
136
|
+
|
|
137
|
+
const items = JSON.parse(output);
|
|
138
|
+
return Array.isArray(items) ? items : [items];
|
|
139
|
+
} catch {
|
|
140
|
+
return [];
|
|
141
|
+
} finally {
|
|
142
|
+
try {
|
|
143
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
144
|
+
} catch (err) {
|
|
145
|
+
this.log(`Cleanup failed: ${(err as Error).message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Format matches for display
|
|
152
|
+
*/
|
|
153
|
+
formatMatches(matches: SgMatch[], isDryRun = false, maxItems = 50): string {
|
|
154
|
+
if (matches.length === 0) return "No matches found";
|
|
155
|
+
const shown = matches.slice(0, maxItems);
|
|
156
|
+
const lines = shown.map((m) => {
|
|
157
|
+
const loc = `${m.file}:${m.range.start.line + 1}:${m.range.start.column + 1}`;
|
|
158
|
+
const text = m.text.length > 100 ? `${m.text.slice(0, 100)}...` : m.text;
|
|
159
|
+
return isDryRun && m.replacement
|
|
160
|
+
? `${loc}\n - ${text}\n + ${m.replacement}`
|
|
161
|
+
: `${loc}: ${text}`;
|
|
162
|
+
});
|
|
163
|
+
if (matches.length > maxItems) {
|
|
164
|
+
lines.unshift(`Found ${matches.length} matches (showing first ${maxItems}):`);
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for CLI tool clients that communicate via subprocess.
|
|
3
|
-
*
|
|
4
|
-
* Provides common patterns for:
|
|
5
|
-
* - Availability checking (cached)
|
|
6
|
-
* - File type detection
|
|
7
|
-
* - Running CLI commands
|
|
8
|
-
* - Logging
|
|
9
|
-
*
|
|
10
|
-
* Subclasses implement:
|
|
11
|
-
* - getToolName(): The CLI tool name
|
|
12
|
-
* - getCheckCommand(): Command to check availability
|
|
13
|
-
* - isSupportedFile(): File extensions the tool handles
|
|
14
|
-
* - parseOutput(): Parse CLI output into diagnostics
|
|
15
|
-
*/
|
|
16
1
|
import { spawnSync } from "node:child_process";
|
|
17
|
-
import * as fs from "node:fs";
|
|
18
2
|
import * as path from "node:path";
|
|
19
3
|
export class SubprocessClient {
|
|
20
4
|
constructor(verbose = false) {
|
|
@@ -24,9 +8,6 @@ export class SubprocessClient {
|
|
|
24
8
|
? (msg) => console.error(`[${this.toolName}] ${msg}`)
|
|
25
9
|
: () => { };
|
|
26
10
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Check if the CLI tool is available (cached)
|
|
29
|
-
*/
|
|
30
11
|
isAvailable() {
|
|
31
12
|
if (this.available !== null)
|
|
32
13
|
return this.available;
|
|
@@ -51,16 +32,10 @@ export class SubprocessClient {
|
|
|
51
32
|
}
|
|
52
33
|
return this.available;
|
|
53
34
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Check if a file is supported by this tool
|
|
56
|
-
*/
|
|
57
35
|
isSupportedFile(filePath) {
|
|
58
36
|
const ext = path.extname(filePath).toLowerCase();
|
|
59
37
|
return this.getSupportedExtensions().includes(ext);
|
|
60
38
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Run a command and return the result
|
|
63
|
-
*/
|
|
64
39
|
runCommand(cmd, options = {}) {
|
|
65
40
|
const { cwd, timeout = 15000, input } = options;
|
|
66
41
|
try {
|
|
@@ -86,16 +61,4 @@ export class SubprocessClient {
|
|
|
86
61
|
};
|
|
87
62
|
}
|
|
88
63
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Resolve a file path to absolute
|
|
91
|
-
*/
|
|
92
|
-
resolvePath(filePath) {
|
|
93
|
-
return path.resolve(filePath);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Check if a file exists
|
|
97
|
-
*/
|
|
98
|
-
fileExists(filePath) {
|
|
99
|
-
return fs.existsSync(filePath);
|
|
100
|
-
}
|
|
101
64
|
}
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for CLI tool clients that communicate via subprocess.
|
|
3
|
-
*
|
|
4
|
-
* Provides common patterns for:
|
|
5
|
-
* - Availability checking (cached)
|
|
6
|
-
* - File type detection
|
|
7
|
-
* - Running CLI commands
|
|
8
|
-
* - Logging
|
|
9
|
-
*
|
|
10
|
-
* Subclasses implement:
|
|
11
|
-
* - getToolName(): The CLI tool name
|
|
12
|
-
* - getCheckCommand(): Command to check availability
|
|
13
|
-
* - isSupportedFile(): File extensions the tool handles
|
|
14
|
-
* - parseOutput(): Parse CLI output into diagnostics
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
1
|
import { spawnSync } from "node:child_process";
|
|
18
|
-
import * as fs from "node:fs";
|
|
19
2
|
import * as path from "node:path";
|
|
20
3
|
|
|
21
4
|
export interface Diagnostic {
|
|
@@ -42,31 +25,11 @@ export abstract class SubprocessClient<T extends Diagnostic> {
|
|
|
42
25
|
: () => {};
|
|
43
26
|
}
|
|
44
27
|
|
|
45
|
-
/**
|
|
46
|
-
* The name of the CLI tool (used in log messages)
|
|
47
|
-
*/
|
|
48
28
|
protected abstract getToolName(): string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Command and args to check if the tool is available
|
|
52
|
-
* e.g., ["ruff", "--version"] or ["npx", "@biomejs/biome", "--version"]
|
|
53
|
-
*/
|
|
54
29
|
protected abstract getCheckCommand(): string[];
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* File extensions this tool supports (with dots)
|
|
58
|
-
* e.g., [".py"] or [".ts", ".tsx", ".js", ".jsx"]
|
|
59
|
-
*/
|
|
60
30
|
protected abstract getSupportedExtensions(): string[];
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parse CLI output into diagnostics
|
|
64
|
-
*/
|
|
65
31
|
protected abstract parseOutput(output: string, filePath: string): T[];
|
|
66
32
|
|
|
67
|
-
/**
|
|
68
|
-
* Check if the CLI tool is available (cached)
|
|
69
|
-
*/
|
|
70
33
|
isAvailable(): boolean {
|
|
71
34
|
if (this.available !== null) return this.available;
|
|
72
35
|
|
|
@@ -92,22 +55,13 @@ export abstract class SubprocessClient<T extends Diagnostic> {
|
|
|
92
55
|
return this.available;
|
|
93
56
|
}
|
|
94
57
|
|
|
95
|
-
/**
|
|
96
|
-
* Check if a file is supported by this tool
|
|
97
|
-
*/
|
|
98
58
|
isSupportedFile(filePath: string): boolean {
|
|
99
59
|
const ext = path.extname(filePath).toLowerCase();
|
|
100
60
|
return this.getSupportedExtensions().includes(ext);
|
|
101
61
|
}
|
|
102
62
|
|
|
103
|
-
/**
|
|
104
|
-
* Run the tool on a file and return diagnostics
|
|
105
|
-
*/
|
|
106
63
|
abstract checkFile(filePath: string): T[];
|
|
107
64
|
|
|
108
|
-
/**
|
|
109
|
-
* Run a command and return the result
|
|
110
|
-
*/
|
|
111
65
|
protected runCommand(
|
|
112
66
|
cmd: string[],
|
|
113
67
|
options: {
|
|
@@ -142,18 +96,4 @@ export abstract class SubprocessClient<T extends Diagnostic> {
|
|
|
142
96
|
} as unknown as ReturnType<typeof spawnSync>;
|
|
143
97
|
}
|
|
144
98
|
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Resolve a file path to absolute
|
|
148
|
-
*/
|
|
149
|
-
protected resolvePath(filePath: string): string {
|
|
150
|
-
return path.resolve(filePath);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if a file exists
|
|
155
|
-
*/
|
|
156
|
-
protected fileExists(filePath: string): boolean {
|
|
157
|
-
return fs.existsSync(filePath);
|
|
158
|
-
}
|
|
159
99
|
}
|
package/clients/ts-service.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
|
-
import * as path from "node:path";
|
|
10
9
|
import * as ts from "typescript";
|
|
11
10
|
|
|
12
11
|
export class TypeScriptService {
|
|
@@ -120,12 +119,7 @@ export class TypeScriptService {
|
|
|
120
119
|
return null;
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
|
-
return ts.createSourceFile(
|
|
124
|
-
filePath,
|
|
125
|
-
content,
|
|
126
|
-
ts.ScriptTarget.Latest,
|
|
127
|
-
true,
|
|
128
|
-
);
|
|
122
|
+
return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
129
123
|
}
|
|
130
124
|
|
|
131
125
|
/**
|
|
@@ -100,7 +100,8 @@ export class TypeSafetyClient {
|
|
|
100
100
|
// Find missing cases
|
|
101
101
|
const missingCases = literalValues.filter((v) => !coveredCases.has(v));
|
|
102
102
|
if (missingCases.length > 0 && !hasDefault) {
|
|
103
|
-
const line = programSourceFile.getLineAndCharacterOfPosition(node.getStart())
|
|
103
|
+
const line = programSourceFile.getLineAndCharacterOfPosition(node.getStart())
|
|
104
|
+
.line + 1;
|
|
104
105
|
const exprText = node.expression.getText(programSourceFile);
|
|
105
106
|
const typeStr = missingCases.map((c) => `'${c}'`).join(", ");
|
|
106
107
|
issues.push({
|
|
@@ -134,10 +135,4 @@ export class TypeSafetyClient {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
// --- Singleton ---
|
|
137
|
-
|
|
138
|
-
export function getTypeSafetyClient(verbose = false) {
|
|
139
|
-
if (!instance) {
|
|
140
|
-
instance = new TypeSafetyClient(verbose);
|
|
141
|
-
}
|
|
142
|
-
return instance;
|
|
143
|
-
}
|
|
138
|
+
const _instance = null;
|
|
@@ -90,7 +90,9 @@ export class TypeSafetyClient {
|
|
|
90
90
|
|
|
91
91
|
// Use the source file from the program (has proper type information)
|
|
92
92
|
const tsService = getTypeScriptService();
|
|
93
|
-
const programSourceFile = tsService.getSourceFileFromProgram(
|
|
93
|
+
const programSourceFile = tsService.getSourceFileFromProgram(
|
|
94
|
+
sourceFile.fileName,
|
|
95
|
+
);
|
|
94
96
|
if (!programSourceFile) return;
|
|
95
97
|
|
|
96
98
|
const visit = (node: ts.Node) => {
|
|
@@ -103,7 +105,9 @@ export class TypeSafetyClient {
|
|
|
103
105
|
|
|
104
106
|
// Get all literal values from the union
|
|
105
107
|
const literalValues = unionTypes
|
|
106
|
-
.filter(
|
|
108
|
+
.filter(
|
|
109
|
+
(t) => t.isLiteral() || t.flags & ts.TypeFlags.BooleanLiteral,
|
|
110
|
+
)
|
|
107
111
|
.map((t) => {
|
|
108
112
|
if (t.isLiteral()) {
|
|
109
113
|
return String(t.value);
|
|
@@ -143,9 +147,9 @@ export class TypeSafetyClient {
|
|
|
143
147
|
);
|
|
144
148
|
|
|
145
149
|
if (missingCases.length > 0 && !hasDefault) {
|
|
146
|
-
const line =
|
|
147
|
-
node.getStart()
|
|
148
|
-
|
|
150
|
+
const line =
|
|
151
|
+
programSourceFile.getLineAndCharacterOfPosition(node.getStart())
|
|
152
|
+
.line + 1;
|
|
149
153
|
|
|
150
154
|
const exprText = node.expression.getText(programSourceFile);
|
|
151
155
|
const typeStr = missingCases.map((c) => `'${c}'`).join(", ");
|
|
@@ -184,16 +188,8 @@ export class TypeSafetyClient {
|
|
|
184
188
|
}
|
|
185
189
|
return checker;
|
|
186
190
|
}
|
|
187
|
-
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
// --- Singleton ---
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
export function getTypeSafetyClient(verbose = false): TypeSafetyClient {
|
|
195
|
-
if (!instance) {
|
|
196
|
-
instance = new TypeSafetyClient(verbose);
|
|
197
|
-
}
|
|
198
|
-
return instance;
|
|
199
|
-
}
|
|
195
|
+
const _instance: TypeSafetyClient | null = null;
|
|
@@ -264,58 +264,61 @@ export class TypeScriptClient {
|
|
|
264
264
|
ls: this.languageService,
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
|
-
|
|
268
|
-
* Go to definition
|
|
269
|
-
*/
|
|
270
|
-
getDefinition(filePath, line, character) {
|
|
267
|
+
withPosition(filePath, line, character, cb) {
|
|
271
268
|
const resolved = this.resolvePosition(filePath, line, character);
|
|
272
269
|
if (!resolved)
|
|
273
270
|
return [];
|
|
274
271
|
const { normalized, position, ls } = resolved;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
272
|
+
return cb(normalized, position, ls) ?? [];
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Go to definition
|
|
276
|
+
*/
|
|
277
|
+
getDefinition(filePath, line, character) {
|
|
278
|
+
return this.withPosition(filePath, line, character, (normalized, position, ls) => {
|
|
279
|
+
const definitions = ls.getDefinitionAtPosition(normalized, position);
|
|
280
|
+
if (!definitions)
|
|
281
|
+
return undefined;
|
|
282
|
+
return definitions.map((def) => {
|
|
283
|
+
if (def.textSpan) {
|
|
284
|
+
const defFile = def.fileName || normalized;
|
|
285
|
+
const defContent = this.fileContents.get(defFile) || "";
|
|
286
|
+
if (defContent) {
|
|
287
|
+
const lines = defContent
|
|
288
|
+
.substring(0, def.textSpan.start)
|
|
289
|
+
.split("\n");
|
|
290
|
+
return {
|
|
291
|
+
file: defFile,
|
|
292
|
+
line: lines.length - 1,
|
|
293
|
+
character: lines[lines.length - 1].length,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
289
296
|
}
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
return { file: def.fileName, line: 0, character: 0 };
|
|
298
|
+
});
|
|
292
299
|
});
|
|
293
300
|
}
|
|
294
301
|
/**
|
|
295
302
|
* Get type definition
|
|
296
303
|
*/
|
|
297
304
|
getTypeDefinition(filePath, line, character) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return [];
|
|
305
|
-
return this.toLocations(defs, normalized);
|
|
305
|
+
return this.withPosition(filePath, line, character, (normalized, position, ls) => {
|
|
306
|
+
const defs = ls.getTypeDefinitionAtPosition(normalized, position);
|
|
307
|
+
if (!defs)
|
|
308
|
+
return undefined;
|
|
309
|
+
return this.toLocations(defs, normalized);
|
|
310
|
+
});
|
|
306
311
|
}
|
|
307
312
|
/**
|
|
308
313
|
* Find references
|
|
309
314
|
*/
|
|
310
315
|
getReferences(filePath, line, character) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return [];
|
|
318
|
-
return this.toLocations(references);
|
|
316
|
+
return this.withPosition(filePath, line, character, (normalized, position, ls) => {
|
|
317
|
+
const references = ls.getReferencesAtPosition(normalized, position);
|
|
318
|
+
if (!references)
|
|
319
|
+
return undefined;
|
|
320
|
+
return this.toLocations(references);
|
|
321
|
+
});
|
|
319
322
|
}
|
|
320
323
|
/** Map TS definition/reference entries to Location objects. */
|
|
321
324
|
toLocations(entries, fallbackFile) {
|
|
@@ -369,31 +372,27 @@ export class TypeScriptClient {
|
|
|
369
372
|
* Get completions at a position
|
|
370
373
|
*/
|
|
371
374
|
getCompletions(filePath, line, character) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
sortText: entry.sortText,
|
|
383
|
-
}));
|
|
375
|
+
return this.withPosition(filePath, line, character, (normalized, position, ls) => {
|
|
376
|
+
const completions = ls.getCompletionsAtPosition(normalized, position, {});
|
|
377
|
+
if (!completions)
|
|
378
|
+
return undefined;
|
|
379
|
+
return completions.entries.slice(0, 50).map((entry) => ({
|
|
380
|
+
name: entry.name,
|
|
381
|
+
kind: this.completionKind(entry.kind),
|
|
382
|
+
sortText: entry.sortText,
|
|
383
|
+
}));
|
|
384
|
+
});
|
|
384
385
|
}
|
|
385
386
|
/**
|
|
386
387
|
* Go to implementation
|
|
387
388
|
*/
|
|
388
389
|
getImplementation(filePath, line, character) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return [];
|
|
396
|
-
return this.toLocations(implementations);
|
|
390
|
+
return this.withPosition(filePath, line, character, (normalized, position, ls) => {
|
|
391
|
+
const implementations = ls.getImplementationAtPosition(normalized, position);
|
|
392
|
+
if (!implementations)
|
|
393
|
+
return undefined;
|
|
394
|
+
return this.toLocations(implementations);
|
|
395
|
+
});
|
|
397
396
|
}
|
|
398
397
|
/**
|
|
399
398
|
* Get folding ranges
|