@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.21
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/alias-chain.js +33 -2
- package/dist/src/analysis.js +18 -77
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contract/contract.generated.js +4 -3
- package/dist/src/dependencies.js +1 -1
- package/dist/src/exemptions/argument-places.js +131 -0
- package/dist/src/{calls.js → exemptions/calls.js} +126 -179
- package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
- package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
- package/dist/src/exemptions/detectors.js +48 -0
- package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
- package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
- package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
- package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
- package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
- package/dist/src/exemptions/installed-globals.js +173 -0
- package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
- package/dist/src/exemptions/lifecycle.js +150 -0
- package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
- package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
- package/dist/src/exemptions/sites.js +182 -0
- package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
- package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
- package/dist/src/index/sites.js +1 -1
- package/dist/src/index/values.js +124 -0
- package/dist/src/liveness/sweep.js +2 -2
- package/dist/src/query.js +48 -0
- package/dist/src/roots/configured-declarations.js +1 -1
- package/dist/src/verbs/print-retained.js +23 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alias-chain.ts +38 -2
- package/src/analysis.ts +19 -100
- package/src/bindings.ts +1 -1
- package/src/conformance-record.ts +2 -2
- package/src/contract/contract.generated.ts +4 -3
- package/src/dependencies.ts +1 -1
- package/src/exemptions/argument-places.ts +198 -0
- package/src/{calls.ts → exemptions/calls.ts} +190 -224
- package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
- package/src/exemptions/decorator.ts +56 -0
- package/src/exemptions/detectors.ts +75 -0
- package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
- package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
- package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
- package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
- package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
- package/src/exemptions/installed-globals.ts +255 -0
- package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
- package/src/exemptions/lifecycle.ts +286 -0
- package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
- package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
- package/src/exemptions/sites.ts +293 -0
- package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
- package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
- package/src/facts.ts +1 -1
- package/src/index/sites.ts +1 -1
- package/src/index/values.ts +170 -0
- package/src/liveness/sweep.ts +2 -2
- package/src/query.ts +109 -10
- package/src/roots/configured-declarations.ts +1 -2
- package/src/stores.ts +1 -1
- package/src/verbs/print-retained.ts +29 -1
- package/dist/src/exempt.js +0 -164
- package/dist/src/value-flow.js +0 -74
- package/src/decorator.ts +0 -78
- package/src/exempt.ts +0 -295
- package/src/value-flow.ts +0 -104
package/src/analysis.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
configuredRef,
|
|
16
16
|
declarationsNamed,
|
|
17
17
|
} from "./roots/configured-declarations.ts";
|
|
18
|
-
import { decorator } from "./decorator.ts";
|
|
19
18
|
import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.ts";
|
|
20
19
|
import { carriersOf, NO_CARRIERS, type Carriers } from "./exposure.ts";
|
|
21
20
|
import { deprecatedDeclarations, type Deprecation } from "./deprecation.ts";
|
|
@@ -26,32 +25,19 @@ import type { AppliedConvention } from "./entries/conventions.ts";
|
|
|
26
25
|
import { readSources, type SourceFacts } from "./entries/sources.ts";
|
|
27
26
|
import { readWorkflows, type Workflows } from "./entries/workflow-steps.ts";
|
|
28
27
|
import { readEdgeSides } from "./edges.ts";
|
|
29
|
-
import { enumGroup } from "./enum-group.ts";
|
|
30
28
|
import { bindingsOf, distinctBindings, type BindingRecord } from "./bindings.ts";
|
|
31
|
-
import {
|
|
29
|
+
import { generatedFilesOf, type GeneratedFact } from "./exemptions/generated-file.ts";
|
|
32
30
|
import { inOperandNames } from "./findings/in-operands.ts";
|
|
33
31
|
import { sourceFilesOf } from "./source-files.ts";
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
heldRecords,
|
|
37
|
-
disabledClasses,
|
|
38
|
-
exemptionsOf,
|
|
39
|
-
isUnansweredRecord,
|
|
40
|
-
writtenRecords,
|
|
41
|
-
type Detector,
|
|
42
|
-
type Detectors,
|
|
43
|
-
} from "./exempt.ts";
|
|
44
|
-
import type { TSExemptionClass } from "./exempt-classes.ts";
|
|
32
|
+
import { DETECTORS, detection } from "./exemptions/detectors.ts";
|
|
33
|
+
import { exemptionsOf, settle, type Detectors, type Exemption } from "./exemptions/lifecycle.ts";
|
|
45
34
|
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.ts";
|
|
46
35
|
import type { Finding } from "./finding.ts";
|
|
47
36
|
import { findingsPass, recordedFindings, rowKey, type PassResult } from "./findings-pass.ts";
|
|
48
37
|
import type { EmitterInput } from "./findings/emitter.ts";
|
|
49
38
|
import { unmatchedRootFindings, type SelfCheckFacts } from "./findings/self-check.ts";
|
|
50
|
-
import { frameworkLifecycle } from "./framework-lifecycle.ts";
|
|
51
39
|
import type { Host } from "./host.ts";
|
|
52
40
|
import { implementations, mergeImplementations } from "./findings/implementations.ts";
|
|
53
|
-
import { injectionContainer } from "./injection-container.ts";
|
|
54
|
-
import { interfaceSatisfaction, settledSatisfaction } from "./interface-satisfaction.ts";
|
|
55
41
|
import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-function-parts.ts";
|
|
56
42
|
import { type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
57
43
|
import { inventory } from "./inventory/enumerate.ts";
|
|
@@ -69,7 +55,6 @@ import type { UnansweredQuestion } from "./query.ts";
|
|
|
69
55
|
import { byPosition, isComponentFile, type Position } from "./position.ts";
|
|
70
56
|
import type { TypeErrorSkip, UnansweredCount } from "./report.ts";
|
|
71
57
|
import { skippedUnits, type SkippedUnit } from "./type-errors.ts";
|
|
72
|
-
import { reflectiveLookup } from "./reflective-lookup.ts";
|
|
73
58
|
import { references, type Reference } from "./references.ts";
|
|
74
59
|
import {
|
|
75
60
|
rootSet,
|
|
@@ -79,7 +64,7 @@ import {
|
|
|
79
64
|
type HeldScripts,
|
|
80
65
|
} from "./roots/root-set.ts";
|
|
81
66
|
import type { Scope } from "./scope.ts";
|
|
82
|
-
import { decodedMembers,
|
|
67
|
+
import { decodedMembers, type Decoded } from "./exemptions/serialization-contract.ts";
|
|
83
68
|
import { outsideTestReferences, testFilesAt, type OutsideTest } from "./tests/outside.ts";
|
|
84
69
|
import type { UnseenLoad } from "./roots/unseen-loads.ts";
|
|
85
70
|
import type { UnresolvedSpecifier } from "./load/specifiers.ts";
|
|
@@ -100,9 +85,7 @@ import { isPartKind, type SuppressionRecord, type Suppressions, type Verdict } f
|
|
|
100
85
|
import { bindRows, readBaseline, readIgnoreFile, type Recorded } from "./suppress-file.ts";
|
|
101
86
|
import { inlineDirectives, inlineSuppressions, type InlineDirective } from "./suppress-inline.ts";
|
|
102
87
|
import { sweep, type Sweep, type SweepPolicy } from "./liveness/sweep.ts";
|
|
103
|
-
import type { Exemption } from "./exempt.ts";
|
|
104
88
|
import type { Mode } from "./tests/file-class.ts";
|
|
105
|
-
import { readTemplates, templateField } from "./template-field.ts";
|
|
106
89
|
import { bytewise } from "./text/compare.ts";
|
|
107
90
|
import { holdsManifest, manifestAt } from "./fs/manifest.ts";
|
|
108
91
|
import { runDirectories } from "./entries/run-directories.ts";
|
|
@@ -444,19 +427,6 @@ interface ComponentWarning {
|
|
|
444
427
|
readonly reason: string;
|
|
445
428
|
}
|
|
446
429
|
|
|
447
|
-
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
448
|
-
export const DETECTORS: Detectors = new Map<TSExemptionClass, Detector>([
|
|
449
|
-
["interface-satisfaction", interfaceSatisfaction],
|
|
450
|
-
["enum-group", enumGroup],
|
|
451
|
-
["generated-file", generatedFile],
|
|
452
|
-
["template-field", templateField],
|
|
453
|
-
["reflective-lookup", reflectiveLookup],
|
|
454
|
-
["decorator", decorator],
|
|
455
|
-
["injection-container", injectionContainer],
|
|
456
|
-
["framework-lifecycle", frameworkLifecycle],
|
|
457
|
-
["serialization-contract", serializationContract],
|
|
458
|
-
]);
|
|
459
|
-
|
|
460
430
|
/** What the sweep of one run reads of each project, beside what a caller's stage adds. */
|
|
461
431
|
interface SweptProject<Extra> {
|
|
462
432
|
readonly configFile: string;
|
|
@@ -599,11 +569,8 @@ function readRun<Extra>(
|
|
|
599
569
|
) => Extra,
|
|
600
570
|
): ReadRun<Extra> {
|
|
601
571
|
const targetRoot = scope.target.path;
|
|
602
|
-
const disabled = disabledClasses(config);
|
|
603
|
-
const templates = disabled.has("template-field")
|
|
604
|
-
? { delimiters: config.analysis.template_delimiters, files: [] }
|
|
605
|
-
: readTemplates(host, targetRoot, config.analysis);
|
|
606
572
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
573
|
+
const detect = detection(host, targetRoot, config, consumers, detectors);
|
|
607
574
|
const directories = runDirectories(host, targetRoot, (fileName, text) =>
|
|
608
575
|
engine.parseSourceFile(fileName, text),
|
|
609
576
|
);
|
|
@@ -616,21 +583,7 @@ function readRun<Extra>(
|
|
|
616
583
|
formedForNode: projectRead.formedForNode,
|
|
617
584
|
});
|
|
618
585
|
const testFiles = testFilePaths(project, targetRoot, testFile);
|
|
619
|
-
const evidence =
|
|
620
|
-
{
|
|
621
|
-
project,
|
|
622
|
-
held: projectRead.held,
|
|
623
|
-
targetRoot,
|
|
624
|
-
templates,
|
|
625
|
-
ts: config.ts,
|
|
626
|
-
consumers,
|
|
627
|
-
...(config.analysis.generated_files === "include"
|
|
628
|
-
? {}
|
|
629
|
-
: { generated: projectRead.generated }),
|
|
630
|
-
},
|
|
631
|
-
detectors,
|
|
632
|
-
disabled,
|
|
633
|
-
);
|
|
586
|
+
const evidence = detect({ project, held: projectRead.held, generated: projectRead.generated });
|
|
634
587
|
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
635
588
|
const injected = project.program
|
|
636
589
|
.getSourceFileNames()
|
|
@@ -724,30 +677,29 @@ function readRun<Extra>(
|
|
|
724
677
|
...one.decoded.copied,
|
|
725
678
|
]),
|
|
726
679
|
]);
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
evidence: evidence[index] ?? [],
|
|
680
|
+
const settled = settle(
|
|
681
|
+
withOutside.map((one) => ({
|
|
682
|
+
records: one.evidence,
|
|
731
683
|
held: one.projectRead.held,
|
|
684
|
+
testFiles: new Set([...one.testFiles, ...support]),
|
|
732
685
|
})),
|
|
733
|
-
|
|
686
|
+
{
|
|
687
|
+
references: [...withOutside.flatMap((one) => one.references), ...read.result.references],
|
|
688
|
+
written,
|
|
689
|
+
mode,
|
|
690
|
+
},
|
|
734
691
|
);
|
|
735
692
|
const answers = withOutside.map((one, index): SweptProject<Extra> => {
|
|
736
693
|
const { projectRead } = one;
|
|
737
|
-
const
|
|
738
|
-
const
|
|
739
|
-
mode,
|
|
740
|
-
testFiles: new Set([...one.testFiles, ...support]),
|
|
741
|
-
});
|
|
742
|
-
const exempt = detected.records;
|
|
743
|
-
const made = [...one.references, ...(satisfied?.references ?? [])];
|
|
694
|
+
const exempt = settled[index]?.records ?? [];
|
|
695
|
+
const made = [...one.references, ...(settled[index]?.references ?? [])];
|
|
744
696
|
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
745
697
|
const supportFiles = [...support].filter((path) => held.has(path)).sort(bytewise);
|
|
746
698
|
const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort(bytewise);
|
|
747
699
|
const configured: Configured = {
|
|
748
700
|
configuration: projectRead.configuration,
|
|
749
701
|
symbols: projectRead.held.symbols,
|
|
750
|
-
references:
|
|
702
|
+
references: made,
|
|
751
703
|
roots: projectRead.rooted.liveUnderReachability,
|
|
752
704
|
testFiles: [...one.testFiles, ...supportFiles],
|
|
753
705
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
@@ -769,7 +721,7 @@ function readRun<Extra>(
|
|
|
769
721
|
...projectRead.rooted.unanswered,
|
|
770
722
|
...made.filter((reference) => reference.guessed === true).map((reference) => reference.to),
|
|
771
723
|
...one.held,
|
|
772
|
-
...exempt.filter(
|
|
724
|
+
...exempt.filter((record) => record.unanswered === true).map((record) => record.id),
|
|
773
725
|
...one.decoded.unanswered,
|
|
774
726
|
],
|
|
775
727
|
filledOutside: one.decoded.filled,
|
|
@@ -822,39 +774,6 @@ function readRun<Extra>(
|
|
|
822
774
|
};
|
|
823
775
|
}
|
|
824
776
|
|
|
825
|
-
/**
|
|
826
|
-
* The test references exemption evidence written in a test file makes: each is a read of
|
|
827
|
-
* the declaration it would retain, made by the test file at the evidence's site.
|
|
828
|
-
*/
|
|
829
|
-
function evidenceReferences(
|
|
830
|
-
symbols: readonly InventorySymbol[],
|
|
831
|
-
inTestFiles: readonly Exemption[],
|
|
832
|
-
): readonly Reference[] {
|
|
833
|
-
if (inTestFiles.length === 0) {
|
|
834
|
-
return [];
|
|
835
|
-
}
|
|
836
|
-
const files = new Map(
|
|
837
|
-
symbols
|
|
838
|
-
.filter((symbol) => symbol.kind === "file")
|
|
839
|
-
.map((symbol) => [symbol.position.path, symbol.id]),
|
|
840
|
-
);
|
|
841
|
-
return inTestFiles.flatMap((record) => {
|
|
842
|
-
const from = files.get(record.site.path);
|
|
843
|
-
return from === undefined
|
|
844
|
-
? []
|
|
845
|
-
: [
|
|
846
|
-
{
|
|
847
|
-
from,
|
|
848
|
-
to: record.id,
|
|
849
|
-
position: record.site,
|
|
850
|
-
use: "read",
|
|
851
|
-
resolution: "syntax",
|
|
852
|
-
test: true,
|
|
853
|
-
},
|
|
854
|
-
];
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
|
|
858
777
|
/**
|
|
859
778
|
* The declarations a write the mode counts stores into, a write a test-support file
|
|
860
779
|
* makes being a test's.
|
package/src/bindings.ts
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
type SourceFile,
|
|
30
30
|
} from "@typescript/native/ast";
|
|
31
31
|
import { GENERATORS } from "./contract/contract.generated.ts";
|
|
32
|
-
import type { GeneratedFact } from "./generated-file.ts";
|
|
32
|
+
import type { GeneratedFact } from "./exemptions/generated-file.ts";
|
|
33
33
|
import { componentOf, nameNodeOf } from "./inventory.ts";
|
|
34
34
|
import { nameComponent, spell } from "./ref.ts";
|
|
35
35
|
import { bytewise } from "./text/compare.ts";
|
|
@@ -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: "6.
|
|
5
|
+
corpusVersion: "6.1.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:352b870acbc05213d4869bc18611137d0782cc0c9a8379e7a998a3d9cea2d966",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|
|
@@ -4,7 +4,7 @@ export const CONTRACT_VERSION = "8.0.0";
|
|
|
4
4
|
export const SCHEMA_VERSIONS = ["9.0.0"] as const;
|
|
5
5
|
export const SCHEMA_VERSION = "9.0.0";
|
|
6
6
|
export const FACTS_VERSION = "1.0.0";
|
|
7
|
-
export const CORPUS_VERSION = "6.
|
|
7
|
+
export const CORPUS_VERSION = "6.1.0";
|
|
8
8
|
export const CODE_PREFIX = "DS";
|
|
9
9
|
export const FAMILY_KEY_LENGTH = 4;
|
|
10
10
|
|
|
@@ -421,7 +421,7 @@ export const KIND_ROWS = [
|
|
|
421
421
|
languages: ["go", "ts"],
|
|
422
422
|
subject: "declaration",
|
|
423
423
|
holdsLive: true,
|
|
424
|
-
rule: "An exported symbol whose every reference is inside the symbol's own package or module, reported as a candidate for unexporting. The subject is a package-level declaration or a method.
|
|
424
|
+
rule: "An exported symbol whose every reference is inside the symbol's own package or module, reported as a candidate for unexporting. The subject is a package-level declaration or a method. Four subjects are excluded. An interface method is excluded because its exportedness is the contract of the interface that declares it. A struct field is excluded because an encoder reads it by name. A method that satisfies an interface some symbol uses as a type is excluded because it cannot be unexported without its type ceasing to satisfy that interface, and so is a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, because that interface fixes them whether or not the program converts the method's type to it. A Go function a driver finds by its exported name is excluded, whatever else refers to it, because unexporting it stops the driver from running it: a test, a benchmark, a fuzz test or an example of a test file, TestMain, a test method a suite runner calls, and a go-fuzz entry point. The finding names the narrower visibility the references support. A declared cross-language edge counts as an out-of-package reference. Where the edge's other side is unknown to the analyzer, the finding is emitted pending. A reference from a Go external test package, a test file whose package clause names its directory's package with the suffix _test, is a reference from outside the package, because that test builds as another package and needs the export.",
|
|
425
425
|
precondition:
|
|
426
426
|
"Closed world only. A Go main package, and an internal/ directory tree for importers outside its parent, are closed worlds whatever the configuration declares, because no other program can import them, so this finding is reported there always. A published package is a closed world only when the configuration declares the consumer set complete and every declared consumer loads. A library with no consumer loaded and no complete consumer set declared gets this finding on its internal/ tree and its main packages and never on its published API. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first. A type is not reported when a reference from outside the package reaches its values through its own package's exported declarations: a reference to an exported function or method whose parameters or results name the type, to an exported variable or constant whose type names it, to an exported field or property whose type names it, an embedded field included, or to an exported member of the type itself, a field promoted through an embedding included. A type a reference from outside the package names, or whose values such a reference reaches, passes the reach on to each type its definition names other than through an unexported member, whether it is exported or not, so an unexported type an exported function returns passes it to the types its exported fields name. A type names another through every composite that spells it: a pointer, an array, a slice, a map key or value, a channel, a function signature and a type argument. The referencing code holds values of the type, so unexporting it would leave an exported declaration naming a type its users cannot name. A package-level declaration whose name a string literal spells carries the class grammar/analysis/confidence.md gives it.",
|
|
427
427
|
details: { narrower_visibility: "required", name_literal: "optional" },
|
|
@@ -437,7 +437,7 @@ export const KIND_ROWS = [
|
|
|
437
437
|
languages: ["go"],
|
|
438
438
|
subject: "declaration",
|
|
439
439
|
holdsLive: true,
|
|
440
|
-
rule: "An exported symbol of a non-internal package whose every reference is inside the target module, reported as a candidate for relocation behind an internal boundary. The subject is a package-level declaration or a method.
|
|
440
|
+
rule: "An exported symbol of a non-internal package whose every reference is inside the target module, reported as a candidate for relocation behind an internal boundary. The subject is a package-level declaration or a method. Four subjects are excluded. An interface method is excluded because its exportedness is the contract of the interface that declares it. A struct field is excluded because an encoder reads it by name. A method that satisfies an interface some symbol uses as a type is excluded because it cannot be unexported without its type ceasing to satisfy that interface, and so is a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, because that interface fixes them whether or not the program converts the method's type to it. A Go function a driver finds by its exported name is excluded, whatever else refers to it, because unexporting it stops the driver from running it: a test, a benchmark, a fuzz test or an example of a test file, TestMain, a test method a suite runner calls, and a go-fuzz entry point. The finding names the narrower visibility the references support. A declared cross-language edge counts as an out-of-package reference. Where the edge's other side is unknown to the analyzer, the finding is emitted pending. A reference from a Go external test package, a test file whose package clause names its directory's package with the suffix _test, is a reference from outside the package, because that test builds as another package and needs the export.",
|
|
441
441
|
precondition:
|
|
442
442
|
"Closed world only. Reported always for a main package and an internal/ directory tree, and for a published package only when the configuration declares the consumer set complete and every declared consumer loads. A library with no consumer loaded and no complete consumer set declared gets this finding on its internal/ tree and its main packages and never on its published API. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first. A type is not reported when a reference from outside the target module reaches its values through its own package's exported declarations, as DS1501 defines that reach, because the referencing code holds values of the type, and moving it behind an internal boundary would leave an exported declaration naming a type its users cannot name.",
|
|
443
443
|
details: { narrower_visibility: "required", name_literal: "optional" },
|
|
@@ -1444,6 +1444,7 @@ export const FAILURE_CLASSES = {
|
|
|
1444
1444
|
"unwritten-output": "run",
|
|
1445
1445
|
"unreportable-path": "run",
|
|
1446
1446
|
"memory-exhausted": "run",
|
|
1447
|
+
"missing-toolchain": "run",
|
|
1447
1448
|
"missing-analyzer": "run",
|
|
1448
1449
|
"unadmitted-analyzer": "run",
|
|
1449
1450
|
"unresolved-edge": "run",
|
package/src/dependencies.ts
CHANGED
|
@@ -202,7 +202,7 @@ export function packageOfSpecifier(specifier: string): string | undefined {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/** The package an installed file belongs to, read below the last dependency directory of its path. */
|
|
205
|
-
function packageOfPath(path: string): string | undefined {
|
|
205
|
+
export function packageOfPath(path: string): string | undefined {
|
|
206
206
|
const at = path.lastIndexOf(MODULES_SEGMENT);
|
|
207
207
|
return at < 0 ? undefined : packageOfSpecifier(path.slice(at + MODULES_SEGMENT.length));
|
|
208
208
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which parameter place each argument of a call binds, the one reading every question
|
|
3
|
+
* about a call's arguments asks. A plain argument fills the next place, and a spread or
|
|
4
|
+
* an `apply` list typed as a tuple whose elements are all required fills one place per
|
|
5
|
+
* element. Any other spread or list has no known length: from its first place on, each of
|
|
6
|
+
* its element types, and every later argument, may fill any place, so a value is never
|
|
7
|
+
* read as missing a parameter only because its place could not be read. The type at each
|
|
8
|
+
* place is the checker's answer for it, so this reading names places and never types them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isSpreadElement, type Expression } from "@typescript/native/ast";
|
|
12
|
+
import { ElementFlags, type Signature, type Type } from "@typescript/native/sync";
|
|
13
|
+
import { must } from "../query.ts";
|
|
14
|
+
import type { ProjectView } from "../session.ts";
|
|
15
|
+
|
|
16
|
+
/** The places one argument fills. */
|
|
17
|
+
export interface Span {
|
|
18
|
+
/** The argument's index among the invocation's arguments. */
|
|
19
|
+
readonly argument: number;
|
|
20
|
+
/** The first place it can fill. */
|
|
21
|
+
readonly first: number;
|
|
22
|
+
/** The type at each place from `first` on, or, where `open`, at any place from it on. */
|
|
23
|
+
readonly given: readonly (Type | undefined)[];
|
|
24
|
+
/** Whether the argument's length, or the length of a spread before it, is unknown. */
|
|
25
|
+
readonly open: boolean;
|
|
26
|
+
/** Whether the argument is a spread or a list, whose value is its elements. */
|
|
27
|
+
readonly spread: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One argument at one parameter place, with the type it gives there. */
|
|
31
|
+
export interface Placed {
|
|
32
|
+
readonly argument: number;
|
|
33
|
+
/** The index in the argument's span `given` of the type it gives there. */
|
|
34
|
+
readonly element: number;
|
|
35
|
+
readonly place: number;
|
|
36
|
+
readonly given: Type | undefined;
|
|
37
|
+
/** Whether the signature has no such place, so it says nothing of what the argument keeps. */
|
|
38
|
+
readonly uncovered: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Whether it stands for every place of a rest from `place` on, an argument of unknown
|
|
41
|
+
* length there taking any type the rest's element type holds.
|
|
42
|
+
*/
|
|
43
|
+
readonly rest: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* How many places a signature has. Where it `repeats`, its rest has no fixed length and
|
|
48
|
+
* `count` ends at the rest's first place: the checker answers each place past it, and an
|
|
49
|
+
* argument of unknown length meets every one of them.
|
|
50
|
+
*/
|
|
51
|
+
export interface Layout {
|
|
52
|
+
readonly count: number;
|
|
53
|
+
readonly repeats: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The element flags of a tuple type; none for another type. */
|
|
57
|
+
function elementFlags<Brand>(project: ProjectView<Brand>, type: Type): readonly ElementFlags[] {
|
|
58
|
+
const target = type.isTupleType() ? must(project.queries.targetOf(type)) : undefined;
|
|
59
|
+
return target?.isTupleTypeTarget() === true ? target.elementFlags : [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** What a spread argument puts at the places it fills, and how many it fills at least. */
|
|
63
|
+
function spread<Brand>(
|
|
64
|
+
project: ProjectView<Brand>,
|
|
65
|
+
type: Type | undefined,
|
|
66
|
+
): {
|
|
67
|
+
readonly types: readonly (Type | undefined)[];
|
|
68
|
+
readonly fixed: boolean;
|
|
69
|
+
readonly least: number;
|
|
70
|
+
} {
|
|
71
|
+
const { queries } = project;
|
|
72
|
+
if (type === undefined) {
|
|
73
|
+
return { types: [undefined], fixed: false, least: 0 };
|
|
74
|
+
}
|
|
75
|
+
if (type.isTupleType()) {
|
|
76
|
+
const flags = elementFlags(project, type);
|
|
77
|
+
return {
|
|
78
|
+
types: must(queries.typeArguments(type)).slice(0, flags.length),
|
|
79
|
+
fixed: flags.every((flag) => (flag & ElementFlags.Required) !== 0),
|
|
80
|
+
least: flags.filter((flag) => (flag & ElementFlags.Required) !== 0).length,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (must(queries.isArray(type))) {
|
|
84
|
+
return { types: must(queries.typeArguments(type)), fixed: false, least: 0 };
|
|
85
|
+
}
|
|
86
|
+
// Another iterable's elements are read off its type arguments, as `Set<T>` holds `T`.
|
|
87
|
+
return { types: [type, ...must(queries.typeArguments(type))], fixed: false, least: 0 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The element types of a tuple of `count` required elements; none for another type. */
|
|
91
|
+
export function knownElements<Brand>(
|
|
92
|
+
project: ProjectView<Brand>,
|
|
93
|
+
type: Type,
|
|
94
|
+
count: number,
|
|
95
|
+
): readonly (Type | undefined)[] | undefined {
|
|
96
|
+
if (!type.isTupleType()) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const elements = spread(project, type);
|
|
100
|
+
return elements.fixed && elements.types.length === count ? elements.types : undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The places each argument fills, `types` holding the type of each argument's value; it
|
|
105
|
+
* may be empty where no argument is a spread and no given type is read. `listed` reads
|
|
106
|
+
* the last argument as a list whose elements the places bind.
|
|
107
|
+
*/
|
|
108
|
+
export function spansOf<Brand>(
|
|
109
|
+
project: ProjectView<Brand>,
|
|
110
|
+
args: readonly Expression[],
|
|
111
|
+
listed: boolean,
|
|
112
|
+
types: readonly (Type | undefined)[],
|
|
113
|
+
): readonly Span[] {
|
|
114
|
+
const spans: Span[] = [];
|
|
115
|
+
let place = 0;
|
|
116
|
+
let open = false;
|
|
117
|
+
for (const [at, argument] of args.entries()) {
|
|
118
|
+
const type = types[at];
|
|
119
|
+
if (!isSpreadElement(argument) && !(listed && at === args.length - 1)) {
|
|
120
|
+
spans.push({ argument: at, first: place, given: [type], open, spread: false });
|
|
121
|
+
place += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const elements = spread(project, type);
|
|
125
|
+
open ||= !elements.fixed;
|
|
126
|
+
spans.push({ argument: at, first: place, given: elements.types, open, spread: true });
|
|
127
|
+
place += elements.least;
|
|
128
|
+
}
|
|
129
|
+
return spans;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The places of one signature as the checker counts them: one per parameter, and in
|
|
134
|
+
* place of a rest as many as the `length` the checker answers for its type, where that
|
|
135
|
+
* is a number literal or a union of them. Any other `length`, an array's or an array-like
|
|
136
|
+
* interface's `number` among them, leaves the rest open.
|
|
137
|
+
*/
|
|
138
|
+
export function layoutOf<Brand>(project: ProjectView<Brand>, signature: Signature): Layout {
|
|
139
|
+
const { queries } = project;
|
|
140
|
+
const fixed = signature.parameters.length;
|
|
141
|
+
const rest = must(queries.restTypeOf(signature));
|
|
142
|
+
if (rest === undefined) {
|
|
143
|
+
return { count: fixed, repeats: false };
|
|
144
|
+
}
|
|
145
|
+
const length = must(queries.propertyTypeOf(rest, "length"));
|
|
146
|
+
const lengths = length === undefined ? [] : must(queries.constituents(length));
|
|
147
|
+
const bounds = lengths.flatMap((one) => (one.isNumberLiteralType() ? [one.value] : []));
|
|
148
|
+
if (bounds.length > 0 && bounds.length === lengths.length) {
|
|
149
|
+
return { count: fixed - 1 + Math.max(...bounds), repeats: false };
|
|
150
|
+
}
|
|
151
|
+
return { count: fixed, repeats: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The parameter place each argument binds in a signature of `layout`, with the type it
|
|
156
|
+
* gives there. A span of unknown length binds every place from its first to the last the
|
|
157
|
+
* layout counts, and past them, where the rest is open, one `rest` placement for every
|
|
158
|
+
* later place, or else its first place alone. A place past every place of a layout that
|
|
159
|
+
* does not repeat is `uncovered`.
|
|
160
|
+
*/
|
|
161
|
+
export function placesIn(spans: readonly Span[], layout: Layout): readonly Placed[] {
|
|
162
|
+
const placed: Placed[] = [];
|
|
163
|
+
const add = (span: Span, place: number, rest: boolean): void => {
|
|
164
|
+
const uncovered = place >= layout.count && !layout.repeats;
|
|
165
|
+
const elements = span.open
|
|
166
|
+
? span.given.map((given, element) => ({ given, element }))
|
|
167
|
+
: [{ given: span.given[place - span.first], element: place - span.first }];
|
|
168
|
+
placed.push(
|
|
169
|
+
...elements.map((one) => ({ argument: span.argument, place, uncovered, rest, ...one })),
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
for (const span of spans) {
|
|
173
|
+
const open = span.open && layout.repeats;
|
|
174
|
+
const end = !span.open
|
|
175
|
+
? span.first + span.given.length
|
|
176
|
+
: Math.max(layout.count, open ? span.first : span.first + 1);
|
|
177
|
+
for (let place = span.first; place < end; place += 1) {
|
|
178
|
+
add(span, place, false);
|
|
179
|
+
}
|
|
180
|
+
if (open) {
|
|
181
|
+
add(span, Math.max(layout.count, span.first), true);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return placed;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Whether a span fills the parameter declared at `index`, a rest parameter every place
|
|
189
|
+
* from it on; with `element`, whether that element of a span that is not open does.
|
|
190
|
+
*/
|
|
191
|
+
export function fills(span: Span, index: number, rest: boolean, element?: number): boolean {
|
|
192
|
+
if (element !== undefined && !span.open) {
|
|
193
|
+
const place = span.first + element;
|
|
194
|
+
return rest ? place >= index : place === index;
|
|
195
|
+
}
|
|
196
|
+
const last = span.open ? Number.POSITIVE_INFINITY : span.first + span.given.length - 1;
|
|
197
|
+
return rest ? last >= index : span.first <= index && index <= last;
|
|
198
|
+
}
|