carrick 0.3.123 → 0.3.125
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 +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/check-classify.js +11 -5
- package/sidecar/dist/src/capture/check.js +8 -1
- package/sidecar/dist/src/capture/deep-walk.d.ts +31 -0
- package/sidecar/dist/src/capture/deep-walk.js +43 -1
- package/sidecar/dist/src/capture/index.d.ts +1 -1
- package/sidecar/dist/src/capture/index.js +1 -1
- package/sidecar/dist/src/capture/machinery.js +6 -0
- package/sidecar/dist/src/capture/service-config.d.ts +11 -0
- package/sidecar/dist/src/capture/service-config.js +40 -0
- package/sidecar/dist/src/index.js +1 -1
- package/sidecar/dist/src/library-claims.d.ts +1 -1
- package/sidecar/dist/src/library-claims.js +170 -11
- package/sidecar/dist/src/position-rows.d.ts +44 -0
- package/sidecar/dist/src/position-rows.js +6 -4
- package/sidecar/dist/src/project-loader.d.ts +16 -0
- package/sidecar/dist/src/project-loader.js +51 -4
- package/sidecar/dist/src/type-inferrer.d.ts +56 -15
- package/sidecar/dist/src/type-inferrer.js +328 -67
- package/sidecar/dist/src/types.d.ts +32 -0
- package/sidecar/dist/src/validators.d.ts +392 -60
- package/sidecar/dist/src/validators.js +14 -0
|
@@ -107,5 +107,49 @@ export interface PositionRowsOptions {
|
|
|
107
107
|
* a directory inside the scanned trees anywhere but beneath `.carrick`.
|
|
108
108
|
*/
|
|
109
109
|
export declare function writePositionRows(options: PositionRowsOptions): PositionRowsCounts;
|
|
110
|
+
/** What a declaration's file is written as in a row's `decl_file`: `lib:<name>` for the default library, else `PathNames`'. */
|
|
111
|
+
export declare function declaringFileName(program: ts.Program, paths: PathNames, file: ts.SourceFile): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Names for the files rows point at, never an absolute path: relative to the
|
|
114
|
+
* scanned repo's root when inside it (its real path too, so a root reached
|
|
115
|
+
* through a link still contains its files), `lib:<name>` for the compiler's
|
|
116
|
+
* default library, `pkg:<name>/<path>` for an installed package outside the
|
|
117
|
+
* root, and nothing otherwise.
|
|
118
|
+
*/
|
|
119
|
+
export declare class PathNames {
|
|
120
|
+
private readonly roots;
|
|
121
|
+
private readonly packageNames;
|
|
122
|
+
/**
|
|
123
|
+
* Answers by declaring file: a service's rows name a few thousand files
|
|
124
|
+
* hundreds of thousands of times, and the module graph's answer is a scan
|
|
125
|
+
* of its packages (on a Deno service it was 40% of the walk).
|
|
126
|
+
*/
|
|
127
|
+
private readonly declaringFiles;
|
|
128
|
+
private readonly declaringPackages;
|
|
129
|
+
constructor(scanRoot: string);
|
|
130
|
+
/** The root `file` lies under, and its path from there; `undefined` outside every root. */
|
|
131
|
+
private inside;
|
|
132
|
+
/** A request file's name: relative to the root, which every file the scanner sends is inside. */
|
|
133
|
+
relative(file: string): string;
|
|
134
|
+
/** What a declaring file is written as, or `null` when nothing names it. */
|
|
135
|
+
declaringFile(fileName: string): string | null;
|
|
136
|
+
private nameDeclaringFile;
|
|
137
|
+
/**
|
|
138
|
+
* The package a declaring file belongs to: the module graph's or the path's
|
|
139
|
+
* answer (`installedPackageOf`), else, for a file inside the root, the
|
|
140
|
+
* `name` of the nearest package.json at or above it, up to the root.
|
|
141
|
+
*/
|
|
142
|
+
declaringPackage(fileName: string, packageOf: (filePath: string) => string | undefined): string | null;
|
|
143
|
+
private nameDeclaringPackage;
|
|
144
|
+
private packageNameIn;
|
|
145
|
+
/**
|
|
146
|
+
* `import("/abs/path")` in a printed type, written with the declaring
|
|
147
|
+
* file's name instead. The print has no enclosing node, so it names every
|
|
148
|
+
* module by the file that declares it and one type reads the same from
|
|
149
|
+
* every file; it also skips the accessibility search an enclosing node
|
|
150
|
+
* costs (40% of the walk on a 600-file service).
|
|
151
|
+
*/
|
|
152
|
+
scrubType(text: string): string;
|
|
153
|
+
}
|
|
110
154
|
/** The kind of row a node gives, if any (exported for the editor-parity test, carrick#2418). */
|
|
111
155
|
export declare function kindOf(node: ts.Node): NodeKind | undefined;
|
|
@@ -242,6 +242,10 @@ export function writePositionRows(options) {
|
|
|
242
242
|
counts.load_ms = Math.round(counts.load_ms);
|
|
243
243
|
return counts;
|
|
244
244
|
}
|
|
245
|
+
/** What a declaration's file is written as in a row's `decl_file`: `lib:<name>` for the default library, else `PathNames`'. */
|
|
246
|
+
export function declaringFileName(program, paths, file) {
|
|
247
|
+
return program.isSourceFileDefaultLibrary(file) ? `lib:${path.basename(file.fileName)}` : paths.declaringFile(file.fileName);
|
|
248
|
+
}
|
|
245
249
|
/** A file's line, with its keys in the pinned order. */
|
|
246
250
|
function fileLine(line) {
|
|
247
251
|
return JSON.stringify(line);
|
|
@@ -253,7 +257,7 @@ function fileLine(line) {
|
|
|
253
257
|
* default library, `pkg:<name>/<path>` for an installed package outside the
|
|
254
258
|
* root, and nothing otherwise.
|
|
255
259
|
*/
|
|
256
|
-
class PathNames {
|
|
260
|
+
export class PathNames {
|
|
257
261
|
roots;
|
|
258
262
|
packageNames = new Map();
|
|
259
263
|
/**
|
|
@@ -466,9 +470,7 @@ class PositionWalker {
|
|
|
466
470
|
}
|
|
467
471
|
/** How rows name a position in a program file: its file, 1-based line and package. */
|
|
468
472
|
located(declaringFile, position) {
|
|
469
|
-
const declFile = this.program.
|
|
470
|
-
? `lib:${path.basename(declaringFile.fileName)}`
|
|
471
|
-
: this.paths.declaringFile(declaringFile.fileName);
|
|
473
|
+
const declFile = declaringFileName(this.program, this.paths, declaringFile);
|
|
472
474
|
const declLine = declaringFile.getLineAndCharacterOfPosition(position).line + 1;
|
|
473
475
|
const declPackage = this.paths.declaringPackage(declaringFile.fileName, (file) => this.loader.packageOf(file));
|
|
474
476
|
return { declFile, declLine, declPackage };
|
|
@@ -80,6 +80,16 @@ export declare class ProjectLoader {
|
|
|
80
80
|
private denoProject;
|
|
81
81
|
/** The tsconfig the service names, when the project is built from one. */
|
|
82
82
|
private namedTsconfigPath;
|
|
83
|
+
/**
|
|
84
|
+
* Set when the service has no config at or above its root: each file is then
|
|
85
|
+
* typed under the nearest `tsconfig.json`/`jsconfig.json` on its way up to
|
|
86
|
+
* the service root (carrick#1604), and the default project holds the rest.
|
|
87
|
+
*/
|
|
88
|
+
private inferredService;
|
|
89
|
+
/** The per-directory answers of `fileConfigPath` for this service. */
|
|
90
|
+
private readonly fileConfigs;
|
|
91
|
+
/** The reference owner rule of each config a file was found under. */
|
|
92
|
+
private readonly ownerRules;
|
|
83
93
|
/** Each file's owning config (carrick#1604), resolved on first use. */
|
|
84
94
|
private configPathFor;
|
|
85
95
|
/** Programs built for owning projects other than the named tsconfig. */
|
|
@@ -128,6 +138,12 @@ export declare class ProjectLoader {
|
|
|
128
138
|
* as before.
|
|
129
139
|
*/
|
|
130
140
|
projectKeyFor(file?: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* `projectKeyFor` for a service with no config at its root: the owner of the
|
|
143
|
+
* nearest config on the file's way up to the service root, or `''` (the
|
|
144
|
+
* default project, under the inferred options) for a file with none.
|
|
145
|
+
*/
|
|
146
|
+
private inferredKeyFor;
|
|
131
147
|
/**
|
|
132
148
|
* The project behind a key from `projectKeyFor`, built on first use and
|
|
133
149
|
* kept: a service whose files all belong to one project builds one.
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { Project, getCompilerOptionsFromTsConfig } from 'ts-morph';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
+
import ts from 'typescript';
|
|
14
15
|
import * as fs from 'node:fs';
|
|
15
|
-
import { DenoProject, findDenoConfig, findServiceTsconfig, referenceRedirectOf, serviceConfigPath, sidecarCompilerOptions, untrustedReadBound, } from './capture/index.js';
|
|
16
|
+
import { DenoProject, findDenoConfig, fileConfigPath, findServiceTsconfig, referenceRedirectOf, serviceConfigPath, sidecarCompilerOptions, untrustedReadBound, } from './capture/index.js';
|
|
16
17
|
import { moduleFormatResolutionHost } from './module-format.js';
|
|
17
18
|
import { ExternalImports, registerExternalImports } from './origin.js';
|
|
18
19
|
/**
|
|
@@ -92,6 +93,11 @@ const TARGET_MAP = {
|
|
|
92
93
|
'ES2023': 10,
|
|
93
94
|
'ESNext': 99,
|
|
94
95
|
};
|
|
96
|
+
/** Whether the config's own `compilerOptions` state `maxNodeModuleJsDepth`. */
|
|
97
|
+
function statesJsDepth(configPath) {
|
|
98
|
+
const raw = ts.readConfigFile(configPath, ts.sys.readFile).config;
|
|
99
|
+
return raw?.compilerOptions?.maxNodeModuleJsDepth !== undefined;
|
|
100
|
+
}
|
|
95
101
|
/**
|
|
96
102
|
* ProjectLoader - Manages ts-morph Project initialization and access
|
|
97
103
|
*
|
|
@@ -126,6 +132,16 @@ export class ProjectLoader {
|
|
|
126
132
|
denoProject;
|
|
127
133
|
/** The tsconfig the service names, when the project is built from one. */
|
|
128
134
|
namedTsconfigPath;
|
|
135
|
+
/**
|
|
136
|
+
* Set when the service has no config at or above its root: each file is then
|
|
137
|
+
* typed under the nearest `tsconfig.json`/`jsconfig.json` on its way up to
|
|
138
|
+
* the service root (carrick#1604), and the default project holds the rest.
|
|
139
|
+
*/
|
|
140
|
+
inferredService = false;
|
|
141
|
+
/** The per-directory answers of `fileConfigPath` for this service. */
|
|
142
|
+
fileConfigs = new Map();
|
|
143
|
+
/** The reference owner rule of each config a file was found under. */
|
|
144
|
+
ownerRules = new Map();
|
|
129
145
|
/** Each file's owning config (carrick#1604), resolved on first use. */
|
|
130
146
|
configPathFor;
|
|
131
147
|
/** Programs built for owning projects other than the named tsconfig. */
|
|
@@ -231,6 +247,7 @@ export class ProjectLoader {
|
|
|
231
247
|
}
|
|
232
248
|
else {
|
|
233
249
|
this.log('No tsconfig.json found, using default compiler options');
|
|
250
|
+
this.inferredService = true;
|
|
234
251
|
this.buildProject = (kind) => {
|
|
235
252
|
const project = new Project({
|
|
236
253
|
...this.fileSystemOption(),
|
|
@@ -239,7 +256,7 @@ export class ProjectLoader {
|
|
|
239
256
|
});
|
|
240
257
|
// Add source files from common locations
|
|
241
258
|
if (kind === 'listed')
|
|
242
|
-
this.addDefaultSourceFiles(project);
|
|
259
|
+
this.addDefaultSourceFiles(project, (file) => this.projectKeyFor(file) === '');
|
|
243
260
|
return project;
|
|
244
261
|
};
|
|
245
262
|
}
|
|
@@ -347,11 +364,18 @@ export class ProjectLoader {
|
|
|
347
364
|
*/
|
|
348
365
|
projectFromConfig(configPath, kind = 'listed') {
|
|
349
366
|
const imports = new ExternalImports();
|
|
367
|
+
const options = getCompilerOptionsFromTsConfig(configPath).options;
|
|
368
|
+
// TypeScript reads a `jsconfig.json` with an implicit depth of 2, which
|
|
369
|
+
// loads the JavaScript sources of installed packages; every other program
|
|
370
|
+
// here reads 0. A depth the config states is kept (carrick#1604).
|
|
371
|
+
if (this.inferredService && path.basename(configPath) === 'jsconfig.json' && !statesJsDepth(configPath)) {
|
|
372
|
+
options.maxNodeModuleJsDepth = 0;
|
|
373
|
+
}
|
|
350
374
|
const project = new Project({
|
|
351
375
|
...this.fileSystemOption(),
|
|
352
376
|
tsConfigFilePath: configPath,
|
|
353
377
|
// Over the tsconfig's own options: ts-morph lays these on top.
|
|
354
|
-
compilerOptions: sidecarCompilerOptions(optionsFor(kind,
|
|
378
|
+
compilerOptions: sidecarCompilerOptions(optionsFor(kind, options)),
|
|
355
379
|
skipAddingFilesFromTsConfig: kind === 'scripts',
|
|
356
380
|
// Each import resolves in its file's own module format (carrick#1619),
|
|
357
381
|
// a referenced project's unbuilt output reads as its source, under its
|
|
@@ -373,12 +397,33 @@ export class ProjectLoader {
|
|
|
373
397
|
* as before.
|
|
374
398
|
*/
|
|
375
399
|
projectKeyFor(file) {
|
|
400
|
+
if (this.inferredService)
|
|
401
|
+
return this.inferredKeyFor(file);
|
|
376
402
|
if (!this.namedTsconfigPath || file === undefined)
|
|
377
403
|
return '';
|
|
378
404
|
this.configPathFor ??= serviceConfigPath(this.namedTsconfigPath, (d) => this.logError(d));
|
|
379
405
|
const config = this.configPathFor(path.resolve(this.repoRoot, file));
|
|
380
406
|
return config === this.namedTsconfigPath ? '' : config;
|
|
381
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* `projectKeyFor` for a service with no config at its root: the owner of the
|
|
410
|
+
* nearest config on the file's way up to the service root, or `''` (the
|
|
411
|
+
* default project, under the inferred options) for a file with none.
|
|
412
|
+
*/
|
|
413
|
+
inferredKeyFor(file) {
|
|
414
|
+
if (file === undefined)
|
|
415
|
+
return '';
|
|
416
|
+
const absolute = path.resolve(this.repoRoot, file);
|
|
417
|
+
const config = fileConfigPath(absolute, this.repoRoot, this.fileConfigs);
|
|
418
|
+
if (config === undefined)
|
|
419
|
+
return '';
|
|
420
|
+
let owner = this.ownerRules.get(config);
|
|
421
|
+
if (!owner) {
|
|
422
|
+
owner = serviceConfigPath(config, (d) => this.logError(d));
|
|
423
|
+
this.ownerRules.set(config, owner);
|
|
424
|
+
}
|
|
425
|
+
return owner(absolute);
|
|
426
|
+
}
|
|
382
427
|
/**
|
|
383
428
|
* The project behind a key from `projectKeyFor`, built on first use and
|
|
384
429
|
* kept: a service whose files all belong to one project builds one.
|
|
@@ -512,7 +557,7 @@ export class ProjectLoader {
|
|
|
512
557
|
* dependencies installed it cost 40 s and added nothing the compiler needs
|
|
513
558
|
* (carrick#749).
|
|
514
559
|
*/
|
|
515
|
-
addDefaultSourceFiles(project) {
|
|
560
|
+
addDefaultSourceFiles(project, keep = () => true) {
|
|
516
561
|
const globs = DEFAULT_SOURCE_PATTERNS.map((pattern) => path.join(this.repoRoot, pattern));
|
|
517
562
|
// Negated last so it applies to every pattern above it.
|
|
518
563
|
globs.push(`!${path.join(this.repoRoot, '**/node_modules/**')}`);
|
|
@@ -525,6 +570,8 @@ export class ProjectLoader {
|
|
|
525
570
|
return;
|
|
526
571
|
}
|
|
527
572
|
for (const filePath of filePaths) {
|
|
573
|
+
if (!keep(filePath))
|
|
574
|
+
continue;
|
|
528
575
|
try {
|
|
529
576
|
project.addSourceFileAtPath(filePath);
|
|
530
577
|
}
|
|
@@ -1800,8 +1800,10 @@ export declare class TypeInferrer {
|
|
|
1800
1800
|
* - a cast on it: `(await request.json()) as NewWidget`;
|
|
1801
1801
|
* - the annotation of the binding it initialises:
|
|
1802
1802
|
* `const input: NewWidget = await request.json()`;
|
|
1803
|
-
* - a
|
|
1804
|
-
* (`
|
|
1803
|
+
* - a type the handler states for the read further on
|
|
1804
|
+
* (`statedInFunction`): a schema it is handed to, the annotation of a
|
|
1805
|
+
* name it is assigned to, a pass-through function, a cast of the whole
|
|
1806
|
+
* value.
|
|
1805
1807
|
*
|
|
1806
1808
|
* The first two are `typeNodeStatedAtRead`, the reading a consumer's
|
|
1807
1809
|
* response read already gets. An annotation further out is not one of them:
|
|
@@ -2030,19 +2032,58 @@ export declare class TypeInferrer {
|
|
|
2030
2032
|
*/
|
|
2031
2033
|
private middlewareBodySchemaValues;
|
|
2032
2034
|
/**
|
|
2033
|
-
* The
|
|
2034
|
-
* (carrick#
|
|
2035
|
-
*
|
|
2036
|
-
*
|
|
2037
|
-
*
|
|
2038
|
-
*
|
|
2039
|
-
*
|
|
2040
|
-
* (`
|
|
2041
|
-
*
|
|
2042
|
-
*
|
|
2043
|
-
*
|
|
2044
|
-
|
|
2045
|
-
|
|
2035
|
+
* The type the function a value is made in states for it, where the
|
|
2036
|
+
* compiler types the value itself `any` or `unknown` (carrick#2594), or
|
|
2037
|
+
* null.
|
|
2038
|
+
*
|
|
2039
|
+
* `value` is the located expression: a body read, or a call whose result is
|
|
2040
|
+
* untyped. The value is followed through the names that hold it: a
|
|
2041
|
+
* declaration it initialises, and a name it is assigned to later
|
|
2042
|
+
* (`let body: unknown; body = await read()`). At each place the value or
|
|
2043
|
+
* one of those names is used whole, the source can state a type:
|
|
2044
|
+
*
|
|
2045
|
+
* 0. the annotation of the name that holds it (`let body: Upload`), or the
|
|
2046
|
+
* declared return type of the function that returns it unchanged (only
|
|
2047
|
+
* with `reads.returned`);
|
|
2048
|
+
* 1. a schema it is handed to (`Schema.safeParse(body ?? {})`,
|
|
2049
|
+
* `parse(Schema, body)`): the schema's input is what a sender may send
|
|
2050
|
+
* (carrick#1166). A candidate counts only when it declares schema types,
|
|
2051
|
+
* so no method or library name is matched;
|
|
2052
|
+
* 2. a function of the project it is handed to that returns that argument
|
|
2053
|
+
* unchanged (through casts) or an empty default: its declared return
|
|
2054
|
+
* type. A function that builds a new value from its argument is a
|
|
2055
|
+
* converter, and its return type describes what it built, never the
|
|
2056
|
+
* value it was handed, so it states nothing here;
|
|
2057
|
+
* 3. a cast of the whole value (`(body as { phone: string }).phone`);
|
|
2058
|
+
* 4. the parameter of a call it is handed to (only with
|
|
2059
|
+
* `reads.parameters`), where that parameter's type is a body shape once
|
|
2060
|
+
* any callable member is dropped (a function never crosses the wire).
|
|
2061
|
+
*
|
|
2062
|
+
* The first rank with an answer decides. A placeholder (`unknown`,
|
|
2063
|
+
* `Record<string, unknown>`, any type node with `any` or `unknown` written
|
|
2064
|
+
* in it) states nothing, and two different statements in one rank decide
|
|
2065
|
+
* nothing: the answer is null.
|
|
2066
|
+
*/
|
|
2067
|
+
private statedInFunction;
|
|
2068
|
+
/**
|
|
2069
|
+
* What a call that takes the value at argument `index` states for it:
|
|
2070
|
+
* a schema among its receiver and other arguments (rank 1), a callee of the
|
|
2071
|
+
* project that returns that argument unchanged (rank 2), or, with
|
|
2072
|
+
* `reads.parameters`, the parameter's own type (rank 4).
|
|
2073
|
+
*/
|
|
2074
|
+
private stateAtReceivingCall;
|
|
2075
|
+
/**
|
|
2076
|
+
* The project function `call` invokes, when every value it returns is its
|
|
2077
|
+
* argument at `index` as it was handed in (through parentheses, casts,
|
|
2078
|
+
* `satisfies` and `!`) or an empty default (`{}`, `[]`, `null`,
|
|
2079
|
+
* `undefined`), and at least one return is that argument. Such a function
|
|
2080
|
+
* narrows its argument, so its declared return type describes the value it
|
|
2081
|
+
* was handed. Any other function builds something new from it, and is not
|
|
2082
|
+
* this.
|
|
2083
|
+
*/
|
|
2084
|
+
private passThroughFunction;
|
|
2085
|
+
/** `{}`, `[]`, `null` or `undefined`: a default that states no member. */
|
|
2086
|
+
private isEmptyDefault;
|
|
2046
2087
|
/**
|
|
2047
2088
|
* The part a validated read names when that part is not a body, or
|
|
2048
2089
|
* `undefined` (carrick#1166).
|