@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.2
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/src/analysis.js +108 -10
- package/dist/src/augmentations.js +62 -18
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +50 -12
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +98 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +183 -28
- package/dist/src/discover.js +132 -11
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +172 -40
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +30 -5
- package/dist/src/findings/visibility-narrowing.js +29 -5
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +297 -45
- package/dist/src/inventory.js +52 -8
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +57 -10
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/overrides.js +41 -10
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +38 -4
- package/dist/src/report.js +4 -2
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +54 -8
- package/dist/src/session.js +7 -0
- package/dist/src/source-files.js +39 -14
- package/dist/src/suppress-file.js +10 -2
- package/dist/src/suppress-inline.js +18 -2
- package/dist/src/suppress.js +8 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/test-support.js +119 -18
- package/dist/src/type-errors.js +34 -7
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/verbs/analyze.js +16 -0
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +53 -10
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +146 -11
- package/src/augmentations.ts +76 -22
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +66 -12
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +128 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +247 -41
- package/src/discover.ts +156 -13
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +15 -0
- package/src/findings/completion.ts +222 -46
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +36 -5
- package/src/findings/visibility-narrowing.ts +40 -5
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +394 -59
- package/src/inventory.ts +71 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +104 -15
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/overrides.ts +51 -12
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +48 -3
- package/src/report.ts +16 -3
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +55 -4
- package/src/session.ts +7 -0
- package/src/source-files.ts +66 -16
- package/src/suppress-file.ts +12 -1
- package/src/suppress-inline.ts +18 -1
- package/src/suppress.ts +10 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/test-support.ts +155 -18
- package/src/type-errors.ts +38 -6
- package/src/type-parameter-reads.ts +227 -0
- package/src/verbs/analyze.ts +22 -0
- package/src/verbs/explain.ts +5 -2
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +62 -10
package/src/base-url.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { resolvePath } from "./paths.ts";
|
|
2
|
+
import type { Engine } from "./session.ts";
|
|
3
|
+
|
|
4
|
+
type CompilerOptions = ReturnType<Engine["parseConfigFile"]>["options"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `paths` one configuration's resolved options map under the compilers before version
|
|
8
|
+
* 7, each target absolute: a target is read against the effective `baseUrl`, and a specifier
|
|
9
|
+
* no other pattern maps is looked up below it. Undefined where no `baseUrl` is set, so the
|
|
10
|
+
* options' own `paths` stand. Version 7 reads no `baseUrl`:
|
|
11
|
+
* https://github.com/microsoft/TypeScript/issues/62207
|
|
12
|
+
*/
|
|
13
|
+
export function baseUrlPaths(options: CompilerOptions): Record<string, string[]> | undefined {
|
|
14
|
+
const baseUrl: unknown = Reflect.get(options, "baseUrl");
|
|
15
|
+
if (typeof baseUrl !== "string") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
Object.entries({ "*": ["*"], ...options.paths }).map(([key, targets]) => [
|
|
20
|
+
key,
|
|
21
|
+
targets.map((target) => resolvePath(baseUrl, target)),
|
|
22
|
+
]),
|
|
23
|
+
);
|
|
24
|
+
}
|
package/src/components.ts
CHANGED
|
@@ -208,12 +208,12 @@ function linesSpanned(symbols: readonly (InventorySymbol | undefined)[]): number
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* The dead declarations by subgraph place (`at`, and `position` per graph declaration),
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* test
|
|
211
|
+
* The dead declarations by subgraph place (`at`, and `position` per graph declaration), a
|
|
212
|
+
* test file's only where it tests dead code or nothing the tests reach. `adjacent` holds each
|
|
213
|
+
* reference one makes to another, but an unreferenced test-file declaration's to production
|
|
214
|
+
* code, which decides nothing; an edge each way between a dead member and its dead container;
|
|
215
|
+
* and one back from each non-ambient production target of an admitted test, which puts the
|
|
216
|
+
* test in its subject's component. `references` holds the first alone.
|
|
217
217
|
*/
|
|
218
218
|
function deadSubgraph(
|
|
219
219
|
graph: Graph,
|
|
@@ -248,6 +248,7 @@ function deadSubgraph(
|
|
|
248
248
|
if (
|
|
249
249
|
testOfDeadCode[index] === true &&
|
|
250
250
|
graph.test[edge.to] !== true &&
|
|
251
|
+
graph.ambient[edge.to] !== true &&
|
|
251
252
|
graph.subject[edge.to] === true
|
|
252
253
|
) {
|
|
253
254
|
adjacent[to]?.push(from);
|
package/src/config.ts
CHANGED
|
@@ -175,6 +175,8 @@ export interface TSSection {
|
|
|
175
175
|
readonly injectionRegistrations: readonly DeclarationEntry[];
|
|
176
176
|
readonly lifecycleContracts: readonly LifecycleContract[];
|
|
177
177
|
readonly serializers: readonly DeclarationEntry[];
|
|
178
|
+
/** Whether the maintainer declares the roots the analysis finds every root the target has. */
|
|
179
|
+
readonly rootsComplete: boolean;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
/**
|
|
@@ -253,6 +255,7 @@ export function defaultConfig(): Config {
|
|
|
253
255
|
injectionRegistrations: [],
|
|
254
256
|
lifecycleContracts: [],
|
|
255
257
|
serializers: [],
|
|
258
|
+
rootsComplete: false,
|
|
256
259
|
},
|
|
257
260
|
};
|
|
258
261
|
}
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
isCallExpression,
|
|
11
12
|
isExportDeclaration,
|
|
13
|
+
isIdentifier,
|
|
12
14
|
isImportDeclaration,
|
|
13
15
|
isNoSubstitutionTemplateLiteral,
|
|
14
16
|
isStringLiteral,
|
|
17
|
+
SyntaxKind,
|
|
15
18
|
type Node,
|
|
16
19
|
type SourceFile,
|
|
17
20
|
} from "@typescript/native/ast";
|
|
@@ -110,7 +113,7 @@ interface ConfigurationDocument {
|
|
|
110
113
|
export type ParseFile = (fileName: string, text: string) => SourceFile;
|
|
111
114
|
|
|
112
115
|
/** One file no program holds that a configuration module outside the program leads to. */
|
|
113
|
-
interface OutsideModule {
|
|
116
|
+
export interface OutsideModule {
|
|
114
117
|
/** The file, parsed alone. */
|
|
115
118
|
readonly file: SourceFile;
|
|
116
119
|
/** The directories of the manifests of the configuration files the file was reached from. */
|
|
@@ -439,19 +442,70 @@ export function configurationFiles(
|
|
|
439
442
|
};
|
|
440
443
|
}
|
|
441
444
|
|
|
442
|
-
/** The
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
|
|
445
|
+
/** The literal module specifier one `require(...)` or `import(...)` call names, if any. */
|
|
446
|
+
function calledSpecifier(node: Node): Node | undefined {
|
|
447
|
+
if (!isCallExpression(node) || node.arguments.length !== 1) {
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
const callee = node.expression;
|
|
451
|
+
const loads =
|
|
452
|
+
callee.kind === SyntaxKind.ImportKeyword || (isIdentifier(callee) && callee.text === "require");
|
|
453
|
+
return loads ? node.arguments[0] : undefined;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The relative specifiers one file's imports and re-exports name, and those its
|
|
458
|
+
* `require(...)` and `import(...)` calls name by a literal.
|
|
459
|
+
*/
|
|
460
|
+
export function relativeSpecifiers(file: SourceFile): readonly string[] {
|
|
461
|
+
const known = specifiersOf.get(file);
|
|
462
|
+
if (known !== undefined) {
|
|
463
|
+
return known;
|
|
464
|
+
}
|
|
465
|
+
const found: string[] = [];
|
|
466
|
+
const named = (specifier: Node | undefined): void => {
|
|
467
|
+
if (
|
|
468
|
+
specifier !== undefined &&
|
|
469
|
+
(isStringLiteral(specifier) || isNoSubstitutionTemplateLiteral(specifier)) &&
|
|
470
|
+
RELATIVE_PREFIXES.some((prefix) => specifier.text.startsWith(prefix))
|
|
471
|
+
) {
|
|
472
|
+
found.push(specifier.text);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
const visit = (node: Node): void => {
|
|
476
|
+
named(calledSpecifier(node));
|
|
477
|
+
node.forEachChild(visit);
|
|
478
|
+
};
|
|
479
|
+
for (const statement of file.statements) {
|
|
480
|
+
named(
|
|
446
481
|
isImportDeclaration(statement) || isExportDeclaration(statement)
|
|
447
482
|
? statement.moduleSpecifier
|
|
448
|
-
: undefined
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
483
|
+
: undefined,
|
|
484
|
+
);
|
|
485
|
+
visit(statement);
|
|
486
|
+
}
|
|
487
|
+
specifiersOf.set(file, found);
|
|
488
|
+
return found;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** What {@link relativeSpecifiers} found in each file, which every project of a run reads alike. */
|
|
492
|
+
const specifiersOf = new WeakMap<SourceFile, readonly string[]>();
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* The files no program holds that a chain from `seeds` reads: each seed, and each file
|
|
496
|
+
* below the target root one imports, re-exports or loads by a relative specifier that no
|
|
497
|
+
* program holds either, until no further file joins, each read against the manifest
|
|
498
|
+
* directory its seed names. A file that cannot be read is skipped.
|
|
499
|
+
*/
|
|
500
|
+
export function outsideChain(
|
|
501
|
+
host: Host,
|
|
502
|
+
targetRoot: string,
|
|
503
|
+
files: SourceFiles,
|
|
504
|
+
parse: ParseFile,
|
|
505
|
+
seeds: readonly { readonly path: string; readonly manifestDir: string }[],
|
|
506
|
+
): readonly OutsideModule[] {
|
|
507
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
508
|
+
return outsideModules(host, root, [], files, () => false, parse, seeds);
|
|
455
509
|
}
|
|
456
510
|
|
|
457
511
|
/**
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
|
-
corpusVersion: "
|
|
5
|
+
corpusVersion: "6.0.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:d65daae4b7c95050c07896b166d0250d233cca71f97de75a6086c1d80fe76232",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|
package/src/convention-rows.ts
CHANGED
|
@@ -31,6 +31,22 @@ export interface ShortNameReading extends MoveReading {
|
|
|
31
31
|
readonly package: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* One configuration property whose values name packages by the tool's prefix convention:
|
|
36
|
+
* `x` stands for `<prefix>-x`, `@s` for `@s/<prefix>` and `@s/x` for `@s/<prefix>-x`, and
|
|
37
|
+
* a name already holding the prefix for itself.
|
|
38
|
+
*/
|
|
39
|
+
export interface PrefixedNameReading extends MoveReading {
|
|
40
|
+
/** The prefix the convention puts before a short name. */
|
|
41
|
+
readonly prefix: string;
|
|
42
|
+
/**
|
|
43
|
+
* The text a value starts with to name a package, the name ending at its last `/`.
|
|
44
|
+
* Absent, every value without a `:` names one, the name ending at its first `/` past
|
|
45
|
+
* a scope.
|
|
46
|
+
*/
|
|
47
|
+
readonly marker?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
/**
|
|
35
51
|
* One configuration property whose value makes the tool's output import a runtime helper
|
|
36
52
|
* package, so the package is used where the property holds that value.
|
|
@@ -63,6 +79,14 @@ export interface TestFileReading extends MoveReading {
|
|
|
63
79
|
readonly roots: readonly string[];
|
|
64
80
|
}
|
|
65
81
|
|
|
82
|
+
/** One member of the declaring `package.json` whose strings name the tool's entries. */
|
|
83
|
+
export interface ManifestEntryKey {
|
|
84
|
+
/** The member's path through nested objects, its keys joined by `.`, a `*` key standing for every member. */
|
|
85
|
+
readonly key: string;
|
|
86
|
+
/** Whether each string names a directory of entries, or one entry, its extension optional. */
|
|
87
|
+
readonly directory: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
66
90
|
/** One directory a row's globs name that a configuration property moves. */
|
|
67
91
|
export interface DirectoryMove {
|
|
68
92
|
/** The placeholder the row's globs and later moves write as `<id>`. */
|
|
@@ -96,6 +120,8 @@ export interface ConventionRow {
|
|
|
96
120
|
readonly moves: readonly DirectoryMove[];
|
|
97
121
|
/** The properties that name a package the configuration uses by a short name. */
|
|
98
122
|
readonly shortNames?: readonly ShortNameReading[];
|
|
123
|
+
/** The properties that name a package the configuration uses by its prefix convention. */
|
|
124
|
+
readonly prefixedNames?: readonly PrefixedNameReading[];
|
|
99
125
|
/**
|
|
100
126
|
* The properties whose value only selects files to test, mutate, cover or ignore, so a
|
|
101
127
|
* string there names no file the tool runs or loads.
|
|
@@ -110,6 +136,8 @@ export interface ConventionRow {
|
|
|
110
136
|
readonly packageKeys?: readonly PackageKey[];
|
|
111
137
|
/** The members of a `package.json` the tool reads its configuration from. */
|
|
112
138
|
readonly manifestKeys?: readonly string[];
|
|
139
|
+
/** The members of the declaring `package.json` whose strings name entries, as an entry glob does. */
|
|
140
|
+
readonly manifestEntries?: readonly ManifestEntryKey[];
|
|
113
141
|
/** The settings that make the tool's output import a runtime helper package. */
|
|
114
142
|
readonly helpers?: readonly HelperReading[];
|
|
115
143
|
}
|
|
@@ -118,6 +146,8 @@ const SCRIPT = "{js,jsx,ts,tsx}";
|
|
|
118
146
|
const MODULE = "{js,mjs,ts,mts}";
|
|
119
147
|
const ANY_MODULE = "{js,mjs,cjs,ts,mts,cts}";
|
|
120
148
|
const VITE_CONFIG = `vite.config.${ANY_MODULE}`;
|
|
149
|
+
/** The legacy configuration files of ESLint that hold JSON or a CommonJS module. */
|
|
150
|
+
const ESLINTRC = [".eslintrc", ".eslintrc.json", ".eslintrc.{js,cjs}"];
|
|
121
151
|
|
|
122
152
|
const SVELTEKIT_ROUTE_FILES = "+{page,layout,server,page.server,layout.server,error}";
|
|
123
153
|
|
|
@@ -179,6 +209,31 @@ const ANGULAR_PACKAGE_KEYS: readonly PackageKey[] = [
|
|
|
179
209
|
"scripts",
|
|
180
210
|
].map((key) => ({ key, files: ["angular.json"] }));
|
|
181
211
|
|
|
212
|
+
/**
|
|
213
|
+
* The test runners and the assertion, mock and runner libraries whose import is test
|
|
214
|
+
* evidence beside each runner a row reads test files for. Each applies whether or not a
|
|
215
|
+
* manifest declares it, so none is a row.
|
|
216
|
+
*/
|
|
217
|
+
export const TEST_LIBRARIES: readonly string[] = [
|
|
218
|
+
"@jest/globals",
|
|
219
|
+
"@playwright/test",
|
|
220
|
+
"ava",
|
|
221
|
+
"chai",
|
|
222
|
+
"expect",
|
|
223
|
+
"jasmine",
|
|
224
|
+
"jest",
|
|
225
|
+
"mocha",
|
|
226
|
+
"msw",
|
|
227
|
+
"nock",
|
|
228
|
+
"node:test",
|
|
229
|
+
"sinon",
|
|
230
|
+
"tap",
|
|
231
|
+
"uvu",
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
/** The scopes every package of which is a test library. */
|
|
235
|
+
export const TEST_LIBRARY_SCOPES: readonly string[] = ["@testing-library/"];
|
|
236
|
+
|
|
182
237
|
/** Every row, ordered by name and then by range. */
|
|
183
238
|
export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
184
239
|
{
|
|
@@ -231,6 +286,25 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
231
286
|
package: "@babel/runtime",
|
|
232
287
|
})),
|
|
233
288
|
},
|
|
289
|
+
{
|
|
290
|
+
name: "docusaurus",
|
|
291
|
+
package: "@docusaurus/core",
|
|
292
|
+
range: ">=2.0.0 <4.0.0",
|
|
293
|
+
entries: [`src/pages/**/*.${SCRIPT}`, `src/theme/**/*.${SCRIPT}`],
|
|
294
|
+
moves: [],
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "eslint",
|
|
298
|
+
package: "eslint",
|
|
299
|
+
range: ">=4.0.0 <10.0.0",
|
|
300
|
+
entries: [],
|
|
301
|
+
moves: [],
|
|
302
|
+
prefixedNames: [
|
|
303
|
+
{ property: "plugins", files: ESLINTRC, prefix: "eslint-plugin" },
|
|
304
|
+
{ property: "extends", files: ESLINTRC, prefix: "eslint-plugin", marker: "plugin:" },
|
|
305
|
+
{ property: "extends", files: ESLINTRC, prefix: "eslint-config" },
|
|
306
|
+
],
|
|
307
|
+
},
|
|
234
308
|
{
|
|
235
309
|
name: "eslint",
|
|
236
310
|
package: "eslint",
|
|
@@ -238,6 +312,13 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
238
312
|
entries: [`**/eslint.config.${ANY_MODULE}`],
|
|
239
313
|
moves: [],
|
|
240
314
|
},
|
|
315
|
+
{
|
|
316
|
+
name: "expo",
|
|
317
|
+
package: "expo",
|
|
318
|
+
range: ">=44.0.0 <60.0.0",
|
|
319
|
+
entries: [`{index,App}.${SCRIPT}`],
|
|
320
|
+
moves: [],
|
|
321
|
+
},
|
|
241
322
|
{
|
|
242
323
|
name: "expo-router",
|
|
243
324
|
package: "expo-router",
|
|
@@ -322,6 +403,28 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
322
403
|
},
|
|
323
404
|
],
|
|
324
405
|
},
|
|
406
|
+
{
|
|
407
|
+
name: "oclif",
|
|
408
|
+
package: "@oclif/core",
|
|
409
|
+
range: ">=1.0.0 <5.0.0",
|
|
410
|
+
entries: [`src/{commands,hooks}/**/*.${SCRIPT}`],
|
|
411
|
+
moves: [],
|
|
412
|
+
manifestEntries: [
|
|
413
|
+
{ key: "oclif.commands", directory: true },
|
|
414
|
+
{ key: "oclif.hooks", directory: false },
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: "parcel",
|
|
419
|
+
package: "parcel",
|
|
420
|
+
range: ">=2.0.0 <3.0.0",
|
|
421
|
+
entries: [],
|
|
422
|
+
moves: [],
|
|
423
|
+
manifestEntries: [
|
|
424
|
+
{ key: "source", directory: false },
|
|
425
|
+
{ key: "targets.*.source", directory: false },
|
|
426
|
+
],
|
|
427
|
+
},
|
|
325
428
|
{
|
|
326
429
|
name: "prettier",
|
|
327
430
|
package: "prettier",
|
|
@@ -613,6 +716,31 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
613
716
|
},
|
|
614
717
|
],
|
|
615
718
|
},
|
|
719
|
+
{
|
|
720
|
+
name: "tanstack-router",
|
|
721
|
+
package: "@tanstack/router-plugin",
|
|
722
|
+
range: ">=1.0.0 <2.0.0",
|
|
723
|
+
entries: [`<routes>/**/*.${SCRIPT}`],
|
|
724
|
+
moves: [
|
|
725
|
+
{
|
|
726
|
+
id: "routes",
|
|
727
|
+
defaults: ["src/routes"],
|
|
728
|
+
readings: [
|
|
729
|
+
{ property: "routesDirectory", files: ["tsr.config.json"] },
|
|
730
|
+
{
|
|
731
|
+
property: "routesDirectory",
|
|
732
|
+
files: [VITE_CONFIG],
|
|
733
|
+
call: { module: "@tanstack/router-plugin/vite", export: "tanstackRouter" },
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
property: "routesDirectory",
|
|
737
|
+
files: [VITE_CONFIG],
|
|
738
|
+
call: { module: "@tanstack/router-plugin/vite", export: "TanStackRouterVite" },
|
|
739
|
+
},
|
|
740
|
+
],
|
|
741
|
+
},
|
|
742
|
+
],
|
|
743
|
+
},
|
|
616
744
|
{
|
|
617
745
|
name: "vite",
|
|
618
746
|
package: "vite",
|
package/src/conventions.ts
CHANGED
|
@@ -51,9 +51,11 @@ import {
|
|
|
51
51
|
type ConventionRow,
|
|
52
52
|
type DirectoryMove,
|
|
53
53
|
type HelperReading,
|
|
54
|
+
type ManifestEntryKey,
|
|
54
55
|
type MoveReading,
|
|
55
56
|
type OptionsCall,
|
|
56
57
|
type PackageKey,
|
|
58
|
+
type PrefixedNameReading,
|
|
57
59
|
type ShortNameReading,
|
|
58
60
|
type TestFileReading,
|
|
59
61
|
} from "./convention-rows.ts";
|
|
@@ -692,6 +694,47 @@ function manifestKeyUses(manifest: unknown, keys: readonly string[]): readonly s
|
|
|
692
694
|
);
|
|
693
695
|
}
|
|
694
696
|
|
|
697
|
+
/** The extensions an entry a manifest entry key names without one may carry. */
|
|
698
|
+
const ENTRY_EXTENSIONS = "{js,jsx,mjs,cjs,ts,tsx,mts,cts}";
|
|
699
|
+
|
|
700
|
+
/** The characters that make a manifest string a pattern rather than a directory's name. */
|
|
701
|
+
const PATTERN = /[*?{}[\]]/u;
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* The entry globs one applied row's manifest entry keys write, below the manifest's
|
|
705
|
+
* directory: a directory's every module, or a file with or without an extension. A string
|
|
706
|
+
* that leaves the directory names nothing.
|
|
707
|
+
*/
|
|
708
|
+
function manifestEntryGlobs(manifest: unknown, keys: readonly ManifestEntryKey[]): string[] {
|
|
709
|
+
const at = (value: unknown, path: readonly string[]): unknown[] => {
|
|
710
|
+
const [key, ...rest] = path;
|
|
711
|
+
if (key === undefined) {
|
|
712
|
+
return [value];
|
|
713
|
+
}
|
|
714
|
+
if (!isRecord(value)) {
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
717
|
+
const next = key === "*" ? Object.values(value) : Object.hasOwn(value, key) ? [value[key]] : [];
|
|
718
|
+
return next.flatMap((one) => at(one, rest));
|
|
719
|
+
};
|
|
720
|
+
return keys.flatMap(({ key, directory }) =>
|
|
721
|
+
at(manifest, key.split(".")).flatMap((value) =>
|
|
722
|
+
documentStrings(value).flatMap((text) => {
|
|
723
|
+
const path = normalizePath(text)
|
|
724
|
+
.replace(/^(?:\.\/)+/u, "")
|
|
725
|
+
.replace(/\/+$/u, "");
|
|
726
|
+
if (path === "" || path === ".." || path.startsWith("../") || path.startsWith("/")) {
|
|
727
|
+
return [];
|
|
728
|
+
}
|
|
729
|
+
if (!directory) {
|
|
730
|
+
return [path, `${path}.${ENTRY_EXTENSIONS}`];
|
|
731
|
+
}
|
|
732
|
+
return PATTERN.test(path) ? [] : [`${path}/**/*.${ENTRY_EXTENSIONS}`];
|
|
733
|
+
}),
|
|
734
|
+
),
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
695
738
|
/** What one configuration file states about one property. */
|
|
696
739
|
interface PropertyReading {
|
|
697
740
|
/** The literal values the file sets the property to. */
|
|
@@ -892,6 +935,66 @@ function shortNameUses(
|
|
|
892
935
|
return [...used].filter((name) => declared.has(name)).sort(compare);
|
|
893
936
|
}
|
|
894
937
|
|
|
938
|
+
/**
|
|
939
|
+
* The package one value of a prefixed-name reading names, or none: ESLint's own naming,
|
|
940
|
+
* https://github.com/eslint/eslintrc/blob/main/lib/shared/naming.js
|
|
941
|
+
*/
|
|
942
|
+
function prefixedPackage(value: string, reading: PrefixedNameReading): string | undefined {
|
|
943
|
+
const { prefix, marker } = reading;
|
|
944
|
+
let name: string;
|
|
945
|
+
if (marker === undefined) {
|
|
946
|
+
if (value.includes(":") || value.startsWith(".") || value.startsWith("/")) {
|
|
947
|
+
return undefined;
|
|
948
|
+
}
|
|
949
|
+
const parts = value.split("/");
|
|
950
|
+
name = (value.startsWith("@") ? parts.slice(0, 2) : parts.slice(0, 1)).join("/");
|
|
951
|
+
} else {
|
|
952
|
+
if (!value.startsWith(marker) || !value.includes("/", marker.length)) {
|
|
953
|
+
return undefined;
|
|
954
|
+
}
|
|
955
|
+
name = value.slice(marker.length, value.lastIndexOf("/"));
|
|
956
|
+
}
|
|
957
|
+
if (name === "") {
|
|
958
|
+
return undefined;
|
|
959
|
+
}
|
|
960
|
+
if (!name.startsWith("@")) {
|
|
961
|
+
return name.startsWith(`${prefix}-`) ? name : `${prefix}-${name}`;
|
|
962
|
+
}
|
|
963
|
+
const [scope, rest] = name.split("/");
|
|
964
|
+
if (rest === undefined || rest === "" || rest === prefix) {
|
|
965
|
+
return `${scope ?? name}/${prefix}`;
|
|
966
|
+
}
|
|
967
|
+
return rest.startsWith(`${prefix}-`) ? name : `${scope ?? name}/${prefix}-${rest}`;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/** The declared dependencies an applied row's prefixed names use. */
|
|
971
|
+
function prefixedNameUses(
|
|
972
|
+
host: Host,
|
|
973
|
+
parse: ParseSource,
|
|
974
|
+
dir: string,
|
|
975
|
+
manifest: unknown,
|
|
976
|
+
readings: readonly PrefixedNameReading[],
|
|
977
|
+
): readonly string[] {
|
|
978
|
+
const declared = new Set(
|
|
979
|
+
DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
980
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
981
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
982
|
+
}),
|
|
983
|
+
);
|
|
984
|
+
const used = new Set<string>();
|
|
985
|
+
for (const reading of readings) {
|
|
986
|
+
for (const { file } of readingFiles(host, parse, dir, reading.files)) {
|
|
987
|
+
for (const value of readProperty(file, reading.property, reading.call, true).values) {
|
|
988
|
+
const named = prefixedPackage(value, reading);
|
|
989
|
+
if (named !== undefined && declared.has(named)) {
|
|
990
|
+
used.add(named);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return [...used].sort(compare);
|
|
996
|
+
}
|
|
997
|
+
|
|
895
998
|
/** The declared dependencies an applied row's helper settings use. */
|
|
896
999
|
function helperUses(
|
|
897
1000
|
host: Host,
|
|
@@ -1064,6 +1167,10 @@ export function readConventions(
|
|
|
1064
1167
|
for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
|
|
1065
1168
|
uses.add(name);
|
|
1066
1169
|
}
|
|
1170
|
+
const prefixed = row.prefixedNames ?? [];
|
|
1171
|
+
for (const name of prefixedNameUses(host, parse, manifest.dir, declared, prefixed)) {
|
|
1172
|
+
uses.add(name);
|
|
1173
|
+
}
|
|
1067
1174
|
const keys = row.packageKeys ?? [];
|
|
1068
1175
|
for (const name of packageKeyUses(host, parse, manifest.dir, declared, keys)) {
|
|
1069
1176
|
uses.add(name);
|
|
@@ -1082,7 +1189,11 @@ export function readConventions(
|
|
|
1082
1189
|
for (const below of new Set((row.generated ?? []).flatMap((one) => expand(one, resolved)))) {
|
|
1083
1190
|
generated.push({ dir: manifest.dir, row: row.name, below });
|
|
1084
1191
|
}
|
|
1085
|
-
|
|
1192
|
+
const written = [
|
|
1193
|
+
...row.entries.flatMap((entry) => expand(entry, resolved)),
|
|
1194
|
+
...manifestEntryGlobs(declared, row.manifestEntries ?? []),
|
|
1195
|
+
];
|
|
1196
|
+
for (const glob of new Set(written)) {
|
|
1086
1197
|
globs.push({
|
|
1087
1198
|
dir: manifest.dir,
|
|
1088
1199
|
row: row.name,
|
|
@@ -1138,12 +1249,15 @@ export function conventionEntries(
|
|
|
1138
1249
|
const found = new Map<string, ConventionEntry>();
|
|
1139
1250
|
for (const glob of conventions.globs) {
|
|
1140
1251
|
for (const file of files.byName.values()) {
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1252
|
+
const matched = [file.fileName, ...files.writtenAs(file)].some((name) => {
|
|
1253
|
+
const path = relativePath(glob.dir, name);
|
|
1254
|
+
return (
|
|
1255
|
+
path !== undefined &&
|
|
1256
|
+
glob.expression.test(path) &&
|
|
1257
|
+
!glob.excludes.some((one) => one.test(path))
|
|
1258
|
+
);
|
|
1259
|
+
});
|
|
1260
|
+
if (matched) {
|
|
1147
1261
|
found.set(`${file.fileName}\u0000${glob.row}`, { file, row: glob.row });
|
|
1148
1262
|
}
|
|
1149
1263
|
}
|
package/src/decorator.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The decorator class: a member a decorator expression is attached to, and every
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The use the decorator itself makes of what it decorates is the reference pass's,
|
|
9
|
-
* which records it as a `decorator` reference; this class records only that the
|
|
10
|
-
* declaration is held back, and the member set of a decorated class is every member
|
|
11
|
-
* the inventory holds for the class, static members included.
|
|
2
|
+
* The decorator class: a member a decorator expression is attached to, and every member
|
|
3
|
+
* of a class one is attached to but the member a parameter property declares, is held
|
|
4
|
+
* back, because the decorator may reach the member by its name. The rule is read from
|
|
5
|
+
* the tree alone. The decorator's own use of what it decorates is the reference pass's
|
|
6
|
+
* `decorator` reference; this class records only that the declaration is held back,
|
|
7
|
+
* static members of a decorated class included.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
10
|
import type { Node } from "@typescript/native/ast";
|
|
@@ -44,25 +40,39 @@ export function decorator<Brand>(input: DetectorInput<Brand>): readonly Evidence
|
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
const
|
|
43
|
+
const decorated: {
|
|
44
|
+
readonly symbol: InventorySymbol;
|
|
45
|
+
readonly detail: string;
|
|
46
|
+
readonly site: Evidence["site"];
|
|
47
|
+
}[] = [];
|
|
48
|
+
// A decorated class's container passes each constructor argument, which keeps the
|
|
49
|
+
// parameter, not the member a parameter property stores it in.
|
|
50
|
+
const parameterMembers = new Set<string>();
|
|
48
51
|
for (const file of project.ownSourceFiles()) {
|
|
49
52
|
const visit = (node: Node): void => {
|
|
50
53
|
const id = held.declarations.get(nodeKey(file, node));
|
|
51
54
|
const symbol = id === undefined ? undefined : byId.get(id);
|
|
55
|
+
if (id !== undefined && isParameterProperty(node)) {
|
|
56
|
+
parameterMembers.add(id);
|
|
57
|
+
}
|
|
52
58
|
// A parameter property's decorator is attached to its parameter, and to no member.
|
|
53
59
|
if (symbol !== undefined && DECORATED.has(symbol.kind) && !isParameterProperty(node)) {
|
|
54
60
|
for (const attached of decoratorsOf(node)) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
+
decorated.push({
|
|
62
|
+
symbol,
|
|
63
|
+
detail: `decorated by ${spelledDecorator(attached)}`,
|
|
64
|
+
site: renderPosition(file, targetRoot, attached.getStart()),
|
|
65
|
+
});
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
node.forEachChild(visit);
|
|
64
69
|
};
|
|
65
70
|
file.forEachChild(visit);
|
|
66
71
|
}
|
|
67
|
-
return
|
|
72
|
+
return decorated.flatMap(({ symbol, detail, site }) =>
|
|
73
|
+
(symbol.kind === "class"
|
|
74
|
+
? (membersOf.get(symbol.id) ?? []).filter((member) => !parameterMembers.has(member.id))
|
|
75
|
+
: [symbol]
|
|
76
|
+
).map((member) => ({ id: member.id, detail, site })),
|
|
77
|
+
);
|
|
68
78
|
}
|