@cplieger/deadset-ts 5.1.0 → 5.2.0
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/README.md +1 -1
- package/dist/src/analysis.js +79 -37
- package/dist/src/config.js +3 -0
- package/dist/src/configuration-files.js +24 -25
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +46 -18
- 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/exempt.js +23 -8
- package/dist/src/file-facts.js +23 -20
- 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 +6 -5
- package/dist/src/html-entries.js +137 -0
- package/dist/src/inventory.js +13 -3
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +34 -5
- package/dist/src/matrix.js +9 -3
- package/dist/src/namespace-objects.js +158 -0
- package/dist/src/outside-tests.js +139 -0
- package/dist/src/overrides.js +104 -0
- package/dist/src/paths.js +17 -0
- package/dist/src/references.js +150 -4
- package/dist/src/roots.js +172 -39
- package/dist/src/session.js +4 -3
- package/dist/src/sweep.js +17 -0
- package/dist/src/template-field.js +60 -3
- package/dist/src/test-support.js +35 -23
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +122 -49
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +29 -25
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +51 -20
- 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 +29 -12
- package/src/file-facts.ts +25 -21
- 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 +9 -8
- package/src/html-entries.ts +170 -0
- package/src/inventory.ts +15 -3
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +49 -7
- package/src/matrix.ts +21 -3
- package/src/namespace-objects.ts +216 -0
- package/src/outside-tests.ts +191 -0
- package/src/overrides.ts +136 -0
- package/src/paths.ts +18 -0
- package/src/references.ts +206 -9
- package/src/roots.ts +210 -53
- package/src/session.ts +4 -2
- package/src/sweep.ts +19 -0
- package/src/template-field.ts +65 -3
- package/src/test-support.ts +34 -22
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ A TypeScript caller that imports the source sets `allowImportingTsExtensions`, b
|
|
|
90
90
|
|
|
91
91
|
## Related projects
|
|
92
92
|
|
|
93
|
-
deadset-ts implements the [deadset Contract](https://github.com/cplieger/deadset-spec), which fixes the issue codes, the report schema and the exit codes. It passes every fixture of the Contract's conformance corpus
|
|
93
|
+
deadset-ts implements the [deadset Contract](https://github.com/cplieger/deadset-spec), which fixes the issue codes, the report schema and the exit codes. It passes every fixture of the Contract's conformance corpus.
|
|
94
94
|
|
|
95
95
|
- [deadset-go](https://github.com/cplieger/deadset-go) is the same analysis for Go modules.
|
|
96
96
|
- [deadset](https://github.com/cplieger/deadset) runs both analyzers as one command and merges their reports, resolving the edges between Go and TypeScript code.
|
package/dist/src/analysis.js
CHANGED
|
@@ -11,16 +11,17 @@ 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
|
-
import {
|
|
23
|
-
import { heldByInclusion, sourceTree } from "./file-facts.js";
|
|
22
|
+
import { evidenceRecords, heldRecords, disabledClasses, exemptionsOf, holdingWhile, isUnansweredRecord, retainedIn, } from "./exempt.js";
|
|
23
|
+
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
|
|
24
|
+
import { globExpression } from "./glob.js";
|
|
24
25
|
import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
|
|
25
26
|
import { NO_SELF_CHECK, unmatchedRootFindings, } from "./findings/self-check.js";
|
|
26
27
|
import { frameworkLifecycle } from "./framework-lifecycle.js";
|
|
@@ -29,9 +30,9 @@ import { injectionContainer } from "./injection-container.js";
|
|
|
29
30
|
import { interfaceSatisfaction } from "./interface-satisfaction.js";
|
|
30
31
|
import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
|
|
31
32
|
import { inventory } from "./inventory.js";
|
|
32
|
-
import { readManifest } from "./manifest.js";
|
|
33
|
+
import { packageEntries, readManifest } from "./manifest.js";
|
|
33
34
|
import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
|
|
34
|
-
import { joinPath, relativePath, resolvePath } from "./paths.js";
|
|
35
|
+
import { joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
35
36
|
import { byPosition, isComponentFile } from "./position.js";
|
|
36
37
|
import { SetupError, setupLine } from "./setup-failure.js";
|
|
37
38
|
import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
|
|
@@ -39,6 +40,7 @@ import { reflectiveLookup } from "./reflective-lookup.js";
|
|
|
39
40
|
import { references, testFileRulesOf } from "./references.js";
|
|
40
41
|
import { roots, unmatchedEverywhere } from "./roots.js";
|
|
41
42
|
import { serializationContract } from "./serialization-contract.js";
|
|
43
|
+
import { outsideTestReferences, testFilesAt } from "./outside-tests.js";
|
|
42
44
|
import { supportReferences, testSupportFiles } from "./test-support.js";
|
|
43
45
|
import { diagnosticsOf, ownedAs, runSession } from "./session.js";
|
|
44
46
|
import { accessorsOf, storesOf } from "./stores.js";
|
|
@@ -107,7 +109,10 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
107
109
|
const options = {
|
|
108
110
|
host,
|
|
109
111
|
manifest: workspace?.manifest ?? readManifest(host, targetRoot),
|
|
110
|
-
|
|
112
|
+
otherPackages: [
|
|
113
|
+
...(workspace?.entries ?? []),
|
|
114
|
+
...packageEntries(host, normalizePath(absoluteRoot), discovered.configFiles, new Set(workspace?.memberDirs ?? [])),
|
|
115
|
+
],
|
|
111
116
|
patterns: config.rootPatterns,
|
|
112
117
|
entryFiles: config.ts.entryFiles,
|
|
113
118
|
conventions,
|
|
@@ -127,6 +132,8 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
127
132
|
const unbuildable = [];
|
|
128
133
|
const setupDropped = [];
|
|
129
134
|
const consumed = new Map();
|
|
135
|
+
const heldFiles = new Set();
|
|
136
|
+
let outsideTests = [];
|
|
130
137
|
const setupMet = (configFile, met) => {
|
|
131
138
|
const [first] = met;
|
|
132
139
|
if (first !== undefined && isGuess(discovered, configFile)) {
|
|
@@ -217,6 +224,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
217
224
|
...kept.map((id) => ({ id, kind: "type-error", source: "" })),
|
|
218
225
|
],
|
|
219
226
|
};
|
|
227
|
+
const own = sourceFilesOf(project, targetRoot);
|
|
220
228
|
return {
|
|
221
229
|
configuration,
|
|
222
230
|
configFile: project.configFile,
|
|
@@ -228,11 +236,17 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
228
236
|
.ownSourceFiles()
|
|
229
237
|
.filter((file) => referenceOnly(file.fileName))
|
|
230
238
|
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
231
|
-
generated:
|
|
239
|
+
generated: generatedFilesOf(own, generatedFiles(conventions, own)),
|
|
232
240
|
};
|
|
233
241
|
};
|
|
242
|
+
for (const file of project.ownSourceFiles()) {
|
|
243
|
+
heldFiles.add(relativePath(absoluteRoot, file.fileName) ?? file.fileName);
|
|
244
|
+
}
|
|
234
245
|
return stage(project, { configuration, read });
|
|
235
|
-
}, discovered.derived ? "record" : "refuse", workspace?.programs)
|
|
246
|
+
}, discovered.derived ? "record" : "refuse", workspace?.programs, () => {
|
|
247
|
+
const patterns = config.ts.testFiles.map(globExpression);
|
|
248
|
+
outsideTests = testFilesAt(host, targetRoot, sourcePaths(host, targetRoot).filter((path) => !heldFiles.has(path) && patterns.some((pattern) => pattern.test(path))), options.parse);
|
|
249
|
+
});
|
|
236
250
|
for (const configFile of session.unopened) {
|
|
237
251
|
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
238
252
|
throw new DiscoveryError(`${configFile} opened no project`, []);
|
|
@@ -263,6 +277,8 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
263
277
|
unanswered: session.unanswered,
|
|
264
278
|
componentsUnread: workspace?.componentsUnread ?? [],
|
|
265
279
|
conventionsApplied: conventions.applied,
|
|
280
|
+
conventionUses: conventions.uses,
|
|
281
|
+
outsideTests,
|
|
266
282
|
};
|
|
267
283
|
}
|
|
268
284
|
/**
|
|
@@ -351,8 +367,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
351
367
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
352
368
|
testFiles: config.ts.testFiles,
|
|
353
369
|
});
|
|
354
|
-
const
|
|
355
|
-
const detected = detectExemptions({
|
|
370
|
+
const evidence = evidenceRecords({
|
|
356
371
|
project,
|
|
357
372
|
held: projectRead.held,
|
|
358
373
|
targetRoot,
|
|
@@ -362,52 +377,78 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
362
377
|
...(config.analysis.generatedFiles === "include"
|
|
363
378
|
? {}
|
|
364
379
|
: { generated: projectRead.generated }),
|
|
365
|
-
}, detectors,
|
|
380
|
+
}, detectors, disabled);
|
|
381
|
+
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
382
|
+
const pathOf = (file) => relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
383
|
+
return {
|
|
384
|
+
projectRead,
|
|
385
|
+
references: resolved.references,
|
|
386
|
+
testFiles: resolved.testFilePaths,
|
|
387
|
+
evidence,
|
|
388
|
+
componentFiles: components.map(pathOf),
|
|
389
|
+
componentWarnings: components.flatMap((file) => readComponent(file.originalText).warnings.map((warning) => ({
|
|
390
|
+
path: pathOf(file),
|
|
391
|
+
line: lineOf(file.originalText, warning.offset),
|
|
392
|
+
reason: warning.reason,
|
|
393
|
+
}))),
|
|
394
|
+
extra: extra(project, projectRead, resolved.references),
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
const outsidePaths = read.outsideTests.map((one) => one.path);
|
|
398
|
+
const withOutside = read.answers.map((one) => ({
|
|
399
|
+
...one,
|
|
400
|
+
references: [
|
|
401
|
+
...one.references,
|
|
402
|
+
...outsideTestReferences(read.outsideTests, one.projectRead.held, targetRoot),
|
|
403
|
+
],
|
|
404
|
+
testFiles: [...one.testFiles, ...outsidePaths],
|
|
405
|
+
}));
|
|
406
|
+
// Test-support code is code only test code imports in any configuration of the run: a
|
|
407
|
+
// configuration that holds no test file says nothing about what the tests import.
|
|
408
|
+
const support = testSupportFiles(withOutside.flatMap((one) => one.projectRead.held.symbols), withOutside.flatMap((one) => one.references), withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability), withOutside.flatMap((one) => one.testFiles));
|
|
409
|
+
const answers = withOutside.map((one) => {
|
|
410
|
+
const { projectRead } = one;
|
|
411
|
+
const detected = heldRecords(one.evidence, {
|
|
366
412
|
disabled,
|
|
367
413
|
mode,
|
|
368
|
-
testFiles: new Set([...
|
|
414
|
+
testFiles: new Set([...one.testFiles, ...support]),
|
|
369
415
|
});
|
|
370
416
|
const exempt = detected.records;
|
|
371
|
-
const made = supportReferences(
|
|
417
|
+
const made = supportReferences(one.references, support);
|
|
418
|
+
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
419
|
+
const supportFiles = [...support].filter((path) => held.has(path)).sort();
|
|
372
420
|
const configured = {
|
|
373
421
|
configuration: projectRead.configuration,
|
|
374
422
|
symbols: projectRead.held.symbols,
|
|
375
423
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
376
424
|
roots: projectRead.rooted.liveUnderReachability,
|
|
377
|
-
testFiles:
|
|
378
|
-
...(
|
|
425
|
+
testFiles: one.testFiles,
|
|
426
|
+
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
379
427
|
...(projectRead.referenceOnly.length === 0
|
|
380
428
|
? {}
|
|
381
429
|
: { referenceOnly: projectRead.referenceOnly }),
|
|
382
430
|
};
|
|
383
|
-
const unanswered = [
|
|
384
|
-
...projectRead.held.unanswered,
|
|
385
|
-
...projectRead.rooted.unanswered,
|
|
386
|
-
...made
|
|
387
|
-
.filter((reference) => reference.resolution === "by-name")
|
|
388
|
-
.map((reference) => reference.to),
|
|
389
|
-
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
390
|
-
];
|
|
391
|
-
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
392
|
-
const pathOf = (file) => relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
393
431
|
return {
|
|
394
432
|
configFile: projectRead.configFile,
|
|
395
433
|
configured,
|
|
396
434
|
exempt,
|
|
397
|
-
unanswered
|
|
435
|
+
unanswered: [
|
|
436
|
+
...projectRead.held.unanswered,
|
|
437
|
+
...projectRead.rooted.unanswered,
|
|
438
|
+
...made
|
|
439
|
+
.filter((reference) => reference.resolution === "by-name")
|
|
440
|
+
.map((reference) => reference.to),
|
|
441
|
+
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
442
|
+
],
|
|
398
443
|
typeErrorSkips: projectRead.typeErrorSkips,
|
|
399
444
|
skipped: projectRead.skipped,
|
|
400
|
-
componentFiles:
|
|
401
|
-
componentWarnings:
|
|
402
|
-
|
|
403
|
-
line: lineOf(file.originalText, warning.offset),
|
|
404
|
-
reason: warning.reason,
|
|
405
|
-
}))),
|
|
406
|
-
extra: extra(project, projectRead, made),
|
|
445
|
+
componentFiles: one.componentFiles,
|
|
446
|
+
componentWarnings: one.componentWarnings,
|
|
447
|
+
extra: one.extra,
|
|
407
448
|
};
|
|
408
449
|
});
|
|
409
450
|
const consumed = read.consumers.references;
|
|
410
|
-
const projects =
|
|
451
|
+
const projects = answers.map((one) => consumed.length === 0
|
|
411
452
|
? one
|
|
412
453
|
: {
|
|
413
454
|
...one,
|
|
@@ -421,7 +462,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
421
462
|
exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
|
|
422
463
|
unansweredHeld: [
|
|
423
464
|
...new Set([
|
|
424
|
-
...
|
|
465
|
+
...answers.flatMap((one) => one.unanswered),
|
|
425
466
|
...consumed.filter((one) => one.resolution === "by-name").map((one) => one.to),
|
|
426
467
|
]),
|
|
427
468
|
],
|
|
@@ -431,6 +472,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
431
472
|
unanswered: read.unanswered,
|
|
432
473
|
componentsUnread: read.componentsUnread,
|
|
433
474
|
conventionsApplied: read.conventionsApplied,
|
|
475
|
+
conventionUses: read.conventionUses,
|
|
434
476
|
};
|
|
435
477
|
}
|
|
436
478
|
/**
|
|
@@ -607,7 +649,7 @@ function emitterInputOver(host, scope, config, read, input, selfCheck) {
|
|
|
607
649
|
references: one.configured.references,
|
|
608
650
|
accessors: one.extra.accessors,
|
|
609
651
|
})), swept.exempt, input.mode),
|
|
610
|
-
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)),
|
|
611
653
|
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
|
612
654
|
files: {
|
|
613
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"],
|
|
@@ -39,7 +39,7 @@ const COUNTERPARTS = [
|
|
|
39
39
|
/** The extensions appended to a relative specifier from a configuration file, in order. */
|
|
40
40
|
const APPENDED = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
41
41
|
/** The extensions the program's module resolution appends to a specifier written without one. */
|
|
42
|
-
const RESOLVED_EXTENSIONS = [
|
|
42
|
+
export const RESOLVED_EXTENSIONS = [
|
|
43
43
|
".ts",
|
|
44
44
|
".tsx",
|
|
45
45
|
".d.ts",
|
|
@@ -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
|
};
|
|
@@ -32,8 +32,6 @@ function propertyName(property) {
|
|
|
32
32
|
}
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
35
|
-
/** How many element and signature steps the structural walk takes below a value's own type. */
|
|
36
|
-
const MAX_DEPTH = 3;
|
|
37
35
|
/**
|
|
38
36
|
* The contextual uses of one project's literals and values, asked in batches: one
|
|
39
37
|
* contextual type per literal and per value whose type carries a member of the target,
|
|
@@ -41,6 +39,7 @@ const MAX_DEPTH = 3;
|
|
|
41
39
|
*/
|
|
42
40
|
export function contextualUses(project, held, chains, literals, flows, cap, properties) {
|
|
43
41
|
const queries = project.queries;
|
|
42
|
+
const ownPaths = project.ownPaths();
|
|
44
43
|
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
45
44
|
let batches = 0;
|
|
46
45
|
let lookups = 0;
|
|
@@ -80,6 +79,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
80
79
|
.declarationsOf(property)
|
|
81
80
|
.filter((id) => MEMBER_KINDS.has(kinds.get(id) ?? "file")),
|
|
82
81
|
handles: new Set(property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`)),
|
|
82
|
+
own: property.declarations.some((handle) => ownPaths.has(handle.path)),
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
tables.set(type.id, table);
|
|
@@ -111,6 +111,11 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
111
111
|
/** The members a position may read structurally: those of object types, not of classes. */
|
|
112
112
|
const readable = (member) => member.ids.filter((id) => kinds.get(id) === "type-member");
|
|
113
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
|
+
};
|
|
114
119
|
const elements = new Map();
|
|
115
120
|
/** The element type of an array type, read once per type. */
|
|
116
121
|
const elementOf = (type) => {
|
|
@@ -154,14 +159,15 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
154
159
|
* Each object type a value of `source` is checked as against the object types of
|
|
155
160
|
* `target`: the value's own, and through array elements and through a function's
|
|
156
161
|
* parameters, checked the other way, and its result. A source that is one of the
|
|
157
|
-
* target's own object types matches it as it stands.
|
|
162
|
+
* target's own object types matches it as it stands. Each pair is compared once, which
|
|
163
|
+
* ends the walk over a recursive type.
|
|
158
164
|
*/
|
|
159
|
-
const compared = (source, target,
|
|
165
|
+
const compared = (source, target, into, seen) => {
|
|
160
166
|
const key = `${String(source?.id)}>${String(target?.id)}`;
|
|
161
167
|
if (source === undefined || target === undefined || source.id === target.id) {
|
|
162
168
|
return;
|
|
163
169
|
}
|
|
164
|
-
if (
|
|
170
|
+
if (seen.has(key)) {
|
|
165
171
|
return;
|
|
166
172
|
}
|
|
167
173
|
seen.add(key);
|
|
@@ -169,13 +175,16 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
169
175
|
const sources = partsOf(source).filter((one) => !targets.some((part) => part.id === one.id));
|
|
170
176
|
for (const one of sources) {
|
|
171
177
|
if (carries(one)) {
|
|
172
|
-
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 });
|
|
173
182
|
}
|
|
174
183
|
const element = elementOf(one);
|
|
175
184
|
const called = element === undefined ? callOf(one) : undefined;
|
|
176
185
|
for (const part of targets) {
|
|
177
186
|
if (element !== undefined) {
|
|
178
|
-
compared(element, elementOf(part),
|
|
187
|
+
compared(element, elementOf(part), into, seen);
|
|
179
188
|
continue;
|
|
180
189
|
}
|
|
181
190
|
const other = called === undefined ? undefined : callOf(part);
|
|
@@ -183,27 +192,35 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
183
192
|
continue;
|
|
184
193
|
}
|
|
185
194
|
other.parameters.forEach((parameter, index) => {
|
|
186
|
-
compared(parameter, called.parameters[index],
|
|
195
|
+
compared(parameter, called.parameters[index], into, seen);
|
|
187
196
|
});
|
|
188
|
-
compared(called.result, other.result,
|
|
197
|
+
compared(called.result, other.result, into, seen);
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
200
|
};
|
|
192
|
-
/**
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Whether a value of one type may carry a member a position reads, at any depth the
|
|
203
|
+
* walk takes. A type met again on the way adds nothing its first visit did not.
|
|
204
|
+
*/
|
|
205
|
+
const mayCarry = (type, seen = new Set()) => {
|
|
206
|
+
if (seen.has(type.id)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
seen.add(type.id);
|
|
210
|
+
return partsOf(type).some((part) => {
|
|
211
|
+
if (carries(part) || outside(part)) {
|
|
196
212
|
return true;
|
|
197
213
|
}
|
|
198
214
|
const element = elementOf(part);
|
|
199
215
|
if (element !== undefined) {
|
|
200
|
-
return mayCarry(element,
|
|
216
|
+
return mayCarry(element, seen);
|
|
201
217
|
}
|
|
202
218
|
return callOf(part) !== undefined;
|
|
203
219
|
});
|
|
220
|
+
};
|
|
204
221
|
const sourced = carried.flatMap((flow, index) => {
|
|
205
222
|
const type = types[index];
|
|
206
|
-
return type === undefined || !isAnswered(type) || !mayCarry(type
|
|
223
|
+
return type === undefined || !isAnswered(type) || !mayCarry(type) ? [] : [{ flow, type }];
|
|
207
224
|
});
|
|
208
225
|
batches += batchesOf(sourced.length);
|
|
209
226
|
const positions = queries.contextualTypes(sourced.map((one) => one.flow.value), cap);
|
|
@@ -214,11 +231,22 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
|
|
|
214
231
|
return;
|
|
215
232
|
}
|
|
216
233
|
const checked = [];
|
|
217
|
-
compared(type, position,
|
|
234
|
+
compared(type, position, checked, new Set());
|
|
218
235
|
const ids = new Set();
|
|
219
|
-
for (const { source, targets } of checked) {
|
|
236
|
+
for (const { source, targets, outside: held } of checked) {
|
|
220
237
|
const tables = targets.map(membersOf);
|
|
221
|
-
|
|
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) {
|
|
222
250
|
const read = tables.some((table) => {
|
|
223
251
|
const declared = table.get(name);
|
|
224
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
|
];
|