carrick 0.3.117 → 0.3.118
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.118",
|
|
4
4
|
"carrickCache": {
|
|
5
5
|
"cache_version": 22,
|
|
6
6
|
"intent_cache_version": 2
|
|
@@ -113,11 +113,11 @@
|
|
|
113
113
|
"zod": "^3.23.0"
|
|
114
114
|
},
|
|
115
115
|
"optionalDependencies": {
|
|
116
|
-
"@carrick-tools/cli-darwin-arm64": "0.3.
|
|
117
|
-
"@carrick-tools/cli-darwin-x64": "0.3.
|
|
118
|
-
"@carrick-tools/cli-linux-arm64": "0.3.
|
|
119
|
-
"@carrick-tools/cli-linux-x64": "0.3.
|
|
120
|
-
"@carrick-tools/cli-win32-x64": "0.3.
|
|
116
|
+
"@carrick-tools/cli-darwin-arm64": "0.3.118",
|
|
117
|
+
"@carrick-tools/cli-darwin-x64": "0.3.118",
|
|
118
|
+
"@carrick-tools/cli-linux-arm64": "0.3.118",
|
|
119
|
+
"@carrick-tools/cli-linux-x64": "0.3.118",
|
|
120
|
+
"@carrick-tools/cli-win32-x64": "0.3.118"
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
123
|
"@types/node": "^24.13.3"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carrick",
|
|
3
3
|
"description": "Claude Code plugin for Carrick, which indexes TypeScript codebases across service and repository boundaries. After each edit it adds the file's routes, calls, and cross-service type mismatches to the session, and it registers Carrick's language server. It pairs with the Carrick MCP server, which lets agents search functions by intent rather than name.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.118",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Carrick",
|
|
7
7
|
"email": "hello@carrick.tools"
|
|
@@ -2896,16 +2896,21 @@ class DeclarationReader {
|
|
|
2896
2896
|
}
|
|
2897
2897
|
/**
|
|
2898
2898
|
* What a factory builds says nothing: the type itself, or any branch of a
|
|
2899
|
-
* conditional return type, does.
|
|
2899
|
+
* conditional return type, does. A conditional whose branches lead back to
|
|
2900
|
+
* itself (a recursive alias) is read once: `seen` holds the conditionals
|
|
2901
|
+
* on the way down, and one met again says nothing new.
|
|
2900
2902
|
*/
|
|
2901
|
-
returnSaysNothing(type) {
|
|
2903
|
+
returnSaysNothing(type, seen = new Set()) {
|
|
2902
2904
|
if (this.saysNothing(type))
|
|
2903
2905
|
return true;
|
|
2904
2906
|
return this.parts(type).some(part => {
|
|
2905
2907
|
if (!(part.flags & ts.TypeFlags.Conditional))
|
|
2906
2908
|
return false;
|
|
2907
2909
|
const node = part.root.node;
|
|
2908
|
-
|
|
2910
|
+
if (seen.has(node))
|
|
2911
|
+
return false;
|
|
2912
|
+
seen.add(node);
|
|
2913
|
+
return [node.trueType, node.falseType].some(branch => this.returnSaysNothing(this.checker.getTypeFromTypeNode(branch), seen));
|
|
2909
2914
|
});
|
|
2910
2915
|
}
|
|
2911
2916
|
/** `any` (an unresolved type included) or `unknown`. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-position compiler rows for a service (carrick#2405, move 2 of the fact
|
|
3
3
|
* layer). Record-only: nothing in a scan reads the file yet.
|
|
4
4
|
*
|
|
5
|
-
* For every source file the scanner sends, one row for each position of
|
|
5
|
+
* For every source file the scanner sends, one row for each position of six
|
|
6
6
|
* kinds, each holding what the compiler knows there: the symbol, the
|
|
7
7
|
* declaration it is attributed to (file and line), the package that declares
|
|
8
8
|
* it and the type text.
|
|
@@ -13,11 +13,22 @@
|
|
|
13
13
|
* | `member` | property or element access | the name or the argument | the access |
|
|
14
14
|
* | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
|
|
15
15
|
* | `ident` | an identifier in expression position | itself | itself |
|
|
16
|
+
* | `impl` | a `call` or `member` whose declaration has no body | an implementation of it | nothing |
|
|
17
|
+
* | `jsx` | a JSX opening or self-closing element's tag | the tag's name | the tag |
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* An `impl` row (version 2, carrick#2430) follows the `call` or `member` row
|
|
20
|
+
* it belongs to and has its span: one per declaration that go-to-implementation
|
|
21
|
+
* names for that row's declaration, when that declaration is an interface's
|
|
22
|
+
* or type literal's method, a function-typed property signature, an abstract
|
|
23
|
+
* member, or a function-typed class property or parameter with no
|
|
24
|
+
* initializer. Its `decl_*` name the implementing declaration. Past
|
|
25
|
+
* {@link IMPL_CAP} the row is one, with {@link FLAG_TOO_MANY} and nothing else.
|
|
26
|
+
*
|
|
27
|
+
* A row's key is `(start, end, kind)`, except that several `impl` rows share
|
|
28
|
+
* one: TypeScript `getStart()`/`getEnd()` of the node, in UTF-16 code units
|
|
29
|
+
* from the start of the file, which is what the scanner's `sidecar_position`
|
|
30
|
+
* gives for an SWC span. Every path written is relative to the scanned repo's
|
|
31
|
+
* root, with `/` separators.
|
|
21
32
|
*
|
|
22
33
|
* Each file is read in the program of the project that owns it
|
|
23
34
|
* (`ProjectLoader.projectKeyFor`, carrick#1604), one owner's program at a
|
|
@@ -33,7 +44,7 @@ import { ts } from 'ts-morph';
|
|
|
33
44
|
import type { ProjectLoader } from './project-loader.js';
|
|
34
45
|
import type { PositionRowsCounts } from './types.js';
|
|
35
46
|
/** The format's version, written in the header. */
|
|
36
|
-
export declare const POSITION_ROWS_VERSION =
|
|
47
|
+
export declare const POSITION_ROWS_VERSION = 2;
|
|
37
48
|
/** The type is `any`, declared or inferred. Never set with the error type. */
|
|
38
49
|
export declare const FLAG_ANY = 1;
|
|
39
50
|
/** The type is the compiler's error type: something did not resolve. */
|
|
@@ -42,7 +53,13 @@ export declare const FLAG_ERROR_TYPE = 2;
|
|
|
42
53
|
export declare const FLAG_CHECKER_THREW = 4;
|
|
43
54
|
/** The declaration is outside the checkout, not the default library and in no installed package. */
|
|
44
55
|
export declare const FLAG_DECLARATION_UNNAMED = 8;
|
|
45
|
-
|
|
56
|
+
/** An `impl` row standing for more implementations than {@link IMPL_CAP}; it names none. */
|
|
57
|
+
export declare const FLAG_TOO_MANY = 16;
|
|
58
|
+
/** The most implementations one declaration's `impl` rows list. */
|
|
59
|
+
export declare const IMPL_CAP = 16;
|
|
60
|
+
/** The kinds {@link kindOf} gives a node; `impl` and `jsx` rows are written beside them. */
|
|
61
|
+
export type NodeKind = 'call' | 'member' | 'compare' | 'ident';
|
|
62
|
+
export type PositionKind = NodeKind | 'impl' | 'jsx';
|
|
46
63
|
/** `[kind, start, end, symbol, decl_file, decl_line, decl_package, type_text, flags]` */
|
|
47
64
|
export type PositionRow = [
|
|
48
65
|
PositionKind,
|
|
@@ -75,4 +92,4 @@ export interface PositionRowsOptions {
|
|
|
75
92
|
*/
|
|
76
93
|
export declare function writePositionRows(options: PositionRowsOptions): PositionRowsCounts;
|
|
77
94
|
/** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
|
|
78
|
-
export declare function kindOf(node: ts.Node):
|
|
95
|
+
export declare function kindOf(node: ts.Node): NodeKind | undefined;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-position compiler rows for a service (carrick#2405, move 2 of the fact
|
|
3
3
|
* layer). Record-only: nothing in a scan reads the file yet.
|
|
4
4
|
*
|
|
5
|
-
* For every source file the scanner sends, one row for each position of
|
|
5
|
+
* For every source file the scanner sends, one row for each position of six
|
|
6
6
|
* kinds, each holding what the compiler knows there: the symbol, the
|
|
7
7
|
* declaration it is attributed to (file and line), the package that declares
|
|
8
8
|
* it and the type text.
|
|
@@ -13,11 +13,22 @@
|
|
|
13
13
|
* | `member` | property or element access | the name or the argument | the access |
|
|
14
14
|
* | `compare` | `=== !== == != < > <= >= instanceof in` | the left operand | the left operand |
|
|
15
15
|
* | `ident` | an identifier in expression position | itself | itself |
|
|
16
|
+
* | `impl` | a `call` or `member` whose declaration has no body | an implementation of it | nothing |
|
|
17
|
+
* | `jsx` | a JSX opening or self-closing element's tag | the tag's name | the tag |
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* An `impl` row (version 2, carrick#2430) follows the `call` or `member` row
|
|
20
|
+
* it belongs to and has its span: one per declaration that go-to-implementation
|
|
21
|
+
* names for that row's declaration, when that declaration is an interface's
|
|
22
|
+
* or type literal's method, a function-typed property signature, an abstract
|
|
23
|
+
* member, or a function-typed class property or parameter with no
|
|
24
|
+
* initializer. Its `decl_*` name the implementing declaration. Past
|
|
25
|
+
* {@link IMPL_CAP} the row is one, with {@link FLAG_TOO_MANY} and nothing else.
|
|
26
|
+
*
|
|
27
|
+
* A row's key is `(start, end, kind)`, except that several `impl` rows share
|
|
28
|
+
* one: TypeScript `getStart()`/`getEnd()` of the node, in UTF-16 code units
|
|
29
|
+
* from the start of the file, which is what the scanner's `sidecar_position`
|
|
30
|
+
* gives for an SWC span. Every path written is relative to the scanned repo's
|
|
31
|
+
* root, with `/` separators.
|
|
21
32
|
*
|
|
22
33
|
* Each file is read in the program of the project that owns it
|
|
23
34
|
* (`ProjectLoader.projectKeyFor`, carrick#1604), one owner's program at a
|
|
@@ -38,7 +49,7 @@ import { WriteGuard, installedPackageDirectory, isDeclarationFileName, moduleExp
|
|
|
38
49
|
import { attributedDeclaration, installedPackageOf } from './declaration-attribution.js';
|
|
39
50
|
import { typeToStringAsDeclared } from './print-type.js';
|
|
40
51
|
/** The format's version, written in the header. */
|
|
41
|
-
export const POSITION_ROWS_VERSION =
|
|
52
|
+
export const POSITION_ROWS_VERSION = 2;
|
|
42
53
|
/** The type is `any`, declared or inferred. Never set with the error type. */
|
|
43
54
|
export const FLAG_ANY = 1;
|
|
44
55
|
/** The type is the compiler's error type: something did not resolve. */
|
|
@@ -47,6 +58,10 @@ export const FLAG_ERROR_TYPE = 2;
|
|
|
47
58
|
export const FLAG_CHECKER_THREW = 4;
|
|
48
59
|
/** The declaration is outside the checkout, not the default library and in no installed package. */
|
|
49
60
|
export const FLAG_DECLARATION_UNNAMED = 8;
|
|
61
|
+
/** An `impl` row standing for more implementations than {@link IMPL_CAP}; it names none. */
|
|
62
|
+
export const FLAG_TOO_MANY = 16;
|
|
63
|
+
/** The most implementations one declaration's `impl` rows list. */
|
|
64
|
+
export const IMPL_CAP = 16;
|
|
50
65
|
const COMPARE_OPERATORS = new Set([
|
|
51
66
|
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
|
52
67
|
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
@@ -79,6 +94,8 @@ export function writePositionRows(options) {
|
|
|
79
94
|
resolved: 0,
|
|
80
95
|
any: 0,
|
|
81
96
|
error_type: 0,
|
|
97
|
+
impl: 0,
|
|
98
|
+
jsx: 0,
|
|
82
99
|
bytes: 0,
|
|
83
100
|
load_ms: 0,
|
|
84
101
|
walk_ms: 0,
|
|
@@ -127,11 +144,16 @@ export function writePositionRows(options) {
|
|
|
127
144
|
}
|
|
128
145
|
read.set(index, sourceFile?.getFilePath());
|
|
129
146
|
}
|
|
130
|
-
// Built once, after every file the group adds.
|
|
131
|
-
|
|
147
|
+
// Built once, after every file the group adds. The language service's
|
|
148
|
+
// program is the one walked, so go-to-implementation answers over it
|
|
149
|
+
// and no second program is built.
|
|
150
|
+
const service = project.getLanguageService().compilerObject;
|
|
151
|
+
const program = service.getProgram();
|
|
152
|
+
if (!program)
|
|
153
|
+
throw new Error('the language service built no program');
|
|
132
154
|
counts.load_ms += performance.now() - loadStarted;
|
|
133
155
|
const walkStarted = performance.now();
|
|
134
|
-
const walker = new PositionWalker(program, paths, loader);
|
|
156
|
+
const walker = new PositionWalker(program, service, paths, loader);
|
|
135
157
|
const missed = [];
|
|
136
158
|
for (const index of indexes) {
|
|
137
159
|
if (!read.has(index))
|
|
@@ -151,22 +173,23 @@ export function writePositionRows(options) {
|
|
|
151
173
|
counts.any += 1;
|
|
152
174
|
if (row[8] & FLAG_ERROR_TYPE)
|
|
153
175
|
counts.error_type += 1;
|
|
176
|
+
if (row[0] === 'impl')
|
|
177
|
+
counts.impl += 1;
|
|
178
|
+
if (row[0] === 'jsx')
|
|
179
|
+
counts.jsx += 1;
|
|
154
180
|
}
|
|
155
181
|
lines[index] = fileLine({ f: paths.relative(files[index]), h: hashes.get(index), r: rows });
|
|
156
182
|
options.onFile?.();
|
|
157
183
|
}
|
|
158
184
|
counts.walk_ms += performance.now() - walkStarted;
|
|
159
|
-
return missed;
|
|
185
|
+
return { missed, program };
|
|
160
186
|
};
|
|
161
187
|
const hashes = new Map();
|
|
162
188
|
for (const [key, indexes] of groups) {
|
|
163
189
|
const project = loader.getProjectFor(key);
|
|
164
|
-
const missed = walkGroup(project, indexes, hashes);
|
|
190
|
+
const { missed, program } = walkGroup(project, indexes, hashes);
|
|
165
191
|
if (missed.length > 0) {
|
|
166
|
-
const declarations =
|
|
167
|
-
.getProgram()
|
|
168
|
-
.compilerObject.getRootFileNames()
|
|
169
|
-
.filter(isDeclarationFileName);
|
|
192
|
+
const declarations = program.getRootFileNames().filter(isDeclarationFileName);
|
|
170
193
|
untaken.set(key, { indexes: missed, declarations });
|
|
171
194
|
}
|
|
172
195
|
// One owner's program at a time.
|
|
@@ -184,7 +207,7 @@ export function writePositionRows(options) {
|
|
|
184
207
|
}
|
|
185
208
|
}
|
|
186
209
|
counts.load_ms += performance.now() - loadStarted;
|
|
187
|
-
for (const index of walkGroup(project, indexes, hashes)) {
|
|
210
|
+
for (const index of walkGroup(project, indexes, hashes).missed) {
|
|
188
211
|
lines[index] = fileLine({ f: paths.relative(files[index]), h: hashes.get(index), skip: 'not_in_program', r: [] });
|
|
189
212
|
options.onFile?.();
|
|
190
213
|
}
|
|
@@ -344,11 +367,21 @@ function toSlashes(p) {
|
|
|
344
367
|
/** The rows of one program's files. */
|
|
345
368
|
class PositionWalker {
|
|
346
369
|
program;
|
|
370
|
+
service;
|
|
347
371
|
paths;
|
|
348
372
|
loader;
|
|
349
373
|
checker;
|
|
350
|
-
|
|
374
|
+
/**
|
|
375
|
+
* Each bodyless declaration's implementations, as `impl` rows with no span
|
|
376
|
+
* yet (`start` and `end` are 0): go-to-implementation searches the whole
|
|
377
|
+
* program, so it is asked once per declaration, never per call.
|
|
378
|
+
*/
|
|
379
|
+
implementations = new Map();
|
|
380
|
+
/** The same for a member whose symbol has no declaration, keyed by symbol. */
|
|
381
|
+
undeclaredImplementations = new Map();
|
|
382
|
+
constructor(program, service, paths, loader) {
|
|
351
383
|
this.program = program;
|
|
384
|
+
this.service = service;
|
|
352
385
|
this.paths = paths;
|
|
353
386
|
this.loader = loader;
|
|
354
387
|
this.checker = program.getTypeChecker();
|
|
@@ -357,8 +390,17 @@ class PositionWalker {
|
|
|
357
390
|
const rows = [];
|
|
358
391
|
const visit = (node) => {
|
|
359
392
|
const kind = kindOf(node);
|
|
360
|
-
if (kind)
|
|
361
|
-
|
|
393
|
+
if (kind) {
|
|
394
|
+
const row = this.row(kind, node, sourceFile);
|
|
395
|
+
rows.push(row);
|
|
396
|
+
if ((kind === 'call' || kind === 'member') && !(row[8] & FLAG_CHECKER_THREW)) {
|
|
397
|
+
for (const impl of this.implRows(kind, node))
|
|
398
|
+
rows.push(['impl', row[1], row[2], ...tail(impl)]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
402
|
+
rows.push(this.row('jsx', node.tagName, sourceFile));
|
|
403
|
+
}
|
|
362
404
|
ts.forEachChild(node, visit);
|
|
363
405
|
};
|
|
364
406
|
ts.forEachChild(sourceFile, visit);
|
|
@@ -370,7 +412,7 @@ class PositionWalker {
|
|
|
370
412
|
try {
|
|
371
413
|
const symbol = this.symbolOf(kind, node);
|
|
372
414
|
const typed = kind === 'compare' ? node.left : node;
|
|
373
|
-
const type = this.checker.getTypeAtLocation(typed);
|
|
415
|
+
const type = (kind === 'jsx' && this.intrinsicProps(node)) || this.checker.getTypeAtLocation(typed);
|
|
374
416
|
let flags = 0;
|
|
375
417
|
if (type.flags & ts.TypeFlags.Any) {
|
|
376
418
|
flags |= type.intrinsicName === 'error' ? FLAG_ERROR_TYPE : FLAG_ANY;
|
|
@@ -389,13 +431,9 @@ class PositionWalker {
|
|
|
389
431
|
const symbolName = ts.symbolName(symbol);
|
|
390
432
|
const isModule = declaration.kind === ts.SyntaxKind.SourceFile && symbolName.startsWith('"');
|
|
391
433
|
const declaringFile = declaration.getSourceFile();
|
|
392
|
-
const declFile = this.
|
|
393
|
-
? `lib:${path.basename(declaringFile.fileName)}`
|
|
394
|
-
: this.paths.declaringFile(declaringFile.fileName);
|
|
434
|
+
const { declFile, declLine, declPackage } = this.located(declaringFile, declaration.getStart(declaringFile));
|
|
395
435
|
if (declFile === null)
|
|
396
436
|
flags |= FLAG_DECLARATION_UNNAMED;
|
|
397
|
-
const declLine = declaringFile.getLineAndCharacterOfPosition(declaration.getStart(declaringFile)).line + 1;
|
|
398
|
-
const declPackage = this.paths.declaringPackage(declaringFile.fileName, (file) => this.loader.packageOf(file));
|
|
399
437
|
const name = isModule ? declFile : symbolName;
|
|
400
438
|
return [kind, start, end, name, declFile, declLine, declPackage, typeText, flags];
|
|
401
439
|
}
|
|
@@ -403,6 +441,121 @@ class PositionWalker {
|
|
|
403
441
|
return [kind, start, end, null, null, null, null, null, FLAG_CHECKER_THREW];
|
|
404
442
|
}
|
|
405
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* An intrinsic element's type: the attributes `JSX.IntrinsicElements`
|
|
446
|
+
* gives its tag. The compiler types an intrinsic tag name itself as the
|
|
447
|
+
* error type, though the name resolves. A tag is intrinsic when it is an
|
|
448
|
+
* identifier that starts with a lowercase letter or holds a `-`, as the
|
|
449
|
+
* language reads it.
|
|
450
|
+
*/
|
|
451
|
+
intrinsicProps(tag) {
|
|
452
|
+
if (!ts.isIdentifier(tag) || !/^[a-z]|-/.test(tag.text))
|
|
453
|
+
return undefined;
|
|
454
|
+
const element = tag.parent;
|
|
455
|
+
if (!ts.isJsxOpeningElement(element) && !ts.isJsxSelfClosingElement(element))
|
|
456
|
+
return undefined;
|
|
457
|
+
return this.checker.getContextualType(element.attributes);
|
|
458
|
+
}
|
|
459
|
+
/** How rows name a position in a program file: its file, 1-based line and package. */
|
|
460
|
+
located(declaringFile, position) {
|
|
461
|
+
const declFile = this.program.isSourceFileDefaultLibrary(declaringFile)
|
|
462
|
+
? `lib:${path.basename(declaringFile.fileName)}`
|
|
463
|
+
: this.paths.declaringFile(declaringFile.fileName);
|
|
464
|
+
const declLine = declaringFile.getLineAndCharacterOfPosition(position).line + 1;
|
|
465
|
+
const declPackage = this.paths.declaringPackage(declaringFile.fileName, (file) => this.loader.packageOf(file));
|
|
466
|
+
return { declFile, declLine, declPackage };
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* The `impl` rows of a `call` or `member` row (spans unset): none unless the
|
|
470
|
+
* row's declaration has no body of its own ({@link isBodyless}). Its own
|
|
471
|
+
* search, apart from the row's: a throw here writes one `impl` row flagged
|
|
472
|
+
* {@link FLAG_CHECKER_THREW} and leaves the row as it was.
|
|
473
|
+
*/
|
|
474
|
+
implRows(kind, node) {
|
|
475
|
+
let declaration;
|
|
476
|
+
try {
|
|
477
|
+
const symbol = this.symbolOf(kind, node);
|
|
478
|
+
if (symbol && (symbol.declarations ?? []).length === 0)
|
|
479
|
+
return this.undeclaredImplRows(kind, node, symbol);
|
|
480
|
+
declaration = symbol ? attributedDeclaration(() => this.program, symbol.declarations ?? []) : undefined;
|
|
481
|
+
if (!declaration || !isBodyless(declaration, this.checker))
|
|
482
|
+
return [];
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
let rows = this.implementations.get(declaration);
|
|
488
|
+
if (!rows) {
|
|
489
|
+
try {
|
|
490
|
+
rows = this.implementationsOf(declaration);
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
rows = [['impl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
|
|
494
|
+
}
|
|
495
|
+
this.implementations.set(declaration, rows);
|
|
496
|
+
}
|
|
497
|
+
return rows;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* The `impl` rows of a member whose symbol has no declaration (a mapped
|
|
501
|
+
* type's member): there is no declaration name to ask at, so the answer is
|
|
502
|
+
* asked at the use site's name, once per symbol. Same shape as
|
|
503
|
+
* {@link implementationsOf}; nothing of the symbol's own is filtered out.
|
|
504
|
+
*/
|
|
505
|
+
undeclaredImplRows(kind, node, symbol) {
|
|
506
|
+
const cached = this.undeclaredImplementations.get(symbol);
|
|
507
|
+
if (cached)
|
|
508
|
+
return cached;
|
|
509
|
+
let rows;
|
|
510
|
+
try {
|
|
511
|
+
const named = nameNodeOf(kind, node);
|
|
512
|
+
rows = named ? this.implementationRows(named, []) : [];
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
rows = [['impl', 0, 0, null, null, null, null, null, FLAG_CHECKER_THREW]];
|
|
516
|
+
}
|
|
517
|
+
this.undeclaredImplementations.set(symbol, rows);
|
|
518
|
+
return rows;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* What go-to-implementation names for `declaration`, asked at its name:
|
|
522
|
+
* every declaration the language service answers, except the symbol's own
|
|
523
|
+
* (an overloaded method answers with its other signatures), in file and
|
|
524
|
+
* position order.
|
|
525
|
+
*/
|
|
526
|
+
implementationsOf(declaration) {
|
|
527
|
+
const name = ts.getNameOfDeclaration(declaration) ?? declaration;
|
|
528
|
+
const own = (this.checker.getSymbolAtLocation(name) ?? declaration.symbol)
|
|
529
|
+
?.declarations ?? [declaration];
|
|
530
|
+
return this.implementationRows(name, own);
|
|
531
|
+
}
|
|
532
|
+
/** Go-to-implementation at `name`, minus results inside `own`, as sorted `impl` rows. */
|
|
533
|
+
implementationRows(name, own) {
|
|
534
|
+
const declaringFile = name.getSourceFile();
|
|
535
|
+
const found = new Map();
|
|
536
|
+
for (const location of this.service.getImplementationAtPosition(declaringFile.fileName, name.getStart(declaringFile)) ?? []) {
|
|
537
|
+
const file = this.program.getSourceFile(location.fileName);
|
|
538
|
+
if (!file)
|
|
539
|
+
continue;
|
|
540
|
+
const start = location.textSpan.start;
|
|
541
|
+
if (own.some((d) => d.getSourceFile() === file && d.pos <= start && start < d.end))
|
|
542
|
+
continue;
|
|
543
|
+
found.set(`${location.fileName}\u0000${start}`, { file, start });
|
|
544
|
+
}
|
|
545
|
+
if (found.size === 0)
|
|
546
|
+
return [];
|
|
547
|
+
if (found.size > IMPL_CAP)
|
|
548
|
+
return [['impl', 0, 0, null, null, null, null, null, FLAG_TOO_MANY]];
|
|
549
|
+
const rows = [...found.values()].map(({ file, start }) => {
|
|
550
|
+
const { declFile, declLine, declPackage } = this.located(file, start);
|
|
551
|
+
const named = nodeStartingAt(file, start);
|
|
552
|
+
const symbol = named ? this.checker.getSymbolAtLocation(named) : undefined;
|
|
553
|
+
const flags = declFile === null ? FLAG_DECLARATION_UNNAMED : 0;
|
|
554
|
+
return { start, row: ['impl', 0, 0, symbol ? ts.symbolName(symbol) : null, declFile, declLine, declPackage, null, flags] };
|
|
555
|
+
});
|
|
556
|
+
rows.sort((a, b) => compareNames(a.row[4], b.row[4]) || a.start - b.start);
|
|
557
|
+
return rows.map(({ row }) => row);
|
|
558
|
+
}
|
|
406
559
|
/**
|
|
407
560
|
* The declaration a member with none is attributed to (carrick#2448): the
|
|
408
561
|
* mapped type node, or the alias naming it, of the receiver's type. A
|
|
@@ -550,6 +703,54 @@ class PositionWalker {
|
|
|
550
703
|
return this.host;
|
|
551
704
|
}
|
|
552
705
|
}
|
|
706
|
+
/** A row's fields after its kind and span. */
|
|
707
|
+
function tail(row) {
|
|
708
|
+
return [row[3], row[4], row[5], row[6], row[7], row[8]];
|
|
709
|
+
}
|
|
710
|
+
/** Order two `decl_file` names, `null` last. */
|
|
711
|
+
function compareNames(a, b) {
|
|
712
|
+
if (a === b)
|
|
713
|
+
return 0;
|
|
714
|
+
if (a === null)
|
|
715
|
+
return 1;
|
|
716
|
+
if (b === null)
|
|
717
|
+
return -1;
|
|
718
|
+
return a < b ? -1 : 1;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Whether a declaration has no body for a call to run: an interface's or type
|
|
722
|
+
* literal's method, a function-typed property signature, an abstract method or
|
|
723
|
+
* property, or a function-typed class property or parameter with no
|
|
724
|
+
* initializer. Its implementations are what go-to-implementation names.
|
|
725
|
+
*/
|
|
726
|
+
function isBodyless(declaration, checker) {
|
|
727
|
+
const functionTyped = () => checker.getTypeAtLocation(declaration).getNonNullableType().getCallSignatures().length > 0;
|
|
728
|
+
if (ts.isMethodSignature(declaration))
|
|
729
|
+
return true;
|
|
730
|
+
if (ts.isPropertySignature(declaration))
|
|
731
|
+
return functionTyped();
|
|
732
|
+
if (ts.isMethodDeclaration(declaration) || ts.isPropertyDeclaration(declaration)) {
|
|
733
|
+
if (ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Abstract)
|
|
734
|
+
return true;
|
|
735
|
+
return ts.isPropertyDeclaration(declaration) && !declaration.initializer && functionTyped();
|
|
736
|
+
}
|
|
737
|
+
if (ts.isParameter(declaration))
|
|
738
|
+
return !declaration.initializer && functionTyped();
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
/** The innermost node of `file` that starts at `position` (an implementation's name). */
|
|
742
|
+
function nodeStartingAt(file, position) {
|
|
743
|
+
let found;
|
|
744
|
+
const visit = (node) => {
|
|
745
|
+
if (position < node.getStart(file) || position >= node.getEnd())
|
|
746
|
+
return;
|
|
747
|
+
if (node.getStart(file) === position)
|
|
748
|
+
found = node;
|
|
749
|
+
ts.forEachChild(node, visit);
|
|
750
|
+
};
|
|
751
|
+
ts.forEachChild(file, visit);
|
|
752
|
+
return found;
|
|
753
|
+
}
|
|
553
754
|
/** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
|
|
554
755
|
export function kindOf(node) {
|
|
555
756
|
if (ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isTaggedTemplateExpression(node)) {
|
|
@@ -576,6 +777,12 @@ function nameNodeOf(kind, node) {
|
|
|
576
777
|
return referenceName(node.left);
|
|
577
778
|
case 'ident':
|
|
578
779
|
return node;
|
|
780
|
+
case 'impl':
|
|
781
|
+
return undefined;
|
|
782
|
+
case 'jsx':
|
|
783
|
+
// The tag: `<Badge>`, `<ui.Button>`, `<this.Panel>`; a namespaced
|
|
784
|
+
// name (`<svg:path>`) names nothing.
|
|
785
|
+
return ts.isJsxNamespacedName(node) ? undefined : (referenceName(node) ?? node);
|
|
579
786
|
}
|
|
580
787
|
}
|
|
581
788
|
/**
|
|
@@ -765,6 +765,10 @@ export interface PositionRowsCounts {
|
|
|
765
765
|
any: number;
|
|
766
766
|
/** Rows whose type is the compiler's error type (flag 2). */
|
|
767
767
|
error_type: number;
|
|
768
|
+
/** `impl` rows: implementations of a bodyless declaration, or one past the cap (carrick#2430). */
|
|
769
|
+
impl: number;
|
|
770
|
+
/** `jsx` rows: one per JSX opening or self-closing element. */
|
|
771
|
+
jsx: number;
|
|
768
772
|
/** The rows file's size on disk, compressed. */
|
|
769
773
|
bytes: number;
|
|
770
774
|
/** Time spent building programs and reading files into them. */
|