@cplieger/deadset-ts 5.1.1 → 5.2.1
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 +7 -4
- package/dist/src/config.js +3 -0
- package/dist/src/configuration-files.js +23 -24
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +25 -4
- package/dist/src/convention-rows.js +7 -0
- package/dist/src/conventions.js +97 -33
- package/dist/src/dependencies.js +8 -0
- package/dist/src/entry-point-gaps.js +10 -26
- package/dist/src/entry-points.js +4 -0
- package/dist/src/findings/completion.js +27 -11
- package/dist/src/findings/interfaces.js +2 -6
- package/dist/src/findings/intra-function.js +15 -3
- package/dist/src/findings/unused-declarations.js +5 -6
- package/dist/src/findings/visibility-narrowing.js +6 -1
- package/dist/src/generated-file.js +72 -12
- package/dist/src/graph.js +5 -4
- package/dist/src/html-entries.js +137 -0
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +5 -1
- package/dist/src/matrix.js +9 -3
- package/dist/src/namespace-objects.js +158 -0
- package/dist/src/partial.js +1 -1
- package/dist/src/references.js +120 -3
- package/dist/src/roots.js +52 -3
- package/dist/src/sweep.js +17 -0
- package/dist/src/test-support.js +35 -23
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +12 -5
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +28 -24
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +31 -4
- package/src/convention-rows.ts +15 -0
- package/src/conventions.ts +119 -30
- package/src/dependencies.ts +9 -0
- package/src/entry-point-gaps.ts +11 -28
- package/src/entry-points.ts +7 -1
- package/src/exempt.ts +1 -4
- package/src/file-facts.ts +1 -1
- package/src/findings/completion.ts +31 -11
- package/src/findings/interfaces.ts +2 -9
- package/src/findings/intra-function.ts +17 -3
- package/src/findings/unused-declarations.ts +5 -8
- package/src/findings/visibility-narrowing.ts +7 -1
- package/src/generated-file.ts +80 -12
- package/src/graph.ts +5 -4
- package/src/html-entries.ts +170 -0
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +9 -3
- package/src/matrix.ts +21 -3
- package/src/namespace-objects.ts +216 -0
- package/src/partial.ts +1 -1
- package/src/references.ts +161 -3
- package/src/roots.ts +69 -10
- package/src/sweep.ts +19 -0
- package/src/test-support.ts +34 -22
- package/src/version.ts +1 -1
package/dist/src/analysis.js
CHANGED
|
@@ -11,13 +11,13 @@ import { lineOf, readComponent } from "./component-files.js";
|
|
|
11
11
|
import { consumerLoads, consumerReferences, moduleIdentity, } from "./consumers.js";
|
|
12
12
|
import { configuredDeclarations, configuredRef, declarationsNamed, } from "./configured-declarations.js";
|
|
13
13
|
import { decorator } from "./decorator.js";
|
|
14
|
-
import { dependenciesOf, projectNeeds } from "./dependencies.js";
|
|
14
|
+
import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.js";
|
|
15
15
|
import { deprecatedDeclarations } from "./deprecation.js";
|
|
16
16
|
import { discoverProjects, DiscoveryError, isGuess, projectErrors, renderDiagnostic, targetRelative, } from "./discover.js";
|
|
17
17
|
import { conventionFailuresFor, generatedFiles, readConventions, } from "./conventions.js";
|
|
18
18
|
import { readEdgeSides } from "./edges.js";
|
|
19
19
|
import { enumGroup } from "./enum-group.js";
|
|
20
|
-
import { generatedFile } from "./generated-file.js";
|
|
20
|
+
import { generatedFile, generatedFilesOf } from "./generated-file.js";
|
|
21
21
|
import { sourceFilesOf } from "./source-files.js";
|
|
22
22
|
import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
|
|
23
23
|
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
|
|
@@ -224,6 +224,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
224
224
|
...kept.map((id) => ({ id, kind: "type-error", source: "" })),
|
|
225
225
|
],
|
|
226
226
|
};
|
|
227
|
+
const own = sourceFilesOf(project, targetRoot);
|
|
227
228
|
return {
|
|
228
229
|
configuration,
|
|
229
230
|
configFile: project.configFile,
|
|
@@ -235,7 +236,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
235
236
|
.ownSourceFiles()
|
|
236
237
|
.filter((file) => referenceOnly(file.fileName))
|
|
237
238
|
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
238
|
-
generated:
|
|
239
|
+
generated: generatedFilesOf(own, generatedFiles(conventions, own)),
|
|
239
240
|
};
|
|
240
241
|
};
|
|
241
242
|
for (const file of project.ownSourceFiles()) {
|
|
@@ -276,6 +277,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
276
277
|
unanswered: session.unanswered,
|
|
277
278
|
componentsUnread: workspace?.componentsUnread ?? [],
|
|
278
279
|
conventionsApplied: conventions.applied,
|
|
280
|
+
conventionUses: conventions.uses,
|
|
279
281
|
outsideTests,
|
|
280
282
|
};
|
|
281
283
|
}
|
|
@@ -470,6 +472,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
470
472
|
unanswered: read.unanswered,
|
|
471
473
|
componentsUnread: read.componentsUnread,
|
|
472
474
|
conventionsApplied: read.conventionsApplied,
|
|
475
|
+
conventionUses: read.conventionUses,
|
|
473
476
|
};
|
|
474
477
|
}
|
|
475
478
|
/**
|
|
@@ -646,7 +649,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
646
649
|
references: one.configured.references,
|
|
647
650
|
accessors: one.extra.accessors,
|
|
648
651
|
})), swept.exempt, input.mode),
|
|
649
|
-
dependencies: dependenciesOf(host, targetRoot, projects.map((one) => one.extra.needs), swept.sweep.candidates.map((candidate) => candidate.id)),
|
|
652
|
+
dependencies: dependenciesOf(host, targetRoot, [...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)], swept.sweep.candidates.map((candidate) => candidate.id)),
|
|
650
653
|
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
651
654
|
files: {
|
|
652
655
|
tree: sourceTree(host, targetRoot),
|
package/dist/src/config.js
CHANGED
|
@@ -49,9 +49,12 @@ export function defaultConfig() {
|
|
|
49
49
|
ts: {
|
|
50
50
|
testFiles: [
|
|
51
51
|
"**/*.test.{ts,tsx,mts,cts}",
|
|
52
|
+
"**/*.test-d.{ts,tsx,mts,cts}",
|
|
53
|
+
"**/*.spec-d.{ts,tsx,mts,cts}",
|
|
52
54
|
"**/*.spec.*",
|
|
53
55
|
"**/__tests__/**",
|
|
54
56
|
"**/__mocks__/**",
|
|
57
|
+
"**/test-d/**",
|
|
55
58
|
],
|
|
56
59
|
entryFiles: [],
|
|
57
60
|
componentExtensions: [".vue", ".svelte", ".astro"],
|
|
@@ -164,33 +164,31 @@ function relativeSpecifiers(file) {
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
|
-
* The
|
|
168
|
-
*
|
|
169
|
-
*
|
|
167
|
+
* The paths one relative specifier from a configuration file may reach, in the order they
|
|
168
|
+
* are tried: the path it names where that ends with a module extension, that path with its
|
|
169
|
+
* JavaScript extension replaced by the TypeScript counterparts, that path with an
|
|
170
|
+
* extension appended, and a directory's `index` with one appended. A data file such as
|
|
171
|
+
* `./package.json` is no module, so it is no candidate.
|
|
170
172
|
*/
|
|
171
|
-
function
|
|
173
|
+
export function specifierCandidates(dir, specifier) {
|
|
172
174
|
const path = resolvePath(dir, specifier);
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return path;
|
|
175
|
+
const candidates = [];
|
|
176
|
+
if (APPENDED.some((extension) => path.endsWith(extension))) {
|
|
177
|
+
candidates.push(path);
|
|
177
178
|
}
|
|
178
179
|
for (const [extension, counterparts] of COUNTERPARTS) {
|
|
179
180
|
if (path.endsWith(extension)) {
|
|
180
181
|
const stem = path.slice(0, -extension.length);
|
|
181
|
-
|
|
182
|
-
if (found !== undefined) {
|
|
183
|
-
return found;
|
|
184
|
-
}
|
|
182
|
+
candidates.push(...counterparts.map((one) => stem + one));
|
|
185
183
|
}
|
|
186
184
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
185
|
+
candidates.push(...APPENDED.map((extension) => path + extension));
|
|
186
|
+
candidates.push(...APPENDED.map((extension) => joinPath(path, `index${extension}`)));
|
|
187
|
+
return candidates;
|
|
188
|
+
}
|
|
189
|
+
/** The file on disk one relative specifier from a configuration file reaches, or undefined. */
|
|
190
|
+
export function resolveRelativeSpecifier(host, dir, specifier) {
|
|
191
|
+
return specifierCandidates(dir, specifier).find((path) => host.kindOf(path) === "file");
|
|
194
192
|
}
|
|
195
193
|
/**
|
|
196
194
|
* The configuration modules of the directories that hold a manifest which the program
|
|
@@ -233,7 +231,7 @@ function outsideModules(host, dirs, files, holdsManifest, parse) {
|
|
|
233
231
|
const file = parse(next.path, text);
|
|
234
232
|
found.push({ file, manifestDir: next.manifestDir });
|
|
235
233
|
for (const specifier of relativeSpecifiers(file)) {
|
|
236
|
-
const reached =
|
|
234
|
+
const reached = resolveRelativeSpecifier(host, dirnamePath(next.path), specifier);
|
|
237
235
|
if (reached !== undefined) {
|
|
238
236
|
pending.push({ path: reached, manifestDir: next.manifestDir });
|
|
239
237
|
}
|
|
@@ -267,12 +265,13 @@ export function dependenciesNamed(text, declared) {
|
|
|
267
265
|
return declared.filter((name) => text === name || text.startsWith(`${name}/`));
|
|
268
266
|
}
|
|
269
267
|
/**
|
|
270
|
-
* The own files one string names read against a directory:
|
|
271
|
-
*
|
|
272
|
-
*
|
|
268
|
+
* The own files one string names read against a directory: the file it names directly or
|
|
269
|
+
* through the emit mapping, or the file it names with an extension the module resolution
|
|
270
|
+
* appends. An empty string, one starting with `/` and one holding a glob character names
|
|
271
|
+
* none.
|
|
273
272
|
*/
|
|
274
273
|
export function filesNamedBy(files, dir, text) {
|
|
275
|
-
if (
|
|
274
|
+
if (text === "" || text.startsWith("/") || /[*?[{]/u.test(text)) {
|
|
276
275
|
return [];
|
|
277
276
|
}
|
|
278
277
|
const path = resolvePath(dir, text);
|
|
@@ -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: "3.0.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:db52e3164347cab70f45f438b4ac9ce89573be9303503daeb237a3c40361b001",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|
|
@@ -39,6 +39,7 @@ function propertyName(property) {
|
|
|
39
39
|
*/
|
|
40
40
|
export function contextualUses(project, held, chains, literals, flows, cap, properties) {
|
|
41
41
|
const queries = project.queries;
|
|
42
|
+
const ownPaths = project.ownPaths();
|
|
42
43
|
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
43
44
|
let batches = 0;
|
|
44
45
|
let lookups = 0;
|
|
@@ -78,6 +79,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
78
79
|
.declarationsOf(property)
|
|
79
80
|
.filter((id) => MEMBER_KINDS.has(kinds.get(id) ?? "file")),
|
|
80
81
|
handles: new Set(property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`)),
|
|
82
|
+
own: property.declarations.some((handle) => ownPaths.has(handle.path)),
|
|
81
83
|
});
|
|
82
84
|
}
|
|
83
85
|
tables.set(type.id, table);
|
|
@@ -109,6 +111,11 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
109
111
|
/** The members a position may read structurally: those of object types, not of classes. */
|
|
110
112
|
const readable = (member) => member.ids.filter((id) => kinds.get(id) === "type-member");
|
|
111
113
|
const carries = (part) => [...membersOf(part).values()].some((member) => readable(member).length > 0);
|
|
114
|
+
/** Whether an object type has members and declares none of them in the target. */
|
|
115
|
+
const outside = (part) => {
|
|
116
|
+
const members = [...membersOf(part).values()];
|
|
117
|
+
return members.length > 0 && members.every((member) => !member.own);
|
|
118
|
+
};
|
|
112
119
|
const elements = new Map();
|
|
113
120
|
/** The element type of an array type, read once per type. */
|
|
114
121
|
const elementOf = (type) => {
|
|
@@ -168,7 +175,10 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
168
175
|
const sources = partsOf(source).filter((one) => !targets.some((part) => part.id === one.id));
|
|
169
176
|
for (const one of sources) {
|
|
170
177
|
if (carries(one)) {
|
|
171
|
-
into.push({ source: one, targets });
|
|
178
|
+
into.push({ source: one, targets, outside: false });
|
|
179
|
+
}
|
|
180
|
+
else if (outside(one)) {
|
|
181
|
+
into.push({ source: one, targets, outside: true });
|
|
172
182
|
}
|
|
173
183
|
const element = elementOf(one);
|
|
174
184
|
const called = element === undefined ? callOf(one) : undefined;
|
|
@@ -198,7 +208,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
198
208
|
}
|
|
199
209
|
seen.add(type.id);
|
|
200
210
|
return partsOf(type).some((part) => {
|
|
201
|
-
if (carries(part)) {
|
|
211
|
+
if (carries(part) || outside(part)) {
|
|
202
212
|
return true;
|
|
203
213
|
}
|
|
204
214
|
const element = elementOf(part);
|
|
@@ -223,9 +233,20 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
223
233
|
const checked = [];
|
|
224
234
|
compared(type, position, checked, new Set());
|
|
225
235
|
const ids = new Set();
|
|
226
|
-
for (const { source, targets } of checked) {
|
|
236
|
+
for (const { source, targets, outside: held } of checked) {
|
|
227
237
|
const tables = targets.map(membersOf);
|
|
228
|
-
|
|
238
|
+
const sourceMembers = membersOf(source);
|
|
239
|
+
if (held) {
|
|
240
|
+
for (const table of tables) {
|
|
241
|
+
for (const [name, member] of table) {
|
|
242
|
+
if (sourceMembers.has(name)) {
|
|
243
|
+
member.ids.forEach((id) => ids.add(id));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
for (const [name, member] of sourceMembers) {
|
|
229
250
|
const read = tables.some((table) => {
|
|
230
251
|
const declared = table.get(name);
|
|
231
252
|
return (declared !== undefined &&
|
|
@@ -385,5 +385,12 @@ export const CONVENTION_ROWS = [
|
|
|
385
385
|
`**/{vitest,vite}.*.config.${ANY_MODULE}`,
|
|
386
386
|
],
|
|
387
387
|
moves: [],
|
|
388
|
+
shortNames: [
|
|
389
|
+
{
|
|
390
|
+
property: "test.coverage.provider",
|
|
391
|
+
files: [`{vitest,vite}.config.${ANY_MODULE}`],
|
|
392
|
+
package: "@vitest/coverage-{}",
|
|
393
|
+
},
|
|
394
|
+
],
|
|
388
395
|
},
|
|
389
396
|
];
|
package/dist/src/conventions.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* directory a configuration property moves read from the property's literal value.
|
|
6
6
|
* No configuration file is run.
|
|
7
7
|
*/
|
|
8
|
-
import { isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
8
|
+
import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isClassDeclaration, isComputedPropertyName, isExportAssignment, isExportDeclaration, isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, isNamedExports, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNonNullExpression, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadAssignment, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
9
|
import { lineOf } from "./component-files.js";
|
|
10
10
|
import { CONVENTION_ROWS, } from "./convention-rows.js";
|
|
11
11
|
import { globExpression } from "./glob.js";
|
|
@@ -288,12 +288,32 @@ function objectOf(node, declared, throughCalls, seen = new Set()) {
|
|
|
288
288
|
}
|
|
289
289
|
return at;
|
|
290
290
|
}
|
|
291
|
+
/** The text of a string literal or a template literal with no substitution, or undefined. */
|
|
292
|
+
function literalText(node) {
|
|
293
|
+
const at = unwrapped(node);
|
|
294
|
+
return isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at) ? at.text : undefined;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The literal texts one value at a path's end writes: the literal itself, or, where
|
|
298
|
+
* `elements`, each element of an array literal of literals. Undefined for any other value.
|
|
299
|
+
*/
|
|
300
|
+
function literalTexts(held, elements) {
|
|
301
|
+
const one = literalText(held);
|
|
302
|
+
if (one !== undefined) {
|
|
303
|
+
return [one];
|
|
304
|
+
}
|
|
305
|
+
if (!elements || !isArrayLiteralExpression(held)) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
const texts = held.elements.map(literalText);
|
|
309
|
+
return texts.every((text) => text !== undefined) ? texts : undefined;
|
|
310
|
+
}
|
|
291
311
|
/**
|
|
292
312
|
* Reads the values at one path of keys below one object literal into `values`, and
|
|
293
313
|
* answers the first member that does not write its value out: a spread, a value that is
|
|
294
314
|
* not a literal at the path's end, or one that is not an object literal before it.
|
|
295
315
|
*/
|
|
296
|
-
function readPath(object, keys, values) {
|
|
316
|
+
function readPath(object, keys, values, elements) {
|
|
297
317
|
const [key, ...rest] = keys;
|
|
298
318
|
for (const member of object.properties) {
|
|
299
319
|
if (isSpreadAssignment(member) || computesKey(member)) {
|
|
@@ -304,16 +324,17 @@ function readPath(object, keys, values) {
|
|
|
304
324
|
}
|
|
305
325
|
const held = isPropertyAssignment(member) ? unwrapped(member.initializer) : undefined;
|
|
306
326
|
if (rest.length === 0) {
|
|
307
|
-
|
|
327
|
+
const texts = held === undefined ? undefined : literalTexts(held, elements);
|
|
328
|
+
if (texts === undefined) {
|
|
308
329
|
return member;
|
|
309
330
|
}
|
|
310
|
-
values.push(
|
|
331
|
+
values.push(...texts);
|
|
311
332
|
}
|
|
312
333
|
else {
|
|
313
334
|
if (held === undefined || !isObjectLiteralExpression(held)) {
|
|
314
335
|
return member;
|
|
315
336
|
}
|
|
316
|
-
const failed = readPath(held, rest, values);
|
|
337
|
+
const failed = readPath(held, rest, values, elements);
|
|
317
338
|
if (failed !== undefined) {
|
|
318
339
|
return failed;
|
|
319
340
|
}
|
|
@@ -324,11 +345,12 @@ function readPath(object, keys, values) {
|
|
|
324
345
|
/**
|
|
325
346
|
* Every value one file writes at one property's path inside the object it is read from:
|
|
326
347
|
* the options argument of every call `call` names, or the file's default export. A string
|
|
327
|
-
* literal or a template literal with no substitution is the value
|
|
328
|
-
*
|
|
329
|
-
*
|
|
348
|
+
* literal or a template literal with no substitution is the value, and so is each element
|
|
349
|
+
* of an array literal of them where `elements`. A read object, or an object along the
|
|
350
|
+
* path, that is not written out as an object literal, or that spreads another object into
|
|
351
|
+
* itself, is not a literal, and neither is any other value at the path.
|
|
330
352
|
*/
|
|
331
|
-
export function readProperty(file, property, call) {
|
|
353
|
+
export function readProperty(file, property, call, elements = false) {
|
|
332
354
|
const declared = declaredValues(file);
|
|
333
355
|
const objects = call === undefined
|
|
334
356
|
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
@@ -339,7 +361,7 @@ export function readProperty(file, property, call) {
|
|
|
339
361
|
continue;
|
|
340
362
|
}
|
|
341
363
|
const notLiteral = isObjectLiteralExpression(object)
|
|
342
|
-
? readPath(object, property.split("."), values)
|
|
364
|
+
? readPath(object, property.split("."), values, elements)
|
|
343
365
|
: object;
|
|
344
366
|
if (notLiteral !== undefined) {
|
|
345
367
|
return { values, notLiteral };
|
|
@@ -392,29 +414,18 @@ function configurationFiles(host, dir, glob) {
|
|
|
392
414
|
function resolveMove(host, parse, dir, row, move, resolved) {
|
|
393
415
|
const set = [];
|
|
394
416
|
for (const reading of move.readings) {
|
|
395
|
-
for (const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
const file = parse(path, text);
|
|
406
|
-
const read = readProperty(file, reading.property, reading.call);
|
|
407
|
-
if (read.notLiteral !== undefined) {
|
|
408
|
-
const line = lineOf(text, read.notLiteral.getStart());
|
|
409
|
-
return {
|
|
410
|
-
failure: `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
|
|
411
|
-
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
412
|
-
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
413
|
-
`files in ts.entry_files`,
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
set.push(...read.values);
|
|
417
|
+
for (const { name, text, file } of readingFiles(host, parse, dir, reading.files)) {
|
|
418
|
+
const read = readProperty(file, reading.property, reading.call);
|
|
419
|
+
if (read.notLiteral !== undefined) {
|
|
420
|
+
const line = lineOf(text, read.notLiteral.getStart());
|
|
421
|
+
return {
|
|
422
|
+
failure: `${name}:${String(line)} sets ${reading.property}, which moves a directory ` +
|
|
423
|
+
`the convention row ${row.name} reads, to a value that is not a string literal: ` +
|
|
424
|
+
`write it as a literal, or name ${row.name} in ts.disabled_conventions and its ` +
|
|
425
|
+
`files in ts.entry_files`,
|
|
426
|
+
};
|
|
417
427
|
}
|
|
428
|
+
set.push(...read.values);
|
|
418
429
|
}
|
|
419
430
|
}
|
|
420
431
|
const dirs = [];
|
|
@@ -427,6 +438,55 @@ function resolveMove(host, parse, dir, row, move, resolved) {
|
|
|
427
438
|
}
|
|
428
439
|
return { dirs: [...new Set(dirs)] };
|
|
429
440
|
}
|
|
441
|
+
/** The texts each configuration file a reading names holds, parsed, in name order. */
|
|
442
|
+
function readingFiles(host, parse, dir, files) {
|
|
443
|
+
const found = [];
|
|
444
|
+
for (const glob of files) {
|
|
445
|
+
for (const name of configurationFiles(host, dir, glob)) {
|
|
446
|
+
const path = joinPath(dir, name);
|
|
447
|
+
let text;
|
|
448
|
+
try {
|
|
449
|
+
text = host.readFile(path);
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
found.push({ name, text, file: parse(path, text) });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return found;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* The dependencies one manifest declares that an applied row's short names use: the
|
|
461
|
+
* package each literal value stands for, and, for a value that is not a literal, every
|
|
462
|
+
* declared dependency the row's package name stands for with some value.
|
|
463
|
+
*/
|
|
464
|
+
function shortNameUses(host, parse, dir, manifest, readings) {
|
|
465
|
+
const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
466
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
467
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
468
|
+
}));
|
|
469
|
+
const used = new Set();
|
|
470
|
+
for (const reading of readings) {
|
|
471
|
+
const [before = "", after = ""] = reading.package.split("{}");
|
|
472
|
+
for (const { file } of readingFiles(host, parse, dir, reading.files)) {
|
|
473
|
+
const read = readProperty(file, reading.property, reading.call, true);
|
|
474
|
+
for (const value of read.values) {
|
|
475
|
+
used.add(`${before}${value}${after}`);
|
|
476
|
+
}
|
|
477
|
+
if (read.notLiteral !== undefined) {
|
|
478
|
+
for (const name of declared) {
|
|
479
|
+
if (name.length >= before.length + after.length &&
|
|
480
|
+
name.startsWith(before) &&
|
|
481
|
+
name.endsWith(after)) {
|
|
482
|
+
used.add(name);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return [...used].filter((name) => declared.has(name)).sort(compare);
|
|
489
|
+
}
|
|
430
490
|
/** Two strings ordered bytewise. */
|
|
431
491
|
function compare(a, b) {
|
|
432
492
|
if (a === b) {
|
|
@@ -443,6 +503,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
443
503
|
const globs = [];
|
|
444
504
|
const generated = [];
|
|
445
505
|
const failures = [];
|
|
506
|
+
const uses = new Set();
|
|
446
507
|
for (const manifest of manifests) {
|
|
447
508
|
const declared = readJSON(host, joinPath(manifest.dir, "package.json"));
|
|
448
509
|
const versions = new Map();
|
|
@@ -500,6 +561,9 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
500
561
|
version,
|
|
501
562
|
manifest: manifest.path,
|
|
502
563
|
});
|
|
564
|
+
for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
|
|
565
|
+
uses.add(name);
|
|
566
|
+
}
|
|
503
567
|
const excludes = [
|
|
504
568
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
|
505
569
|
].map(globExpression);
|
|
@@ -520,7 +584,7 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
520
584
|
unique.sort((a, b) => compare(a.name, b.name) ||
|
|
521
585
|
compare(a.manifest, b.manifest) ||
|
|
522
586
|
compare(JSON.stringify(a), JSON.stringify(b)));
|
|
523
|
-
return { applied: unique, globs, generated, failures };
|
|
587
|
+
return { applied: unique, globs, generated, failures, uses: [...uses].sort(compare) };
|
|
524
588
|
}
|
|
525
589
|
/**
|
|
526
590
|
* Every own file of one project below a directory an applied row names as generated, by
|
package/dist/src/dependencies.js
CHANGED
|
@@ -24,6 +24,14 @@ const SECTIONS = [
|
|
|
24
24
|
];
|
|
25
25
|
/** The owner of a use no declaration of the inventory holds: a configuration's. */
|
|
26
26
|
const PROJECT_OWNER = "";
|
|
27
|
+
/** What a configuration's own reading needs: packages no deletion of a declaration removes. */
|
|
28
|
+
export function configurationNeeds(packages) {
|
|
29
|
+
return {
|
|
30
|
+
packages: new Set(packages),
|
|
31
|
+
uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
|
|
32
|
+
unanswered: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
27
35
|
function isRecord(value) {
|
|
28
36
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
37
|
}
|
|
@@ -13,9 +13,9 @@ const EVERY_CONVENTION = "every file it loads by its own convention beyond its c
|
|
|
13
13
|
const UNREAD = "No rule reads it beyond a configuration file the file-name form or a string enters, so a " +
|
|
14
14
|
"file only it loads is reported as never imported unless ts.entry_files or roots.patterns " +
|
|
15
15
|
"names it.";
|
|
16
|
-
/** Why a configuration's file names that are not
|
|
17
|
-
const
|
|
18
|
-
"
|
|
16
|
+
/** Why a configuration's file names that are not literals are declined. */
|
|
17
|
+
const NOT_LITERAL = "A string of a configuration file names a file only where it is a literal, so a file a " +
|
|
18
|
+
"computed value names is reported as never imported.";
|
|
19
19
|
/** The tools whose own conventions are not read. */
|
|
20
20
|
const UNREAD_TOOLS = [
|
|
21
21
|
"angular",
|
|
@@ -211,11 +211,6 @@ const PARTLY_ENTERED = [
|
|
|
211
211
|
reason: "A testMatch that is not a string literal leaves the test-file set unknown, and an " +
|
|
212
212
|
"unknown set enters nothing.",
|
|
213
213
|
},
|
|
214
|
-
{
|
|
215
|
-
tool: "playwright",
|
|
216
|
-
convention: "a globalSetup or globalTeardown file named without a leading ./ or ../",
|
|
217
|
-
reason: NOT_RELATIVE,
|
|
218
|
-
},
|
|
219
214
|
{
|
|
220
215
|
tool: "playwright",
|
|
221
216
|
convention: "the testDir and testMatch of an entry in projects",
|
|
@@ -272,20 +267,14 @@ const PARTLY_ENTERED = [
|
|
|
272
267
|
},
|
|
273
268
|
{
|
|
274
269
|
tool: "vite",
|
|
275
|
-
convention: "an entry point its configuration
|
|
276
|
-
reason:
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
tool: "vite",
|
|
280
|
-
convention: "the module scripts an index.html loads",
|
|
281
|
-
reason: "No rule reads an HTML file, so a script only index.html loads is reported as never " +
|
|
282
|
-
"imported unless ts.entry_files names it.",
|
|
270
|
+
convention: "an entry point its configuration computes",
|
|
271
|
+
reason: NOT_LITERAL,
|
|
283
272
|
},
|
|
284
273
|
{
|
|
285
274
|
tool: "vitest",
|
|
286
|
-
convention: "the benchmark
|
|
287
|
-
reason: "A test file is one ts.test_files classifies, so a *.bench.*
|
|
288
|
-
"
|
|
275
|
+
convention: "the benchmark files",
|
|
276
|
+
reason: "A test file is one ts.test_files classifies, so a *.bench.* file is entered only " +
|
|
277
|
+
"where a configured pattern names it.",
|
|
289
278
|
},
|
|
290
279
|
{
|
|
291
280
|
tool: "vitest",
|
|
@@ -293,11 +282,6 @@ const PARTLY_ENTERED = [
|
|
|
293
282
|
reason: "A string is read against the configuration file's directory, so a setup file named " +
|
|
294
283
|
"against a root the configuration moves is not entered.",
|
|
295
284
|
},
|
|
296
|
-
{
|
|
297
|
-
tool: "vitest",
|
|
298
|
-
convention: "a setup file named without a leading ./ or ../",
|
|
299
|
-
reason: NOT_RELATIVE,
|
|
300
|
-
},
|
|
301
285
|
{
|
|
302
286
|
tool: "vitest",
|
|
303
287
|
convention: "the test files a configuration's include member names",
|
|
@@ -306,8 +290,8 @@ const PARTLY_ENTERED = [
|
|
|
306
290
|
},
|
|
307
291
|
{
|
|
308
292
|
tool: "webpack",
|
|
309
|
-
convention: "an entry point its configuration
|
|
310
|
-
reason:
|
|
293
|
+
convention: "an entry point its configuration computes",
|
|
294
|
+
reason: NOT_LITERAL,
|
|
311
295
|
},
|
|
312
296
|
];
|
|
313
297
|
/** Two strings ordered bytewise. */
|
package/dist/src/entry-points.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAss
|
|
|
14
14
|
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
15
15
|
import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
|
|
16
16
|
import { globExpression } from "./glob.js";
|
|
17
|
+
import { htmlEntries } from "./html-entries.js";
|
|
17
18
|
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
18
19
|
import { UNANSWERED } from "./query.js";
|
|
19
20
|
/** The extensions the test runner and the browser-test runner load a configuration from. */
|
|
@@ -406,5 +407,8 @@ export function entryPoints(project, files, testFiles, host, targetRoot, parse)
|
|
|
406
407
|
}
|
|
407
408
|
found.push(...workersOf(project, file, files));
|
|
408
409
|
}
|
|
410
|
+
for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
|
|
411
|
+
found.push({ file, rule: "html-entry", source, exports: true });
|
|
412
|
+
}
|
|
409
413
|
return found;
|
|
410
414
|
}
|
|
@@ -39,23 +39,29 @@ function publishedIds(union) {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* The reachability class of one declaration of the run. A declaration of a library's
|
|
42
|
-
* published API, a public member of a published type included,
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* in the loaded program, a private member and a `#private` name among them, and is
|
|
48
|
-
* `certain`.
|
|
42
|
+
* published API, a public member of a published type included, is `certain` where the
|
|
43
|
+
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
44
|
+
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
45
|
+
* consumer set. A declaration of test-support code that test code references, outside the
|
|
46
|
+
* test-of-dead-code rule, is `possible`. Every other declaration is `certain`.
|
|
49
47
|
*/
|
|
50
48
|
function reachabilityClasses(input) {
|
|
51
49
|
const published = publishedIds(input.swept.matrix.union);
|
|
50
|
+
const usedByTests = new Set(input.swept.sweep.candidates
|
|
51
|
+
.filter((candidate) => candidate.heldByTests === candidate.id)
|
|
52
|
+
.map((candidate) => candidate.id));
|
|
52
53
|
const { declared } = input.boundary.consumers;
|
|
53
54
|
const outside = declared.length === 0
|
|
54
55
|
? "possible"
|
|
55
56
|
: everyDeclaredLoaded(input.boundary)
|
|
56
57
|
? "certain"
|
|
57
58
|
: "probable";
|
|
58
|
-
return (id) =>
|
|
59
|
+
return (id) => {
|
|
60
|
+
if (usedByTests.has(id)) {
|
|
61
|
+
return "possible";
|
|
62
|
+
}
|
|
63
|
+
return published.has(id) ? outside : "certain";
|
|
64
|
+
};
|
|
59
65
|
}
|
|
60
66
|
/**
|
|
61
67
|
* The component each dead declaration of the run falls in, and none for a live one, with
|
|
@@ -117,6 +123,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
117
123
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
118
124
|
let minted = swept.sweep.components.length;
|
|
119
125
|
const dead = new Set(swept.sweep.components.map((component) => component.id));
|
|
126
|
+
const fallingWith = new Map();
|
|
127
|
+
for (const { heldByTests } of swept.sweep.candidates) {
|
|
128
|
+
if (heldByTests !== undefined) {
|
|
129
|
+
fallingWith.set(heldByTests, (fallingWith.get(heldByTests) ?? 0) + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
120
132
|
const done = findings.map((finding) => {
|
|
121
133
|
const row = kinds.get(finding.code);
|
|
122
134
|
if (row === undefined) {
|
|
@@ -134,8 +146,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
134
146
|
const judged = !part && union.subject[at] === true ? symbol : undefined;
|
|
135
147
|
const candidate = judged === undefined ? undefined : candidates.get(judged.id);
|
|
136
148
|
const found = symbol === undefined || part ? undefined : componentOf(symbol.id);
|
|
137
|
-
// A test file's declaration is a component member only as a test of dead code
|
|
138
|
-
|
|
149
|
+
// A test file's declaration is a component member only as a test of dead code, and
|
|
150
|
+
// test code keeps the test-support code it references out of every component.
|
|
151
|
+
if (candidate !== undefined &&
|
|
152
|
+
found === undefined &&
|
|
153
|
+
union.test[at] !== true &&
|
|
154
|
+
candidate.heldByTests === undefined) {
|
|
139
155
|
throw new Error(`${candidate.id} is judged dead and falls in no dead component`);
|
|
140
156
|
}
|
|
141
157
|
let component = found;
|
|
@@ -144,7 +160,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
144
160
|
component = {
|
|
145
161
|
id: componentId(minted),
|
|
146
162
|
root: true,
|
|
147
|
-
symbolCount: 1,
|
|
163
|
+
symbolCount: symbol?.kind === "interface" ? (fallingWith.get(symbol.id) ?? 1) : 1,
|
|
148
164
|
deletableLines: 0,
|
|
149
165
|
};
|
|
150
166
|
}
|