@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/src/analysis.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
declarationsNamed,
|
|
23
23
|
} from "./configured-declarations.ts";
|
|
24
24
|
import { decorator } from "./decorator.ts";
|
|
25
|
-
import { dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
25
|
+
import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
26
26
|
import { deprecatedDeclarations, type Deprecation } from "./deprecation.ts";
|
|
27
27
|
import {
|
|
28
28
|
discoverProjects,
|
|
@@ -43,10 +43,11 @@ import {
|
|
|
43
43
|
} from "./conventions.ts";
|
|
44
44
|
import { readEdgeSides } from "./edges.ts";
|
|
45
45
|
import { enumGroup } from "./enum-group.ts";
|
|
46
|
-
import { generatedFile } from "./generated-file.ts";
|
|
46
|
+
import { generatedFile, generatedFilesOf } from "./generated-file.ts";
|
|
47
47
|
import { sourceFilesOf } from "./source-files.ts";
|
|
48
48
|
import {
|
|
49
|
-
|
|
49
|
+
evidenceRecords,
|
|
50
|
+
heldRecords,
|
|
50
51
|
disabledClasses,
|
|
51
52
|
exemptionsOf,
|
|
52
53
|
holdingWhile,
|
|
@@ -56,7 +57,8 @@ import {
|
|
|
56
57
|
type Detectors,
|
|
57
58
|
} from "./exempt.ts";
|
|
58
59
|
import type { TSExemptionClass } from "./exempt-classes.ts";
|
|
59
|
-
import { heldByInclusion, sourceTree } from "./file-facts.ts";
|
|
60
|
+
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.ts";
|
|
61
|
+
import { globExpression } from "./glob.ts";
|
|
60
62
|
import type { Finding } from "./finding.ts";
|
|
61
63
|
import { findingsPass, recordedFindings, rowKey, type PassResult } from "./findings-pass.ts";
|
|
62
64
|
import type { EmitterInput } from "./findings/emitter.ts";
|
|
@@ -72,7 +74,7 @@ import { injectionContainer } from "./injection-container.ts";
|
|
|
72
74
|
import { interfaceSatisfaction } from "./interface-satisfaction.ts";
|
|
73
75
|
import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-function-parts.ts";
|
|
74
76
|
import { inventory, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
75
|
-
import { readManifest } from "./manifest.ts";
|
|
77
|
+
import { packageEntries, readManifest } from "./manifest.ts";
|
|
76
78
|
import {
|
|
77
79
|
liveFilesAt,
|
|
78
80
|
matrixOf,
|
|
@@ -82,7 +84,7 @@ import {
|
|
|
82
84
|
type Matrix,
|
|
83
85
|
type SweepResult,
|
|
84
86
|
} from "./matrix.ts";
|
|
85
|
-
import { joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
87
|
+
import { joinPath, normalizePath, relativePath, resolvePath } from "./paths.ts";
|
|
86
88
|
import type { UnansweredQuestion } from "./query.ts";
|
|
87
89
|
import { byPosition, isComponentFile, type Position } from "./position.ts";
|
|
88
90
|
import type { TypeErrorSkip, UnansweredCount } from "./report.ts";
|
|
@@ -99,6 +101,7 @@ import { references, testFileRulesOf, type Reference, type TestFileRule } from "
|
|
|
99
101
|
import { roots, unmatchedEverywhere, type RootKind, type Roots } from "./roots.ts";
|
|
100
102
|
import type { Scope } from "./scope.ts";
|
|
101
103
|
import { serializationContract } from "./serialization-contract.ts";
|
|
104
|
+
import { outsideTestReferences, testFilesAt, type OutsideTest } from "./outside-tests.ts";
|
|
102
105
|
import { supportReferences, testSupportFiles } from "./test-support.ts";
|
|
103
106
|
import { diagnosticsOf, ownedAs, runSession, type Engine, type ProjectView } from "./session.ts";
|
|
104
107
|
import { accessorsOf, storesOf } from "./stores.ts";
|
|
@@ -188,7 +191,7 @@ interface ProjectRead {
|
|
|
188
191
|
readonly typeErrorSkips: readonly TypeErrorSkip[];
|
|
189
192
|
/** The lines each of those skips withholds every finding on. */
|
|
190
193
|
readonly skipped: readonly SkippedUnit[];
|
|
191
|
-
/** The project's generated files, by path below the target root, each with the
|
|
194
|
+
/** The project's generated files, by path below the target root, each with the detail its retentions name. */
|
|
192
195
|
readonly generated: ReadonlyMap<string, string>;
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -219,6 +222,10 @@ interface ReadProjects<Answer> {
|
|
|
219
222
|
readonly componentsUnread: readonly ComponentsUnread[];
|
|
220
223
|
/** The convention rows the run applied. */
|
|
221
224
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
225
|
+
/** The dependencies the applied rows' short names use. */
|
|
226
|
+
readonly conventionUses: readonly string[];
|
|
227
|
+
/** The test files below the target root no project the run reads holds, parsed. */
|
|
228
|
+
readonly outsideTests: readonly OutsideTest[];
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
/**
|
|
@@ -284,7 +291,15 @@ function readProjects<Answer>(
|
|
|
284
291
|
const options = {
|
|
285
292
|
host,
|
|
286
293
|
manifest: workspace?.manifest ?? readManifest(host, targetRoot),
|
|
287
|
-
|
|
294
|
+
otherPackages: [
|
|
295
|
+
...(workspace?.entries ?? []),
|
|
296
|
+
...packageEntries(
|
|
297
|
+
host,
|
|
298
|
+
normalizePath(absoluteRoot),
|
|
299
|
+
discovered.configFiles,
|
|
300
|
+
new Set(workspace?.memberDirs ?? []),
|
|
301
|
+
),
|
|
302
|
+
],
|
|
288
303
|
patterns: config.rootPatterns,
|
|
289
304
|
entryFiles: config.ts.entryFiles,
|
|
290
305
|
conventions,
|
|
@@ -304,6 +319,8 @@ function readProjects<Answer>(
|
|
|
304
319
|
const unbuildable: SetupFailure[] = [];
|
|
305
320
|
const setupDropped: SetupFailure[] = [];
|
|
306
321
|
const consumed = new Map<string, Reference>();
|
|
322
|
+
const heldFiles = new Set<string>();
|
|
323
|
+
let outsideTests: readonly OutsideTest[] = [];
|
|
307
324
|
const setupMet = (configFile: string, met: readonly SetupFailure[]): void => {
|
|
308
325
|
const [first] = met;
|
|
309
326
|
if (first !== undefined && isGuess(discovered, configFile)) {
|
|
@@ -420,6 +437,7 @@ function readProjects<Answer>(
|
|
|
420
437
|
...kept.map((id) => ({ id, kind: "type-error" as const, source: "" })),
|
|
421
438
|
],
|
|
422
439
|
};
|
|
440
|
+
const own = sourceFilesOf(project, targetRoot);
|
|
423
441
|
return {
|
|
424
442
|
configuration,
|
|
425
443
|
configFile: project.configFile,
|
|
@@ -431,13 +449,27 @@ function readProjects<Answer>(
|
|
|
431
449
|
.ownSourceFiles()
|
|
432
450
|
.filter((file) => referenceOnly(file.fileName))
|
|
433
451
|
.flatMap((file) => relativePath(absoluteRoot, file.fileName) ?? []),
|
|
434
|
-
generated:
|
|
452
|
+
generated: generatedFilesOf(own, generatedFiles(conventions, own)),
|
|
435
453
|
};
|
|
436
454
|
};
|
|
455
|
+
for (const file of project.ownSourceFiles()) {
|
|
456
|
+
heldFiles.add(relativePath(absoluteRoot, file.fileName) ?? file.fileName);
|
|
457
|
+
}
|
|
437
458
|
return stage(project, { configuration, read });
|
|
438
459
|
},
|
|
439
460
|
discovered.derived ? "record" : "refuse",
|
|
440
461
|
workspace?.programs,
|
|
462
|
+
() => {
|
|
463
|
+
const patterns = config.ts.testFiles.map(globExpression);
|
|
464
|
+
outsideTests = testFilesAt(
|
|
465
|
+
host,
|
|
466
|
+
targetRoot,
|
|
467
|
+
sourcePaths(host, targetRoot).filter(
|
|
468
|
+
(path) => !heldFiles.has(path) && patterns.some((pattern) => pattern.test(path)),
|
|
469
|
+
),
|
|
470
|
+
options.parse,
|
|
471
|
+
);
|
|
472
|
+
},
|
|
441
473
|
);
|
|
442
474
|
for (const configFile of session.unopened) {
|
|
443
475
|
if (consumerOf.has(configFile) || !isGuess(discovered, configFile)) {
|
|
@@ -475,6 +507,8 @@ function readProjects<Answer>(
|
|
|
475
507
|
unanswered: session.unanswered,
|
|
476
508
|
componentsUnread: workspace?.componentsUnread ?? [],
|
|
477
509
|
conventionsApplied: conventions.applied,
|
|
510
|
+
conventionUses: conventions.uses,
|
|
511
|
+
outsideTests,
|
|
478
512
|
};
|
|
479
513
|
}
|
|
480
514
|
|
|
@@ -630,6 +664,17 @@ interface SweptProject<Extra> {
|
|
|
630
664
|
readonly extra: Extra;
|
|
631
665
|
}
|
|
632
666
|
|
|
667
|
+
/** What one project's stage reads while its view is open, before the run's test-support code is known. */
|
|
668
|
+
interface ReadProject<Extra> {
|
|
669
|
+
readonly projectRead: ProjectRead;
|
|
670
|
+
readonly references: readonly Reference[];
|
|
671
|
+
readonly testFiles: readonly string[];
|
|
672
|
+
readonly evidence: readonly Exemption[];
|
|
673
|
+
readonly componentFiles: readonly string[];
|
|
674
|
+
readonly componentWarnings: readonly ComponentWarning[];
|
|
675
|
+
readonly extra: Extra;
|
|
676
|
+
}
|
|
677
|
+
|
|
633
678
|
/** What a run read of its projects before any sweep: the matrix, its exemptions, and each project's answers. */
|
|
634
679
|
interface ReadRun<Extra> {
|
|
635
680
|
readonly matrix: Matrix;
|
|
@@ -647,6 +692,8 @@ interface ReadRun<Extra> {
|
|
|
647
692
|
readonly componentsUnread: readonly ComponentsUnread[];
|
|
648
693
|
/** The convention rows the run applied. */
|
|
649
694
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
695
|
+
/** The dependencies the applied rows' short names use. */
|
|
696
|
+
readonly conventionUses: readonly string[];
|
|
650
697
|
}
|
|
651
698
|
|
|
652
699
|
/**
|
|
@@ -674,18 +721,12 @@ function readRun<Extra>(
|
|
|
674
721
|
? { delimiters: config.analysis.templateDelimiters, files: [] }
|
|
675
722
|
: readTemplates(host, targetRoot, config.analysis);
|
|
676
723
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
677
|
-
const read = readProjects<
|
|
724
|
+
const read = readProjects<ReadProject<Extra>>(engine, host, scope, config, (project, opened) => {
|
|
678
725
|
const projectRead = opened.read();
|
|
679
726
|
const resolved = references(project, projectRead.held, targetRoot, {
|
|
680
727
|
testFiles: config.ts.testFiles,
|
|
681
728
|
});
|
|
682
|
-
const
|
|
683
|
-
projectRead.held.symbols,
|
|
684
|
-
resolved.references,
|
|
685
|
-
projectRead.rooted.liveUnderReachability,
|
|
686
|
-
resolved.testFilePaths,
|
|
687
|
-
);
|
|
688
|
-
const detected = detectExemptions(
|
|
729
|
+
const evidence = evidenceRecords(
|
|
689
730
|
{
|
|
690
731
|
project,
|
|
691
732
|
held: projectRead.held,
|
|
@@ -698,56 +739,87 @@ function readRun<Extra>(
|
|
|
698
739
|
: { generated: projectRead.generated }),
|
|
699
740
|
},
|
|
700
741
|
detectors,
|
|
701
|
-
|
|
702
|
-
disabled,
|
|
703
|
-
mode,
|
|
704
|
-
testFiles: new Set([...resolved.testFilePaths, ...support]),
|
|
705
|
-
},
|
|
742
|
+
disabled,
|
|
706
743
|
);
|
|
744
|
+
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
745
|
+
const pathOf = (file: (typeof components)[number]): string =>
|
|
746
|
+
relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
747
|
+
return {
|
|
748
|
+
projectRead,
|
|
749
|
+
references: resolved.references,
|
|
750
|
+
testFiles: resolved.testFilePaths,
|
|
751
|
+
evidence,
|
|
752
|
+
componentFiles: components.map(pathOf),
|
|
753
|
+
componentWarnings: components.flatMap((file) =>
|
|
754
|
+
readComponent(file.originalText).warnings.map((warning) => ({
|
|
755
|
+
path: pathOf(file),
|
|
756
|
+
line: lineOf(file.originalText, warning.offset),
|
|
757
|
+
reason: warning.reason,
|
|
758
|
+
})),
|
|
759
|
+
),
|
|
760
|
+
extra: extra(project, projectRead, resolved.references),
|
|
761
|
+
};
|
|
762
|
+
});
|
|
763
|
+
const outsidePaths = read.outsideTests.map((one) => one.path);
|
|
764
|
+
const withOutside = read.answers.map((one) => ({
|
|
765
|
+
...one,
|
|
766
|
+
references: [
|
|
767
|
+
...one.references,
|
|
768
|
+
...outsideTestReferences(read.outsideTests, one.projectRead.held, targetRoot),
|
|
769
|
+
],
|
|
770
|
+
testFiles: [...one.testFiles, ...outsidePaths],
|
|
771
|
+
}));
|
|
772
|
+
// Test-support code is code only test code imports in any configuration of the run: a
|
|
773
|
+
// configuration that holds no test file says nothing about what the tests import.
|
|
774
|
+
const support = testSupportFiles(
|
|
775
|
+
withOutside.flatMap((one) => one.projectRead.held.symbols),
|
|
776
|
+
withOutside.flatMap((one) => one.references),
|
|
777
|
+
withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability),
|
|
778
|
+
withOutside.flatMap((one) => one.testFiles),
|
|
779
|
+
);
|
|
780
|
+
const answers = withOutside.map((one): SweptProject<Extra> => {
|
|
781
|
+
const { projectRead } = one;
|
|
782
|
+
const detected = heldRecords(one.evidence, {
|
|
783
|
+
disabled,
|
|
784
|
+
mode,
|
|
785
|
+
testFiles: new Set([...one.testFiles, ...support]),
|
|
786
|
+
});
|
|
707
787
|
const exempt = detected.records;
|
|
708
|
-
const made = supportReferences(
|
|
788
|
+
const made = supportReferences(one.references, support);
|
|
789
|
+
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
790
|
+
const supportFiles = [...support].filter((path) => held.has(path)).sort();
|
|
709
791
|
const configured: Configured = {
|
|
710
792
|
configuration: projectRead.configuration,
|
|
711
793
|
symbols: projectRead.held.symbols,
|
|
712
794
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
713
795
|
roots: projectRead.rooted.liveUnderReachability,
|
|
714
|
-
testFiles:
|
|
715
|
-
...(
|
|
796
|
+
testFiles: one.testFiles,
|
|
797
|
+
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
716
798
|
...(projectRead.referenceOnly.length === 0
|
|
717
799
|
? {}
|
|
718
800
|
: { referenceOnly: projectRead.referenceOnly }),
|
|
719
801
|
};
|
|
720
|
-
const unanswered = [
|
|
721
|
-
...projectRead.held.unanswered,
|
|
722
|
-
...projectRead.rooted.unanswered,
|
|
723
|
-
...made
|
|
724
|
-
.filter((reference) => reference.resolution === "by-name")
|
|
725
|
-
.map((reference) => reference.to),
|
|
726
|
-
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
727
|
-
];
|
|
728
|
-
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
729
|
-
const pathOf = (file: (typeof components)[number]): string =>
|
|
730
|
-
relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
731
802
|
return {
|
|
732
803
|
configFile: projectRead.configFile,
|
|
733
804
|
configured,
|
|
734
805
|
exempt,
|
|
735
|
-
unanswered
|
|
806
|
+
unanswered: [
|
|
807
|
+
...projectRead.held.unanswered,
|
|
808
|
+
...projectRead.rooted.unanswered,
|
|
809
|
+
...made
|
|
810
|
+
.filter((reference) => reference.resolution === "by-name")
|
|
811
|
+
.map((reference) => reference.to),
|
|
812
|
+
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
813
|
+
],
|
|
736
814
|
typeErrorSkips: projectRead.typeErrorSkips,
|
|
737
815
|
skipped: projectRead.skipped,
|
|
738
|
-
componentFiles:
|
|
739
|
-
componentWarnings:
|
|
740
|
-
|
|
741
|
-
path: pathOf(file),
|
|
742
|
-
line: lineOf(file.originalText, warning.offset),
|
|
743
|
-
reason: warning.reason,
|
|
744
|
-
})),
|
|
745
|
-
),
|
|
746
|
-
extra: extra(project, projectRead, made),
|
|
816
|
+
componentFiles: one.componentFiles,
|
|
817
|
+
componentWarnings: one.componentWarnings,
|
|
818
|
+
extra: one.extra,
|
|
747
819
|
};
|
|
748
820
|
});
|
|
749
821
|
const consumed = read.consumers.references;
|
|
750
|
-
const projects =
|
|
822
|
+
const projects = answers.map((one) =>
|
|
751
823
|
consumed.length === 0
|
|
752
824
|
? one
|
|
753
825
|
: {
|
|
@@ -763,7 +835,7 @@ function readRun<Extra>(
|
|
|
763
835
|
exempt: exemptionsOf(projects.flatMap((one) => one.exempt)),
|
|
764
836
|
unansweredHeld: [
|
|
765
837
|
...new Set([
|
|
766
|
-
...
|
|
838
|
+
...answers.flatMap((one) => one.unanswered),
|
|
767
839
|
...consumed.filter((one) => one.resolution === "by-name").map((one) => one.to),
|
|
768
840
|
]),
|
|
769
841
|
],
|
|
@@ -773,6 +845,7 @@ function readRun<Extra>(
|
|
|
773
845
|
unanswered: read.unanswered,
|
|
774
846
|
componentsUnread: read.componentsUnread,
|
|
775
847
|
conventionsApplied: read.conventionsApplied,
|
|
848
|
+
conventionUses: read.conventionUses,
|
|
776
849
|
};
|
|
777
850
|
}
|
|
778
851
|
|
|
@@ -1027,7 +1100,7 @@ function emitterInputOver(
|
|
|
1027
1100
|
dependencies: dependenciesOf(
|
|
1028
1101
|
host,
|
|
1029
1102
|
targetRoot,
|
|
1030
|
-
projects.map((one) => one.extra.needs),
|
|
1103
|
+
[...projects.map((one) => one.extra.needs), configurationNeeds(read.conventionUses)],
|
|
1031
1104
|
swept.sweep.candidates.map((candidate) => candidate.id),
|
|
1032
1105
|
),
|
|
1033
1106
|
implementations: mergeImplementations(projects.map((one) => one.extra.implementations)),
|
package/src/config.ts
CHANGED
|
@@ -232,9 +232,12 @@ export function defaultConfig(): Config {
|
|
|
232
232
|
ts: {
|
|
233
233
|
testFiles: [
|
|
234
234
|
"**/*.test.{ts,tsx,mts,cts}",
|
|
235
|
+
"**/*.test-d.{ts,tsx,mts,cts}",
|
|
236
|
+
"**/*.spec-d.{ts,tsx,mts,cts}",
|
|
235
237
|
"**/*.spec.*",
|
|
236
238
|
"**/__tests__/**",
|
|
237
239
|
"**/__mocks__/**",
|
|
240
|
+
"**/test-d/**",
|
|
238
241
|
],
|
|
239
242
|
entryFiles: [],
|
|
240
243
|
componentExtensions: [".vue", ".svelte", ".astro"],
|
|
@@ -57,7 +57,7 @@ const COUNTERPARTS: readonly (readonly [string, readonly string[]])[] = [
|
|
|
57
57
|
const APPENDED = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"] as const;
|
|
58
58
|
|
|
59
59
|
/** The extensions the program's module resolution appends to a specifier written without one. */
|
|
60
|
-
const RESOLVED_EXTENSIONS = [
|
|
60
|
+
export const RESOLVED_EXTENSIONS = [
|
|
61
61
|
".ts",
|
|
62
62
|
".tsx",
|
|
63
63
|
".d.ts",
|
|
@@ -237,33 +237,36 @@ function relativeSpecifiers(file: SourceFile): readonly string[] {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/**
|
|
240
|
-
* The
|
|
241
|
-
*
|
|
242
|
-
*
|
|
240
|
+
* The paths one relative specifier from a configuration file may reach, in the order they
|
|
241
|
+
* are tried: the path it names where that ends with a module extension, that path with its
|
|
242
|
+
* JavaScript extension replaced by the TypeScript counterparts, that path with an
|
|
243
|
+
* extension appended, and a directory's `index` with one appended. A data file such as
|
|
244
|
+
* `./package.json` is no module, so it is no candidate.
|
|
243
245
|
*/
|
|
244
|
-
function
|
|
246
|
+
export function specifierCandidates(dir: string, specifier: string): readonly string[] {
|
|
245
247
|
const path = resolvePath(dir, specifier);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return path;
|
|
248
|
+
const candidates: string[] = [];
|
|
249
|
+
if (APPENDED.some((extension) => path.endsWith(extension))) {
|
|
250
|
+
candidates.push(path);
|
|
250
251
|
}
|
|
251
252
|
for (const [extension, counterparts] of COUNTERPARTS) {
|
|
252
253
|
if (path.endsWith(extension)) {
|
|
253
254
|
const stem = path.slice(0, -extension.length);
|
|
254
|
-
|
|
255
|
-
if (found !== undefined) {
|
|
256
|
-
return found;
|
|
257
|
-
}
|
|
255
|
+
candidates.push(...counterparts.map((one) => stem + one));
|
|
258
256
|
}
|
|
259
257
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
258
|
+
candidates.push(...APPENDED.map((extension) => path + extension));
|
|
259
|
+
candidates.push(...APPENDED.map((extension) => joinPath(path, `index${extension}`)));
|
|
260
|
+
return candidates;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** The file on disk one relative specifier from a configuration file reaches, or undefined. */
|
|
264
|
+
export function resolveRelativeSpecifier(
|
|
265
|
+
host: Host,
|
|
266
|
+
dir: string,
|
|
267
|
+
specifier: string,
|
|
268
|
+
): string | undefined {
|
|
269
|
+
return specifierCandidates(dir, specifier).find((path) => host.kindOf(path) === "file");
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
/**
|
|
@@ -311,7 +314,7 @@ function outsideModules(
|
|
|
311
314
|
const file = parse(next.path, text);
|
|
312
315
|
found.push({ file, manifestDir: next.manifestDir });
|
|
313
316
|
for (const specifier of relativeSpecifiers(file)) {
|
|
314
|
-
const reached =
|
|
317
|
+
const reached = resolveRelativeSpecifier(host, dirnamePath(next.path), specifier);
|
|
315
318
|
if (reached !== undefined) {
|
|
316
319
|
pending.push({ path: reached, manifestDir: next.manifestDir });
|
|
317
320
|
}
|
|
@@ -347,12 +350,13 @@ export function dependenciesNamed(text: string, declared: readonly string[]): re
|
|
|
347
350
|
}
|
|
348
351
|
|
|
349
352
|
/**
|
|
350
|
-
* The own files one string names read against a directory:
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
+
* The own files one string names read against a directory: the file it names directly or
|
|
354
|
+
* through the emit mapping, or the file it names with an extension the module resolution
|
|
355
|
+
* appends. An empty string, one starting with `/` and one holding a glob character names
|
|
356
|
+
* none.
|
|
353
357
|
*/
|
|
354
358
|
export function filesNamedBy(files: SourceFiles, dir: string, text: string): readonly SourceFile[] {
|
|
355
|
-
if (
|
|
359
|
+
if (text === "" || text.startsWith("/") || /[*?[{]/u.test(text)) {
|
|
356
360
|
return [];
|
|
357
361
|
}
|
|
358
362
|
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
|
} as const;
|
package/src/contextual-uses.ts
CHANGED
|
@@ -97,15 +97,19 @@ interface Called {
|
|
|
97
97
|
interface Checked {
|
|
98
98
|
readonly source: Type;
|
|
99
99
|
readonly targets: readonly Type[];
|
|
100
|
+
/**
|
|
101
|
+
* Whether the source is declared outside the target, so code the analysis does not
|
|
102
|
+
* hold reads the members of the target's types it declares.
|
|
103
|
+
*/
|
|
104
|
+
readonly outside: boolean;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
/** How many element and signature steps the structural walk takes below a value's own type. */
|
|
103
|
-
const MAX_DEPTH = 3;
|
|
104
|
-
|
|
105
107
|
/** One member of an object type, by the inventory's declarations and the checker's declaration handles. */
|
|
106
108
|
interface Member {
|
|
107
109
|
readonly ids: readonly string[];
|
|
108
110
|
readonly handles: ReadonlySet<string>;
|
|
111
|
+
/** Whether a declaration of it is in one of the target's own files. */
|
|
112
|
+
readonly own: boolean;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
/**
|
|
@@ -123,6 +127,7 @@ export function contextualUses<Brand>(
|
|
|
123
127
|
properties: PropertyTables,
|
|
124
128
|
): ContextualUses {
|
|
125
129
|
const queries = project.queries;
|
|
130
|
+
const ownPaths = project.ownPaths();
|
|
126
131
|
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
127
132
|
let batches = 0;
|
|
128
133
|
let lookups = 0;
|
|
@@ -165,6 +170,7 @@ export function contextualUses<Brand>(
|
|
|
165
170
|
handles: new Set(
|
|
166
171
|
property.declarations.map((handle) => `${handle.path}#${String(handle.index)}`),
|
|
167
172
|
),
|
|
173
|
+
own: property.declarations.some((handle) => ownPaths.has(handle.path)),
|
|
168
174
|
});
|
|
169
175
|
}
|
|
170
176
|
tables.set(type.id, table);
|
|
@@ -206,6 +212,11 @@ export function contextualUses<Brand>(
|
|
|
206
212
|
member.ids.filter((id) => kinds.get(id) === "type-member");
|
|
207
213
|
const carries = (part: Type): boolean =>
|
|
208
214
|
[...membersOf(part).values()].some((member) => readable(member).length > 0);
|
|
215
|
+
/** Whether an object type has members and declares none of them in the target. */
|
|
216
|
+
const outside = (part: Type): boolean => {
|
|
217
|
+
const members = [...membersOf(part).values()];
|
|
218
|
+
return members.length > 0 && members.every((member) => !member.own);
|
|
219
|
+
};
|
|
209
220
|
|
|
210
221
|
const elements = new Map<number, Type | undefined>();
|
|
211
222
|
/** The element type of an array type, read once per type. */
|
|
@@ -254,12 +265,12 @@ export function contextualUses<Brand>(
|
|
|
254
265
|
* Each object type a value of `source` is checked as against the object types of
|
|
255
266
|
* `target`: the value's own, and through array elements and through a function's
|
|
256
267
|
* parameters, checked the other way, and its result. A source that is one of the
|
|
257
|
-
* target's own object types matches it as it stands.
|
|
268
|
+
* target's own object types matches it as it stands. Each pair is compared once, which
|
|
269
|
+
* ends the walk over a recursive type.
|
|
258
270
|
*/
|
|
259
271
|
const compared = (
|
|
260
272
|
source: Type | undefined,
|
|
261
273
|
target: Type | undefined,
|
|
262
|
-
depth: number,
|
|
263
274
|
into: Checked[],
|
|
264
275
|
seen: Set<string>,
|
|
265
276
|
): void => {
|
|
@@ -267,7 +278,7 @@ export function contextualUses<Brand>(
|
|
|
267
278
|
if (source === undefined || target === undefined || source.id === target.id) {
|
|
268
279
|
return;
|
|
269
280
|
}
|
|
270
|
-
if (
|
|
281
|
+
if (seen.has(key)) {
|
|
271
282
|
return;
|
|
272
283
|
}
|
|
273
284
|
seen.add(key);
|
|
@@ -275,13 +286,15 @@ export function contextualUses<Brand>(
|
|
|
275
286
|
const sources = partsOf(source).filter((one) => !targets.some((part) => part.id === one.id));
|
|
276
287
|
for (const one of sources) {
|
|
277
288
|
if (carries(one)) {
|
|
278
|
-
into.push({ source: one, targets });
|
|
289
|
+
into.push({ source: one, targets, outside: false });
|
|
290
|
+
} else if (outside(one)) {
|
|
291
|
+
into.push({ source: one, targets, outside: true });
|
|
279
292
|
}
|
|
280
293
|
const element = elementOf(one);
|
|
281
294
|
const called = element === undefined ? callOf(one) : undefined;
|
|
282
295
|
for (const part of targets) {
|
|
283
296
|
if (element !== undefined) {
|
|
284
|
-
compared(element, elementOf(part),
|
|
297
|
+
compared(element, elementOf(part), into, seen);
|
|
285
298
|
continue;
|
|
286
299
|
}
|
|
287
300
|
const other = called === undefined ? undefined : callOf(part);
|
|
@@ -289,30 +302,37 @@ export function contextualUses<Brand>(
|
|
|
289
302
|
continue;
|
|
290
303
|
}
|
|
291
304
|
other.parameters.forEach((parameter, index) => {
|
|
292
|
-
compared(parameter, called.parameters[index],
|
|
305
|
+
compared(parameter, called.parameters[index], into, seen);
|
|
293
306
|
});
|
|
294
|
-
compared(called.result, other.result,
|
|
307
|
+
compared(called.result, other.result, into, seen);
|
|
295
308
|
}
|
|
296
309
|
}
|
|
297
310
|
};
|
|
298
311
|
|
|
299
|
-
/**
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Whether a value of one type may carry a member a position reads, at any depth the
|
|
314
|
+
* walk takes. A type met again on the way adds nothing its first visit did not.
|
|
315
|
+
*/
|
|
316
|
+
const mayCarry = (type: Type, seen: Set<number> = new Set()): boolean => {
|
|
317
|
+
if (seen.has(type.id)) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
seen.add(type.id);
|
|
321
|
+
return partsOf(type).some((part) => {
|
|
322
|
+
if (carries(part) || outside(part)) {
|
|
304
323
|
return true;
|
|
305
324
|
}
|
|
306
325
|
const element = elementOf(part);
|
|
307
326
|
if (element !== undefined) {
|
|
308
|
-
return mayCarry(element,
|
|
327
|
+
return mayCarry(element, seen);
|
|
309
328
|
}
|
|
310
329
|
return callOf(part) !== undefined;
|
|
311
330
|
});
|
|
331
|
+
};
|
|
312
332
|
|
|
313
333
|
const sourced = carried.flatMap((flow, index) => {
|
|
314
334
|
const type = types[index];
|
|
315
|
-
return type === undefined || !isAnswered(type) || !mayCarry(type
|
|
335
|
+
return type === undefined || !isAnswered(type) || !mayCarry(type) ? [] : [{ flow, type }];
|
|
316
336
|
});
|
|
317
337
|
batches += batchesOf(sourced.length);
|
|
318
338
|
const positions = queries.contextualTypes(
|
|
@@ -326,11 +346,22 @@ export function contextualUses<Brand>(
|
|
|
326
346
|
return;
|
|
327
347
|
}
|
|
328
348
|
const checked: Checked[] = [];
|
|
329
|
-
compared(type, position,
|
|
349
|
+
compared(type, position, checked, new Set());
|
|
330
350
|
const ids = new Set<string>();
|
|
331
|
-
for (const { source, targets } of checked) {
|
|
351
|
+
for (const { source, targets, outside: held } of checked) {
|
|
332
352
|
const tables = targets.map(membersOf);
|
|
333
|
-
|
|
353
|
+
const sourceMembers = membersOf(source);
|
|
354
|
+
if (held) {
|
|
355
|
+
for (const table of tables) {
|
|
356
|
+
for (const [name, member] of table) {
|
|
357
|
+
if (sourceMembers.has(name)) {
|
|
358
|
+
member.ids.forEach((id) => ids.add(id));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
for (const [name, member] of sourceMembers) {
|
|
334
365
|
const read = tables.some((table) => {
|
|
335
366
|
const declared = table.get(name);
|
|
336
367
|
return (
|
package/src/convention-rows.ts
CHANGED
|
@@ -25,6 +25,12 @@ interface MoveReading {
|
|
|
25
25
|
readonly call?: OptionsCall;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** One configuration property whose value names a package by a short name. */
|
|
29
|
+
export interface ShortNameReading extends MoveReading {
|
|
30
|
+
/** The package name a value stands for, written with `{}` where the value goes. */
|
|
31
|
+
readonly package: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
/** One directory a row's globs name that a configuration property moves. */
|
|
29
35
|
export interface DirectoryMove {
|
|
30
36
|
/** The placeholder the row's globs and later moves write as `<id>`. */
|
|
@@ -56,6 +62,8 @@ export interface ConventionRow {
|
|
|
56
62
|
readonly generated?: readonly string[];
|
|
57
63
|
/** The directories configuration moves, in the order their placeholders depend on each other. */
|
|
58
64
|
readonly moves: readonly DirectoryMove[];
|
|
65
|
+
/** The properties that name a package the configuration uses by a short name. */
|
|
66
|
+
readonly shortNames?: readonly ShortNameReading[];
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
const SCRIPT = "{js,jsx,ts,tsx}";
|
|
@@ -444,5 +452,12 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
444
452
|
`**/{vitest,vite}.*.config.${ANY_MODULE}`,
|
|
445
453
|
],
|
|
446
454
|
moves: [],
|
|
455
|
+
shortNames: [
|
|
456
|
+
{
|
|
457
|
+
property: "test.coverage.provider",
|
|
458
|
+
files: [`{vitest,vite}.config.${ANY_MODULE}`],
|
|
459
|
+
package: "@vitest/coverage-{}",
|
|
460
|
+
},
|
|
461
|
+
],
|
|
447
462
|
},
|
|
448
463
|
];
|