carrick 0.3.116 → 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/dist/contract.d.ts +2 -0
- package/dist/contract.js.map +1 -1
- package/dist/render.js +2 -1
- package/dist/render.js.map +1 -1
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/anchors.d.ts +1 -0
- package/sidecar/dist/src/capture/anchors.js +1 -1
- package/sidecar/dist/src/capture/api.d.ts +6 -0
- package/sidecar/dist/src/capture/check-classify.js +25 -0
- package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
- package/sidecar/dist/src/capture/check-probe.js +20 -0
- package/sidecar/dist/src/capture/checkout.d.ts +3 -0
- package/sidecar/dist/src/capture/checkout.js +1 -1
- package/sidecar/dist/src/capture/deep-walk.d.ts +10 -0
- package/sidecar/dist/src/capture/deep-walk.js +15 -0
- package/sidecar/dist/src/capture/guarded-fs.d.ts +6 -0
- package/sidecar/dist/src/capture/guarded-fs.js +10 -0
- package/sidecar/dist/src/capture/index.d.ts +5 -0
- package/sidecar/dist/src/capture/index.js +62 -7
- package/sidecar/dist/src/capture/project-references.d.ts +24 -13
- package/sidecar/dist/src/capture/project-references.js +53 -16
- package/sidecar/dist/src/capture/reference-redirect.d.ts +84 -0
- package/sidecar/dist/src/capture/reference-redirect.js +209 -0
- package/sidecar/dist/src/capture/unresolved.d.ts +8 -0
- package/sidecar/dist/src/capture/unresolved.js +18 -2
- package/sidecar/dist/src/declaration-attribution.d.ts +40 -0
- package/sidecar/dist/src/declaration-attribution.js +66 -0
- package/sidecar/dist/src/index.js +37 -0
- package/sidecar/dist/src/library-claims.js +234 -23
- package/sidecar/dist/src/module-format.d.ts +19 -1
- package/sidecar/dist/src/module-format.js +9 -2
- package/sidecar/dist/src/position-rows.d.ts +95 -0
- package/sidecar/dist/src/position-rows.js +844 -0
- package/sidecar/dist/src/project-loader.d.ts +36 -7
- package/sidecar/dist/src/project-loader.js +70 -28
- package/sidecar/dist/src/retype.js +13 -0
- package/sidecar/dist/src/type-inferrer.d.ts +25 -29
- package/sidecar/dist/src/type-inferrer.js +119 -70
- package/sidecar/dist/src/types.d.ts +78 -2
- package/sidecar/dist/src/validators.d.ts +431 -30
- package/sidecar/dist/src/validators.js +45 -1
|
@@ -38,6 +38,14 @@ export interface LoadResult {
|
|
|
38
38
|
error?: string;
|
|
39
39
|
initTimeMs?: number;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* What a build makes (carrick#2413). `listed` is the project as its config
|
|
43
|
+
* lists it. `scripts` has the same options with JavaScript accepted and lists
|
|
44
|
+
* no file: it is for the files the listed program does not take (a `.js`,
|
|
45
|
+
* `.mjs` or `.cjs` file under a config without `allowJs`), which the caller
|
|
46
|
+
* adds.
|
|
47
|
+
*/
|
|
48
|
+
export type BuildKind = 'listed' | 'scripts';
|
|
41
49
|
/**
|
|
42
50
|
* ProjectLoader - Manages ts-morph Project initialization and access
|
|
43
51
|
*
|
|
@@ -58,7 +66,10 @@ export interface LoadResult {
|
|
|
58
66
|
*/
|
|
59
67
|
export declare class ProjectLoader {
|
|
60
68
|
private project;
|
|
61
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Set by `load()`; builds the ts-morph project on first `getProject()`,
|
|
71
|
+
* and the default project's `scripts` program for `scriptProjectFor`.
|
|
72
|
+
*/
|
|
62
73
|
private buildProject;
|
|
63
74
|
/**
|
|
64
75
|
* The Deno graph this project resolved through, when it did (carrick#1260).
|
|
@@ -101,15 +112,18 @@ export declare class ProjectLoader {
|
|
|
101
112
|
* Nothing at all otherwise, so a project is built as it always was.
|
|
102
113
|
*/
|
|
103
114
|
private fileSystemOption;
|
|
104
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* A ts-morph project built from one tsconfig: with the files it lists, or
|
|
117
|
+
* as its `scripts` program (`BuildKind`).
|
|
118
|
+
*/
|
|
105
119
|
private projectFromConfig;
|
|
106
120
|
/**
|
|
107
121
|
* Which project types `file` (carrick#1604): the key of its owning
|
|
108
|
-
* project's program, or `''` for the default one.
|
|
109
|
-
* references other projects
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* project's options. A file no project includes, and every request that
|
|
122
|
+
* project's program, or `''` for the default one. In a tsconfig that
|
|
123
|
+
* references other projects, a file belongs to the first project in search
|
|
124
|
+
* order (the named config, then its references depth-first, in declared
|
|
125
|
+
* order) that lists it while none of its own references does (carrick#2418),
|
|
126
|
+
* and is typed under that project's options. A file no project includes, and every request that
|
|
113
127
|
* names no file, uses the default project, built from the named tsconfig
|
|
114
128
|
* as before.
|
|
115
129
|
*/
|
|
@@ -119,6 +133,21 @@ export declare class ProjectLoader {
|
|
|
119
133
|
* kept: a service whose files all belong to one project builds one.
|
|
120
134
|
*/
|
|
121
135
|
getProjectFor(key: string): Project;
|
|
136
|
+
/**
|
|
137
|
+
* The `scripts` program (`BuildKind`) of the project behind a key from
|
|
138
|
+
* `projectKeyFor`: its options with JavaScript accepted, built by the same
|
|
139
|
+
* factory, and no file listed. Not kept; the caller drops it. For the walk's
|
|
140
|
+
* files that the owner's program does not take (carrick#2413).
|
|
141
|
+
*/
|
|
142
|
+
scriptProjectFor(key: string): Project;
|
|
143
|
+
/**
|
|
144
|
+
* Drop the program behind a key from `projectKeyFor`, so the memory it
|
|
145
|
+
* holds can go before the next one is built. The next `getProjectFor` on
|
|
146
|
+
* the key builds it again. For a walk that reads one owner's program at a
|
|
147
|
+
* time and never comes back to it (carrick#2405); the actions that keep
|
|
148
|
+
* their programs for the life of the process never call it.
|
|
149
|
+
*/
|
|
150
|
+
releaseProjectFor(key: string): void;
|
|
122
151
|
/**
|
|
123
152
|
* Get the ts-morph Project, building it on first call.
|
|
124
153
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { Project, getCompilerOptionsFromTsConfig } from 'ts-morph';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
|
-
import { DenoProject, findDenoConfig, findServiceTsconfig, serviceConfigPath, sidecarCompilerOptions, untrustedReadBound, } from './capture/index.js';
|
|
15
|
+
import { DenoProject, findDenoConfig, findServiceTsconfig, referenceRedirectOf, serviceConfigPath, sidecarCompilerOptions, untrustedReadBound, } from './capture/index.js';
|
|
16
16
|
import { moduleFormatResolutionHost } from './module-format.js';
|
|
17
17
|
import { ExternalImports, registerExternalImports } from './origin.js';
|
|
18
18
|
/**
|
|
@@ -44,6 +44,10 @@ const DEFAULT_COMPILER_OPTIONS = {
|
|
|
44
44
|
resolveJsonModule: true,
|
|
45
45
|
isolatedModules: true,
|
|
46
46
|
};
|
|
47
|
+
/** `options` as `kind` builds them. */
|
|
48
|
+
function optionsFor(kind, options) {
|
|
49
|
+
return kind === 'scripts' ? { ...options, allowJs: true } : options;
|
|
50
|
+
}
|
|
47
51
|
/**
|
|
48
52
|
* Map string module values to ts-morph enum values
|
|
49
53
|
*/
|
|
@@ -108,7 +112,10 @@ const TARGET_MAP = {
|
|
|
108
112
|
*/
|
|
109
113
|
export class ProjectLoader {
|
|
110
114
|
project = null;
|
|
111
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Set by `load()`; builds the ts-morph project on first `getProject()`,
|
|
117
|
+
* and the default project's `scripts` program for `scriptProjectFor`.
|
|
118
|
+
*/
|
|
112
119
|
buildProject = null;
|
|
113
120
|
/**
|
|
114
121
|
* The Deno graph this project resolved through, when it did (carrick#1260).
|
|
@@ -175,13 +182,14 @@ export class ProjectLoader {
|
|
|
175
182
|
if (this.tsconfigSnapshot) {
|
|
176
183
|
const snapshot = this.tsconfigSnapshot;
|
|
177
184
|
this.log('Project will load with tsconfig snapshot');
|
|
178
|
-
this.buildProject = () => {
|
|
185
|
+
this.buildProject = (kind) => {
|
|
179
186
|
const project = new Project({
|
|
180
187
|
...this.fileSystemOption(),
|
|
181
|
-
compilerOptions: sidecarCompilerOptions(this.snapshotToCompilerOptions(snapshot)),
|
|
188
|
+
compilerOptions: sidecarCompilerOptions(optionsFor(kind, this.snapshotToCompilerOptions(snapshot))),
|
|
182
189
|
skipAddingFilesFromTsConfig: true,
|
|
183
190
|
});
|
|
184
|
-
|
|
191
|
+
if (kind === 'listed')
|
|
192
|
+
this.addDefaultSourceFiles(project);
|
|
185
193
|
return project;
|
|
186
194
|
};
|
|
187
195
|
}
|
|
@@ -191,7 +199,7 @@ export class ProjectLoader {
|
|
|
191
199
|
const denoConfig = findDenoConfig(this.repoRoot, this.tsconfigPath);
|
|
192
200
|
if (denoConfig) {
|
|
193
201
|
this.log(`Project will load with Deno config: ${denoConfig.configPath}`);
|
|
194
|
-
this.buildProject = () => {
|
|
202
|
+
this.buildProject = (kind) => {
|
|
195
203
|
const deno = new DenoProject(denoConfig, this.repoRoot);
|
|
196
204
|
this.denoProject = deno;
|
|
197
205
|
// The graph's external-library verdicts, kept past the program
|
|
@@ -199,7 +207,7 @@ export class ProjectLoader {
|
|
|
199
207
|
const imports = new ExternalImports();
|
|
200
208
|
const project = new Project({
|
|
201
209
|
...this.fileSystemOption(),
|
|
202
|
-
compilerOptions: sidecarCompilerOptions(deno.parsed.options),
|
|
210
|
+
compilerOptions: sidecarCompilerOptions(optionsFor(kind, deno.parsed.options)),
|
|
203
211
|
skipAddingFilesFromTsConfig: true,
|
|
204
212
|
resolutionHost: imports.recording((host, getOptions) => ({
|
|
205
213
|
resolveModuleNames: (names, from) => names.map(name => deno.resolve(name, from, getOptions(), host)),
|
|
@@ -207,28 +215,31 @@ export class ProjectLoader {
|
|
|
207
215
|
})),
|
|
208
216
|
});
|
|
209
217
|
registerExternalImports(project, imports);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
if (kind === 'listed') {
|
|
219
|
+
for (const file of deno.parsed.fileNames)
|
|
220
|
+
project.addSourceFileAtPath(file);
|
|
221
|
+
for (const diagnostic of deno.diagnostics)
|
|
222
|
+
this.logError(diagnostic);
|
|
223
|
+
}
|
|
214
224
|
return project;
|
|
215
225
|
};
|
|
216
226
|
}
|
|
217
227
|
else if (tsconfigPath) {
|
|
218
228
|
this.log(`Project will load with tsconfig: ${tsconfigPath}`);
|
|
219
229
|
this.namedTsconfigPath = tsconfigPath;
|
|
220
|
-
this.buildProject = () => this.projectFromConfig(tsconfigPath);
|
|
230
|
+
this.buildProject = (kind) => this.projectFromConfig(tsconfigPath, kind);
|
|
221
231
|
}
|
|
222
232
|
else {
|
|
223
233
|
this.log('No tsconfig.json found, using default compiler options');
|
|
224
|
-
this.buildProject = () => {
|
|
234
|
+
this.buildProject = (kind) => {
|
|
225
235
|
const project = new Project({
|
|
226
236
|
...this.fileSystemOption(),
|
|
227
|
-
compilerOptions: sidecarCompilerOptions(DEFAULT_COMPILER_OPTIONS),
|
|
237
|
+
compilerOptions: sidecarCompilerOptions(optionsFor(kind, DEFAULT_COMPILER_OPTIONS)),
|
|
228
238
|
skipAddingFilesFromTsConfig: true,
|
|
229
239
|
});
|
|
230
240
|
// Add source files from common locations
|
|
231
|
-
|
|
241
|
+
if (kind === 'listed')
|
|
242
|
+
this.addDefaultSourceFiles(project);
|
|
232
243
|
return project;
|
|
233
244
|
};
|
|
234
245
|
}
|
|
@@ -330,30 +341,34 @@ export class ProjectLoader {
|
|
|
330
341
|
const bound = untrustedReadBound(this.repoRoot);
|
|
331
342
|
return bound ? { fileSystem: boundedFileSystem(bound) } : {};
|
|
332
343
|
}
|
|
333
|
-
/**
|
|
334
|
-
|
|
344
|
+
/**
|
|
345
|
+
* A ts-morph project built from one tsconfig: with the files it lists, or
|
|
346
|
+
* as its `scripts` program (`BuildKind`).
|
|
347
|
+
*/
|
|
348
|
+
projectFromConfig(configPath, kind = 'listed') {
|
|
335
349
|
const imports = new ExternalImports();
|
|
336
350
|
const project = new Project({
|
|
337
351
|
...this.fileSystemOption(),
|
|
338
352
|
tsConfigFilePath: configPath,
|
|
339
353
|
// Over the tsconfig's own options: ts-morph lays these on top.
|
|
340
|
-
compilerOptions: sidecarCompilerOptions(getCompilerOptionsFromTsConfig(configPath).options),
|
|
341
|
-
skipAddingFilesFromTsConfig:
|
|
354
|
+
compilerOptions: sidecarCompilerOptions(optionsFor(kind, getCompilerOptionsFromTsConfig(configPath).options)),
|
|
355
|
+
skipAddingFilesFromTsConfig: kind === 'scripts',
|
|
342
356
|
// Each import resolves in its file's own module format (carrick#1619),
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
|
|
357
|
+
// a referenced project's unbuilt output reads as its source, under its
|
|
358
|
+
// own options (carrick#2418), and every external-library answer is kept
|
|
359
|
+
// past the program rebuilds that drop it (carrick#1731).
|
|
360
|
+
resolutionHost: imports.recording(moduleFormatResolutionHost(referenceRedirectOf(configPath, this.repoRoot))),
|
|
346
361
|
});
|
|
347
362
|
registerExternalImports(project, imports);
|
|
348
363
|
return project;
|
|
349
364
|
}
|
|
350
365
|
/**
|
|
351
366
|
* Which project types `file` (carrick#1604): the key of its owning
|
|
352
|
-
* project's program, or `''` for the default one.
|
|
353
|
-
* references other projects
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* project's options. A file no project includes, and every request that
|
|
367
|
+
* project's program, or `''` for the default one. In a tsconfig that
|
|
368
|
+
* references other projects, a file belongs to the first project in search
|
|
369
|
+
* order (the named config, then its references depth-first, in declared
|
|
370
|
+
* order) that lists it while none of its own references does (carrick#2418),
|
|
371
|
+
* and is typed under that project's options. A file no project includes, and every request that
|
|
357
372
|
* names no file, uses the default project, built from the named tsconfig
|
|
358
373
|
* as before.
|
|
359
374
|
*/
|
|
@@ -381,6 +396,33 @@ export class ProjectLoader {
|
|
|
381
396
|
}
|
|
382
397
|
return project;
|
|
383
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* The `scripts` program (`BuildKind`) of the project behind a key from
|
|
401
|
+
* `projectKeyFor`: its options with JavaScript accepted, built by the same
|
|
402
|
+
* factory, and no file listed. Not kept; the caller drops it. For the walk's
|
|
403
|
+
* files that the owner's program does not take (carrick#2413).
|
|
404
|
+
*/
|
|
405
|
+
scriptProjectFor(key) {
|
|
406
|
+
if (!this.initialized || !this.buildProject) {
|
|
407
|
+
throw new Error('Project not initialized. Call load() first and ensure it succeeds.');
|
|
408
|
+
}
|
|
409
|
+
return key === '' ? this.buildProject('scripts') : this.projectFromConfig(key, 'scripts');
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Drop the program behind a key from `projectKeyFor`, so the memory it
|
|
413
|
+
* holds can go before the next one is built. The next `getProjectFor` on
|
|
414
|
+
* the key builds it again. For a walk that reads one owner's program at a
|
|
415
|
+
* time and never comes back to it (carrick#2405); the actions that keep
|
|
416
|
+
* their programs for the life of the process never call it.
|
|
417
|
+
*/
|
|
418
|
+
releaseProjectFor(key) {
|
|
419
|
+
if (key === '') {
|
|
420
|
+
this.project = null;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
this.ownerProjects.delete(key);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
384
426
|
/**
|
|
385
427
|
* Get the ts-morph Project, building it on first call.
|
|
386
428
|
*
|
|
@@ -393,7 +435,7 @@ export class ProjectLoader {
|
|
|
393
435
|
}
|
|
394
436
|
if (!this.project) {
|
|
395
437
|
const startTime = performance.now();
|
|
396
|
-
this.project = this.buildProject();
|
|
438
|
+
this.project = this.buildProject('listed');
|
|
397
439
|
const buildTimeMs = Math.round(performance.now() - startTime);
|
|
398
440
|
this.log(`Project built in ${buildTimeMs}ms ` +
|
|
399
441
|
`(${this.project.getSourceFiles().length} source files)`);
|
|
@@ -290,6 +290,13 @@ export class Retyper {
|
|
|
290
290
|
remaining.set(k, left - 1);
|
|
291
291
|
continue;
|
|
292
292
|
}
|
|
293
|
+
// The rewrite only removes or replaces source text, so an unused
|
|
294
|
+
// declaration that is new after it is the rewrite's own doing: the
|
|
295
|
+
// statement it replaced was the declaration's only use
|
|
296
|
+
// (carrick#2426). Unused diagnostics the file already raised are
|
|
297
|
+
// matched out above and never reach here.
|
|
298
|
+
if (UNUSED_DECLARATION_CODES.has(d.code))
|
|
299
|
+
continue;
|
|
293
300
|
added.push(mapped);
|
|
294
301
|
}
|
|
295
302
|
added.sort((a, b) => a.start - b.start || a.code - b.code);
|
|
@@ -828,6 +835,12 @@ function bodyReadEdit(read) {
|
|
|
828
835
|
function declaresTypeParameters(call) {
|
|
829
836
|
return (resolvedDeclaration(call)?.typeParameters?.length ?? 0) > 0;
|
|
830
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* "Declared but never used" and its grouped forms: unused locals and imports
|
|
840
|
+
* (6133, 6192, 6196, 6198, 6199), unused properties (6138) and unused type
|
|
841
|
+
* parameters (6205).
|
|
842
|
+
*/
|
|
843
|
+
const UNUSED_DECLARATION_CODES = new Set([6133, 6138, 6192, 6196, 6198, 6199, 6205]);
|
|
831
844
|
function key(d) {
|
|
832
845
|
return `${d.start}:${d.code}`;
|
|
833
846
|
}
|
|
@@ -357,9 +357,13 @@ export declare class TypeInferrer {
|
|
|
357
357
|
* Returns `undefined` when this does not apply, which leaves the caller's
|
|
358
358
|
* own reading of the located expression untouched:
|
|
359
359
|
* - the row's line is neither a route registration nor a line inside the
|
|
360
|
-
* handler the located send sits in
|
|
360
|
+
* handler the located send sits in, unless the located send states an
|
|
361
|
+
* error or redirect status and the handler keeps another send (carrick#2463);
|
|
361
362
|
* - the located expression is neither a send nor the argument of one;
|
|
362
|
-
* - that send is
|
|
363
|
+
* - that send is neither one the handler returns nor a statement of it;
|
|
364
|
+
* - it is a success send the handler does not return (a statement send
|
|
365
|
+
* joins only when the located send is dropped, and only when it ends
|
|
366
|
+
* the handler's path);
|
|
363
367
|
* - it is a success send and the only one that survives, so the join would
|
|
364
368
|
* be the located body anyway;
|
|
365
369
|
* - no joined body is the located one (a text body beside JSON ones).
|
|
@@ -370,6 +374,23 @@ export declare class TypeInferrer {
|
|
|
370
374
|
* or the redirect location back in.
|
|
371
375
|
*/
|
|
372
376
|
private inferFromResponseSites;
|
|
377
|
+
/**
|
|
378
|
+
* The response sends `func` makes as statements (`res.status(409).json(e);`),
|
|
379
|
+
* for a handler that sends through a parameter and returns nothing. Only the
|
|
380
|
+
* outermost call of a statement is a send, only in `func`'s own scope, and
|
|
381
|
+
* only where `isSend` accepts it, so a logger call or an `await save()`
|
|
382
|
+
* statement is never one.
|
|
383
|
+
*/
|
|
384
|
+
private responseSendStatements;
|
|
385
|
+
/**
|
|
386
|
+
* True when nothing in `func` runs after `statement`: a bare `return;`
|
|
387
|
+
* follows it, or it is the last statement of its block and the statement
|
|
388
|
+
* that block belongs to (an `if` branch, a `try` or `catch` block) is itself
|
|
389
|
+
* last, up to `func`'s body. A statement inside a loop, a `switch` case or
|
|
390
|
+
* any other construct does not end the path; a send there is not read as a
|
|
391
|
+
* success body.
|
|
392
|
+
*/
|
|
393
|
+
private statementEndsFunctionPath;
|
|
373
394
|
/**
|
|
374
395
|
* True when `candidate` is a response send: a call or `new` whose result is
|
|
375
396
|
* transport machinery (by the extraction config's verified rule or the
|
|
@@ -1533,35 +1554,10 @@ export declare class TypeInferrer {
|
|
|
1533
1554
|
* The npm package name that declares a type. `undefined` when the type has
|
|
1534
1555
|
* no declaration to read (a top type, a primitive, an anonymous object
|
|
1535
1556
|
* literal) or when nothing owns its declaration but the workspace itself.
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1538
|
-
* only thing that can answer for a project whose resolution does not go
|
|
1539
|
-
* through `node_modules`. Deno resolves an npm dependency's types straight
|
|
1540
|
-
* out of its own cache
|
|
1541
|
-
* (`$DENO_DIR/npm/<registry-host>/<name>/<version>/…`), a path with no
|
|
1542
|
-
* `node_modules` segment anywhere in it, so the scan below found no package
|
|
1543
|
-
* for ANY dependency-declared type on such a repo and every receiver the
|
|
1544
|
-
* compiler had typed correctly went unclassified (carrick#1260). It runs
|
|
1545
|
-
* first rather than second because a mixed checkout has both, and the graph
|
|
1546
|
-
* is what actually resolved the module.
|
|
1547
|
-
*
|
|
1548
|
-
* The path scan is the Node answer: the LAST `node_modules` segment wins,
|
|
1549
|
-
* which is what a nested or content-addressed store
|
|
1550
|
-
* (`node_modules/.store/pkg@1.0.0/node_modules/pkg`) requires. Scoped names
|
|
1551
|
-
* keep both segments.
|
|
1557
|
+
* The declaration is the attributed one (carrick#2031), and the package is
|
|
1558
|
+
* named by the module graph first, then by the path (`installedPackageOf`).
|
|
1552
1559
|
*/
|
|
1553
1560
|
private declaringPackageOf;
|
|
1554
|
-
/**
|
|
1555
|
-
* The declaration a symbol is attributed to (carrick#2031). One declaration
|
|
1556
|
-
* is itself. Where several files merge one symbol (a global interface the
|
|
1557
|
-
* compiler's library declares and a typings package augments), their order
|
|
1558
|
-
* is the order the checker merged them in, and on TypeScript 6 that changes
|
|
1559
|
-
* once the program is rebuilt, so the first one is not an answer. The
|
|
1560
|
-
* compiler's default library wins where it declares the symbol; otherwise,
|
|
1561
|
-
* and among the library's own files, the declaring file whose path sorts
|
|
1562
|
-
* first, then the earliest declaration in it.
|
|
1563
|
-
*/
|
|
1564
|
-
private attributedDeclaration;
|
|
1565
1561
|
/**
|
|
1566
1562
|
* The awaited return type of the member invoked on the receiver. Reported as
|
|
1567
1563
|
* a fact next to the receiver; a caller that classified on it alone would be
|
|
@@ -28,6 +28,7 @@ import { externalImportsOf, isExternalOrigin } from './origin.js';
|
|
|
28
28
|
import { reachedOnlyOnFailure } from './failure-path.js';
|
|
29
29
|
import { readResponseModes } from './response-modes.js';
|
|
30
30
|
import { functionAtLine } from './function-line-index.js';
|
|
31
|
+
import { attributedDeclaration, installedPackageOf } from './declaration-attribution.js';
|
|
31
32
|
import { endLineOf, startLineOf } from './line-index.js';
|
|
32
33
|
import { elapsedMs, inferTiming, phaseClock, timedPhase } from './infer-timing.js';
|
|
33
34
|
import { classifyStatusCodes, constituents, httpStatus, statesNoBody, statesNoShape, statusKeyCode, statusTableOf, successEntries, } from './capture/index.js';
|
|
@@ -1566,9 +1567,13 @@ export class TypeInferrer {
|
|
|
1566
1567
|
* Returns `undefined` when this does not apply, which leaves the caller's
|
|
1567
1568
|
* own reading of the located expression untouched:
|
|
1568
1569
|
* - the row's line is neither a route registration nor a line inside the
|
|
1569
|
-
* handler the located send sits in
|
|
1570
|
+
* handler the located send sits in, unless the located send states an
|
|
1571
|
+
* error or redirect status and the handler keeps another send (carrick#2463);
|
|
1570
1572
|
* - the located expression is neither a send nor the argument of one;
|
|
1571
|
-
* - that send is
|
|
1573
|
+
* - that send is neither one the handler returns nor a statement of it;
|
|
1574
|
+
* - it is a success send the handler does not return (a statement send
|
|
1575
|
+
* joins only when the located send is dropped, and only when it ends
|
|
1576
|
+
* the handler's path);
|
|
1572
1577
|
* - it is a success send and the only one that survives, so the join would
|
|
1573
1578
|
* be the located body anyway;
|
|
1574
1579
|
* - no joined body is the located one (a text body beside JSON ones).
|
|
@@ -1585,27 +1590,53 @@ export class TypeInferrer {
|
|
|
1585
1590
|
// The row's line is a registration, or a line inside the handler the
|
|
1586
1591
|
// located send sits in (carrick#2005: a file-based route's row names the
|
|
1587
1592
|
// send's own line, and a send's line is no registration). A row whose
|
|
1588
|
-
// line is outside that function says nothing about it
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
return undefined;
|
|
1597
|
-
const serialisers = this.calleesProvenSerialiser(branches);
|
|
1593
|
+
// line is outside that function says nothing about the function, so it
|
|
1594
|
+
// is "untied" (carrick#2463): only a located error or redirect send is
|
|
1595
|
+
// walked then, and only when a send survives the drop.
|
|
1596
|
+
const tied = !!this.registrationAtLine(sourceFile, request.line_number) ||
|
|
1597
|
+
(func.getStartLineNumber() <= request.line_number &&
|
|
1598
|
+
request.line_number <= func.getEndLineNumber());
|
|
1599
|
+
const returnedBranches = this.responseReturnedExpressions(func).flatMap((expression) => this.expandResponseBranches(expression, 0));
|
|
1600
|
+
const serialisers = this.calleesProvenSerialiser(returnedBranches);
|
|
1598
1601
|
const isSend = (candidate) => this.isResponseSend(candidate, extractionConfig, serialisers);
|
|
1602
|
+
// carrick#2427: a handler that sends through a parameter returns nothing,
|
|
1603
|
+
// its sends are statements, and the located one can be an error send.
|
|
1604
|
+
// Statement sends find and drop that branch. A call whose result is
|
|
1605
|
+
// response machinery is not always a body send, though (a header or
|
|
1606
|
+
// cookie setter returns the response too), so a statement send is a
|
|
1607
|
+
// success body only when the located send is dropped and the statement
|
|
1608
|
+
// ends the handler's path. A located success send joins the returned
|
|
1609
|
+
// sends alone.
|
|
1610
|
+
const statementSends = this.responseSendStatements(func, isSend);
|
|
1611
|
+
const locatingBranches = [...returnedBranches, ...statementSends.map((s) => s.send)];
|
|
1612
|
+
if (locatingBranches.length === 0)
|
|
1613
|
+
return undefined;
|
|
1599
1614
|
const peeled = this.peelTransparentExpression(located);
|
|
1600
1615
|
const site = isSend(peeled) ? peeled : this.receivingCallOf(located, isSend);
|
|
1601
1616
|
if (!site)
|
|
1602
1617
|
return undefined;
|
|
1603
1618
|
const contains = (outer, inner) => outer.getStart() <= inner.getStart() && inner.getEnd() <= outer.getEnd();
|
|
1604
|
-
const locatedBranch =
|
|
1619
|
+
const locatedBranch = locatingBranches.find((branch) => contains(branch, site));
|
|
1605
1620
|
if (!locatedBranch)
|
|
1606
1621
|
return undefined;
|
|
1607
1622
|
const dropped = (status) => status === 'error' || status === 'redirect';
|
|
1608
1623
|
const siteStatus = this.responseSiteStatus(site, true);
|
|
1624
|
+
if (!tied && !dropped(siteStatus))
|
|
1625
|
+
return undefined;
|
|
1626
|
+
let branches;
|
|
1627
|
+
if (dropped(siteStatus)) {
|
|
1628
|
+
branches = [
|
|
1629
|
+
...returnedBranches,
|
|
1630
|
+
...statementSends
|
|
1631
|
+
.filter((s) => s.send === locatedBranch || this.statementEndsFunctionPath(s.statement, func))
|
|
1632
|
+
.map((s) => s.send),
|
|
1633
|
+
];
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
if (!returnedBranches.includes(locatedBranch))
|
|
1637
|
+
return undefined;
|
|
1638
|
+
branches = returnedBranches;
|
|
1639
|
+
}
|
|
1609
1640
|
const survivors = [];
|
|
1610
1641
|
for (const branch of branches) {
|
|
1611
1642
|
const status = branch === locatedBranch ? siteStatus : this.responseSiteStatus(branch, true);
|
|
@@ -1619,6 +1650,10 @@ export class TypeInferrer {
|
|
|
1619
1650
|
continue;
|
|
1620
1651
|
survivors.push(branch);
|
|
1621
1652
|
}
|
|
1653
|
+
// carrick#2463 (D2): an untied row claims "no success payload" only when
|
|
1654
|
+
// the function it reads keeps a send after the error sends are dropped.
|
|
1655
|
+
if (!tied && survivors.length === 0)
|
|
1656
|
+
return undefined;
|
|
1622
1657
|
const locatedDropped = dropped(siteStatus);
|
|
1623
1658
|
if (!locatedDropped && survivors.length <= 1)
|
|
1624
1659
|
return undefined;
|
|
@@ -1646,6 +1681,73 @@ export class TypeInferrer {
|
|
|
1646
1681
|
}
|
|
1647
1682
|
return undefined;
|
|
1648
1683
|
}
|
|
1684
|
+
/**
|
|
1685
|
+
* The response sends `func` makes as statements (`res.status(409).json(e);`),
|
|
1686
|
+
* for a handler that sends through a parameter and returns nothing. Only the
|
|
1687
|
+
* outermost call of a statement is a send, only in `func`'s own scope, and
|
|
1688
|
+
* only where `isSend` accepts it, so a logger call or an `await save()`
|
|
1689
|
+
* statement is never one.
|
|
1690
|
+
*/
|
|
1691
|
+
responseSendStatements(func, isSend) {
|
|
1692
|
+
const body = func.getBody();
|
|
1693
|
+
if (!body || !Node.isBlock(body))
|
|
1694
|
+
return [];
|
|
1695
|
+
const sends = [];
|
|
1696
|
+
for (const statement of body.getDescendantsOfKind(SyntaxKind.ExpressionStatement)) {
|
|
1697
|
+
if (this.findContainingFunctionForNode(statement) !== func)
|
|
1698
|
+
continue;
|
|
1699
|
+
const expression = this.peelTransparentExpression(statement.getExpression());
|
|
1700
|
+
if (isSend(expression))
|
|
1701
|
+
sends.push({ statement, send: expression });
|
|
1702
|
+
}
|
|
1703
|
+
return sends;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* True when nothing in `func` runs after `statement`: a bare `return;`
|
|
1707
|
+
* follows it, or it is the last statement of its block and the statement
|
|
1708
|
+
* that block belongs to (an `if` branch, a `try` or `catch` block) is itself
|
|
1709
|
+
* last, up to `func`'s body. A statement inside a loop, a `switch` case or
|
|
1710
|
+
* any other construct does not end the path; a send there is not read as a
|
|
1711
|
+
* success body.
|
|
1712
|
+
*/
|
|
1713
|
+
statementEndsFunctionPath(statement, func) {
|
|
1714
|
+
const body = func.getBody();
|
|
1715
|
+
const next = statement.getNextSibling();
|
|
1716
|
+
if (next && Node.isReturnStatement(next) && !next.getExpression())
|
|
1717
|
+
return true;
|
|
1718
|
+
let current = statement;
|
|
1719
|
+
for (;;) {
|
|
1720
|
+
const parent = current.getParent();
|
|
1721
|
+
if (!parent)
|
|
1722
|
+
return false;
|
|
1723
|
+
if (Node.isIfStatement(parent)) {
|
|
1724
|
+
current = parent;
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
if (!Node.isBlock(parent))
|
|
1728
|
+
return false;
|
|
1729
|
+
const statements = parent.getStatements();
|
|
1730
|
+
if (statements[statements.length - 1] !== current)
|
|
1731
|
+
return false;
|
|
1732
|
+
if (parent === body)
|
|
1733
|
+
return true;
|
|
1734
|
+
const owner = parent.getParent();
|
|
1735
|
+
if (!owner)
|
|
1736
|
+
return false;
|
|
1737
|
+
if (Node.isIfStatement(owner) || Node.isTryStatement(owner) || Node.isBlock(owner)) {
|
|
1738
|
+
current = Node.isBlock(owner) ? parent : owner;
|
|
1739
|
+
continue;
|
|
1740
|
+
}
|
|
1741
|
+
if (Node.isCatchClause(owner)) {
|
|
1742
|
+
const tryStatement = owner.getParent();
|
|
1743
|
+
if (!tryStatement || !Node.isTryStatement(tryStatement))
|
|
1744
|
+
return false;
|
|
1745
|
+
current = tryStatement;
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1649
1751
|
/**
|
|
1650
1752
|
* True when `candidate` is a response send: a call or `new` whose result is
|
|
1651
1753
|
* transport machinery (by the extraction config's verified rule or the
|
|
@@ -5849,69 +5951,16 @@ export class TypeInferrer {
|
|
|
5849
5951
|
* The npm package name that declares a type. `undefined` when the type has
|
|
5850
5952
|
* no declaration to read (a top type, a primitive, an anonymous object
|
|
5851
5953
|
* literal) or when nothing owns its declaration but the workspace itself.
|
|
5852
|
-
*
|
|
5853
|
-
*
|
|
5854
|
-
* only thing that can answer for a project whose resolution does not go
|
|
5855
|
-
* through `node_modules`. Deno resolves an npm dependency's types straight
|
|
5856
|
-
* out of its own cache
|
|
5857
|
-
* (`$DENO_DIR/npm/<registry-host>/<name>/<version>/…`), a path with no
|
|
5858
|
-
* `node_modules` segment anywhere in it, so the scan below found no package
|
|
5859
|
-
* for ANY dependency-declared type on such a repo and every receiver the
|
|
5860
|
-
* compiler had typed correctly went unclassified (carrick#1260). It runs
|
|
5861
|
-
* first rather than second because a mixed checkout has both, and the graph
|
|
5862
|
-
* is what actually resolved the module.
|
|
5863
|
-
*
|
|
5864
|
-
* The path scan is the Node answer: the LAST `node_modules` segment wins,
|
|
5865
|
-
* which is what a nested or content-addressed store
|
|
5866
|
-
* (`node_modules/.store/pkg@1.0.0/node_modules/pkg`) requires. Scoped names
|
|
5867
|
-
* keep both segments.
|
|
5954
|
+
* The declaration is the attributed one (carrick#2031), and the package is
|
|
5955
|
+
* named by the module graph first, then by the path (`installedPackageOf`).
|
|
5868
5956
|
*/
|
|
5869
5957
|
declaringPackageOf(type) {
|
|
5870
5958
|
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
5871
|
-
const declaration = this.
|
|
5959
|
+
const declaration = attributedDeclaration(() => this.project.getProgram().compilerObject, (symbol?.getDeclarations() ?? []).map((node) => node.compilerNode));
|
|
5872
5960
|
if (!declaration) {
|
|
5873
5961
|
return undefined;
|
|
5874
5962
|
}
|
|
5875
|
-
|
|
5876
|
-
const named = this.packageOf?.(filePath);
|
|
5877
|
-
if (named) {
|
|
5878
|
-
return named;
|
|
5879
|
-
}
|
|
5880
|
-
const marker = '/node_modules/';
|
|
5881
|
-
const index = filePath.lastIndexOf(marker);
|
|
5882
|
-
if (index < 0) {
|
|
5883
|
-
return undefined;
|
|
5884
|
-
}
|
|
5885
|
-
const rest = filePath.slice(index + marker.length).split('/');
|
|
5886
|
-
if (rest.length === 0 || rest[0].length === 0) {
|
|
5887
|
-
return undefined;
|
|
5888
|
-
}
|
|
5889
|
-
if (rest[0].startsWith('@')) {
|
|
5890
|
-
return rest.length > 1 ? `${rest[0]}/${rest[1]}` : undefined;
|
|
5891
|
-
}
|
|
5892
|
-
return rest[0];
|
|
5893
|
-
}
|
|
5894
|
-
/**
|
|
5895
|
-
* The declaration a symbol is attributed to (carrick#2031). One declaration
|
|
5896
|
-
* is itself. Where several files merge one symbol (a global interface the
|
|
5897
|
-
* compiler's library declares and a typings package augments), their order
|
|
5898
|
-
* is the order the checker merged them in, and on TypeScript 6 that changes
|
|
5899
|
-
* once the program is rebuilt, so the first one is not an answer. The
|
|
5900
|
-
* compiler's default library wins where it declares the symbol; otherwise,
|
|
5901
|
-
* and among the library's own files, the declaring file whose path sorts
|
|
5902
|
-
* first, then the earliest declaration in it.
|
|
5903
|
-
*/
|
|
5904
|
-
attributedDeclaration(declarations) {
|
|
5905
|
-
if (declarations.length <= 1)
|
|
5906
|
-
return declarations[0];
|
|
5907
|
-
const program = this.project.getProgram().compilerObject;
|
|
5908
|
-
const fromLibrary = declarations.filter((declaration) => program.isSourceFileDefaultLibrary(declaration.getSourceFile().compilerNode));
|
|
5909
|
-
const candidates = fromLibrary.length > 0 ? fromLibrary : declarations;
|
|
5910
|
-
return [...candidates].sort((a, b) => {
|
|
5911
|
-
const pathA = a.getSourceFile().getFilePath();
|
|
5912
|
-
const pathB = b.getSourceFile().getFilePath();
|
|
5913
|
-
return pathA < pathB ? -1 : pathA > pathB ? 1 : a.getStart() - b.getStart();
|
|
5914
|
-
})[0];
|
|
5963
|
+
return installedPackageOf(declaration.getSourceFile().fileName, this.packageOf);
|
|
5915
5964
|
}
|
|
5916
5965
|
/**
|
|
5917
5966
|
* The awaited return type of the member invoked on the receiver. Reported as
|