@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.3

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.
Files changed (113) hide show
  1. package/dist/src/analysis.js +95 -60
  2. package/dist/src/augmentations.js +14 -12
  3. package/dist/src/component-layer.js +1 -1
  4. package/dist/src/components.js +62 -20
  5. package/dist/src/configuration-files.js +230 -30
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +104 -13
  8. package/dist/src/convention-rows.js +10 -0
  9. package/dist/src/conventions.js +35 -1
  10. package/dist/src/dependencies.js +90 -32
  11. package/dist/src/discover.js +1 -1
  12. package/dist/src/edges.js +1 -1
  13. package/dist/src/element-access.js +341 -0
  14. package/dist/src/erased-stores.js +317 -0
  15. package/dist/src/exempt-classes.js +9 -22
  16. package/dist/src/exempt.js +3 -15
  17. package/dist/src/findings/completion.js +78 -16
  18. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  19. package/dist/src/findings/emitters.js +1 -5
  20. package/dist/src/findings/self-check.js +27 -10
  21. package/dist/src/findings-pass.js +6 -5
  22. package/dist/src/in-operands.js +30 -0
  23. package/dist/src/indexed-access.js +264 -119
  24. package/dist/src/intra-function-parts.js +25 -11
  25. package/dist/src/inventory.js +28 -12
  26. package/dist/src/keyof-comparisons.js +170 -0
  27. package/dist/src/kinds.js +17 -7
  28. package/dist/src/manifest.js +6 -16
  29. package/dist/src/matrix.js +20 -2
  30. package/dist/src/query.js +3 -0
  31. package/dist/src/references.js +14 -3
  32. package/dist/src/report.js +4 -1
  33. package/dist/src/reporters/sarif.js +4 -0
  34. package/dist/src/roots.js +0 -1
  35. package/dist/src/run.js +0 -2
  36. package/dist/src/schema.js +1 -1
  37. package/dist/src/scope.js +2 -2
  38. package/dist/src/serialization-contract.js +43 -14
  39. package/dist/src/session.js +12 -8
  40. package/dist/src/suppress-file.js +58 -6
  41. package/dist/src/suppress-inline.js +14 -5
  42. package/dist/src/suppress.js +64 -35
  43. package/dist/src/unimplemented-callees.js +312 -0
  44. package/dist/src/value-reach.js +7 -7
  45. package/dist/src/verbs/analyze.js +11 -9
  46. package/dist/src/workspace-programs.js +0 -3
  47. package/dist/src/workspace-resolution.js +0 -2
  48. package/dist/src/workspace.js +0 -3
  49. package/jsr.json +1 -1
  50. package/package.json +3 -2
  51. package/src/analysis.ts +145 -82
  52. package/src/augmentations.ts +14 -11
  53. package/src/calls.ts +3 -3
  54. package/src/component-files.ts +7 -7
  55. package/src/component-layer.ts +2 -2
  56. package/src/components.ts +104 -22
  57. package/src/config.ts +4 -4
  58. package/src/configuration-files.ts +250 -31
  59. package/src/conformance-record.ts +1 -1
  60. package/src/contextual-uses.ts +135 -17
  61. package/src/convention-rows.ts +23 -0
  62. package/src/conventions.ts +53 -1
  63. package/src/dependencies.ts +119 -38
  64. package/src/discover.ts +3 -3
  65. package/src/edges.ts +3 -3
  66. package/src/element-access.ts +446 -0
  67. package/src/entry-points.ts +1 -1
  68. package/src/erased-stores.ts +413 -0
  69. package/src/exempt-classes.ts +10 -24
  70. package/src/exempt.ts +5 -29
  71. package/src/file-facts.ts +3 -1
  72. package/src/finding.ts +3 -1
  73. package/src/findings/completion.ts +97 -21
  74. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  75. package/src/findings/emitters.ts +4 -9
  76. package/src/findings/self-check.ts +31 -14
  77. package/src/findings-pass.ts +14 -7
  78. package/src/graph.ts +2 -2
  79. package/src/html-entries.ts +1 -1
  80. package/src/in-operands.ts +45 -0
  81. package/src/indexed-access.ts +303 -137
  82. package/src/intra-function-parts.ts +41 -16
  83. package/src/inventory.ts +37 -33
  84. package/src/json-document.ts +1 -1
  85. package/src/keyof-comparisons.ts +211 -0
  86. package/src/kinds.ts +20 -7
  87. package/src/manifest.ts +7 -22
  88. package/src/matrix.ts +34 -2
  89. package/src/namespace-objects.ts +1 -1
  90. package/src/pnpm-workspace.ts +1 -1
  91. package/src/query.ts +10 -0
  92. package/src/references.ts +30 -7
  93. package/src/report.ts +11 -5
  94. package/src/reporters/sarif.ts +4 -0
  95. package/src/roots.ts +0 -3
  96. package/src/run.ts +0 -2
  97. package/src/schema.ts +2 -2
  98. package/src/scope.ts +2 -2
  99. package/src/serialization-contract.ts +53 -14
  100. package/src/session.ts +34 -31
  101. package/src/stores.ts +1 -1
  102. package/src/suppress-file.ts +68 -5
  103. package/src/suppress-inline.ts +17 -7
  104. package/src/suppress.ts +79 -47
  105. package/src/template-field.ts +1 -1
  106. package/src/unimplemented-callees.ts +411 -0
  107. package/src/value-reach.ts +12 -8
  108. package/src/verbs/analyze.ts +15 -9
  109. package/src/verbs/verb.ts +1 -4
  110. package/src/workspace-programs.ts +2 -9
  111. package/src/workspace-resolution.ts +0 -3
  112. package/src/workspace-run.ts +1 -1
  113. package/src/workspace.ts +1 -9
@@ -59,7 +59,7 @@ const SECTIONS: readonly (readonly [string, DependencySection])[] = [
59
59
  const PROJECT_OWNER = "";
60
60
 
61
61
  /** One dependency a manifest the run reads declares. */
62
- export interface DeclaredDependency {
62
+ interface DeclaredDependency {
63
63
  readonly name: string;
64
64
  readonly section: DependencySection;
65
65
  /** Where the manifest writes the dependency's key, below the target root. */
@@ -71,28 +71,37 @@ export interface DeclaredDependency {
71
71
  /** What a configuration's own reading needs: packages no deletion of a declaration removes. */
72
72
  export function configurationNeeds(packages: readonly string[]): ProjectNeeds {
73
73
  return {
74
- packages: new Set(packages),
75
74
  uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
75
+ at: new Map([[ANYWHERE, new Set(packages)]]),
76
76
  unanswered: [],
77
77
  };
78
78
  }
79
79
 
80
+ /** The key of {@link ProjectNeeds.at} for a use no file of the target governs. */
81
+ const ANYWHERE = "";
82
+
80
83
  /** What the installed copy of one declared dependency states about itself. */
81
- export interface InstalledDependency {
84
+ interface InstalledDependency {
82
85
  /** The commands its manifest's `bin` declares, by name. */
83
86
  readonly commands: readonly string[];
87
+ /** The packages its manifest's `peerDependencies` names, optional ones included. */
88
+ readonly peers: readonly string[];
84
89
  }
85
90
 
86
91
  /** What one project's own files and configuration need from outside the target. */
87
92
  export interface ProjectNeeds {
88
- /** Every package a use names or resolves to. */
89
- readonly packages: ReadonlySet<string>;
90
93
  /**
91
94
  * Per declaration of the inventory, the packages the uses written inside it name; a
92
95
  * use is held by the innermost declaration around it. The empty key holds what the
93
96
  * compiler configuration names, which no deletion removes.
94
97
  */
95
98
  readonly uses: ReadonlyMap<string, ReadonlySet<string>>;
99
+ /**
100
+ * Per absolute path of the file a use is written in, the packages it names, which a
101
+ * dependency only a manifest at or above that file declares serves. The empty key holds
102
+ * the uses no one file holds, which every manifest's dependency of that name serves.
103
+ */
104
+ readonly at: ReadonlyMap<string, ReadonlySet<string>>;
96
105
  /**
97
106
  * The files writing a module specifier the checker left unanswered, each of which may
98
107
  * use any dependency a manifest at or above it declares.
@@ -104,11 +113,14 @@ export interface ProjectNeeds {
104
113
  export interface Dependencies {
105
114
  /** Every declared dependency, manifest by manifest, in the order each writes them. */
106
115
  readonly declared: readonly DeclaredDependency[];
107
- /** Every package some project needs. */
116
+ /**
117
+ * The declared dependencies, by {@link declarationKey}, a use written at or below their
118
+ * manifest's directory needs, and each declared peer of a needed one.
119
+ */
108
120
  readonly needed: ReadonlySet<string>;
109
121
  /**
110
122
  * The declared dependencies whose command a script entry of the manifest declaring them,
111
- * or a workflow step for the target's manifest, runs, each by {@link declarationKey}.
123
+ * or a workflow step of the target, runs, each by {@link declarationKey}.
112
124
  */
113
125
  readonly ran: ReadonlySet<string>;
114
126
  /**
@@ -322,9 +334,10 @@ export function projectNeeds<Brand>(
322
334
  programPackages,
323
335
  );
324
336
  const uses = new Map<string, Set<string>>();
325
- const use = (owners: readonly string[], named: Iterable<string>): void => {
337
+ const at = new Map<string, Set<string>>();
338
+ const use = (owners: readonly string[], named: Iterable<string>, path: string): void => {
326
339
  for (const one of named) {
327
- packages.add(one);
340
+ at.set(path, (at.get(path) ?? new Set()).add(one));
328
341
  for (const owner of owners) {
329
342
  let set = uses.get(owner);
330
343
  if (set === undefined) {
@@ -335,11 +348,14 @@ export function projectNeeds<Brand>(
335
348
  }
336
349
  }
337
350
  };
338
- use([PROJECT_OWNER], packages);
351
+ use([PROJECT_OWNER], packages, project.configFile);
339
352
 
340
353
  const unanswered: string[] = [];
341
354
  for (const file of project.ownSourceFiles()) {
342
- if (fileNeeds(project, held, file, programPackages, use)) {
355
+ const inFile = (owners: readonly string[], named: Iterable<string>): void => {
356
+ use(owners, named, file.fileName);
357
+ };
358
+ if (fileNeeds(project, held, file, programPackages, inFile)) {
343
359
  unanswered.push(file.fileName);
344
360
  }
345
361
  }
@@ -363,22 +379,26 @@ export function projectNeeds<Brand>(
363
379
  for (const { file } of configuration.modules) {
364
380
  const names = declaredAt(file.fileName);
365
381
  for (const { node, text } of moduleStrings(file)) {
366
- use(ownersOf(file, held, node), dependenciesNamed(text, names));
382
+ use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
367
383
  }
368
384
  }
369
- for (const document of configuration.documents) {
370
- const names = declaredAt(document.path);
371
- for (const text of document.strings) {
372
- use([PROJECT_OWNER], dependenciesNamed(text, names));
385
+ for (const { strings, manifestDirs } of configuration.documents) {
386
+ for (const manifestDir of manifestDirs) {
387
+ const names = declaredAt(joinPath(manifestDir, "package.json"));
388
+ for (const text of strings) {
389
+ use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
390
+ }
373
391
  }
374
392
  }
375
- for (const { file, manifestDir } of configuration.outside) {
376
- const names = declaredAt(joinPath(manifestDir, "package.json"));
377
- for (const { text } of moduleStrings(file)) {
378
- use([PROJECT_OWNER], dependenciesNamed(text, names));
393
+ for (const { file, manifestDirs } of configuration.outside) {
394
+ for (const manifestDir of manifestDirs) {
395
+ const names = declaredAt(joinPath(manifestDir, "package.json"));
396
+ for (const { text } of moduleStrings(file)) {
397
+ use([PROJECT_OWNER], dependenciesNamed(text, names), joinPath(manifestDir, MANIFEST));
398
+ }
379
399
  }
380
400
  }
381
- return { packages, uses, unanswered };
401
+ return { uses, at, unanswered };
382
402
  }
383
403
 
384
404
  /** The declarations of the inventory a use written at one node belongs to. */
@@ -634,8 +654,10 @@ function installedOf(host: Host, root: string, name: string): InstalledDependenc
634
654
  const read = readJSON(host, joinPath(dir, MODULES_DIR, name, MANIFEST));
635
655
  if (read !== undefined && isRecord(read.value)) {
636
656
  const bin = read.value["bin"];
657
+ const peers = read.value["peerDependencies"];
637
658
  // A string `bin` names one command, the package's name without its scope.
638
659
  return {
660
+ peers: isRecord(peers) ? Object.keys(peers) : [],
639
661
  commands:
640
662
  typeof bin === "string"
641
663
  ? bin === ""
@@ -707,13 +729,53 @@ function isInside(path: string, dir: string): boolean {
707
729
  return dir === "" || path.startsWith(`${dir}/`);
708
730
  }
709
731
 
732
+ /** The directory below the target root a declared dependency's manifest is in, `""` being the root. */
733
+ function manifestDir(dependency: DeclaredDependency): string {
734
+ const dir = dirnamePath(dependency.position.path);
735
+ return dir === "." ? "" : dir;
736
+ }
737
+
738
+ /**
739
+ * Adds to `needed` each declared dependency that the installed manifest of a needed or
740
+ * run declared dependency names as a peer, until no further one joins: the declaration of
741
+ * that name in the needing dependency's manifest or in a manifest above it, where the
742
+ * installed peer is resolved from. `peers` holds each declaration's installed peers by
743
+ * {@link declarationKey}.
744
+ */
745
+ function addNeededPeers(
746
+ declared: readonly DeclaredDependency[],
747
+ peers: ReadonlyMap<string, readonly string[]>,
748
+ needed: Set<string>,
749
+ ran: ReadonlySet<string>,
750
+ ): void {
751
+ const pending = declared.filter(
752
+ (dependency) => needed.has(declarationKey(dependency)) || ran.has(declarationKey(dependency)),
753
+ );
754
+ for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
755
+ const below = manifestDir(next);
756
+ for (const peer of peers.get(declarationKey(next)) ?? []) {
757
+ for (const dependency of declared) {
758
+ const key = declarationKey(dependency);
759
+ if (
760
+ dependency.name === peer &&
761
+ !needed.has(key) &&
762
+ (below === manifestDir(dependency) || isInside(below, manifestDir(dependency)))
763
+ ) {
764
+ needed.add(key);
765
+ pending.push(dependency);
766
+ }
767
+ }
768
+ }
769
+ }
770
+ }
771
+
710
772
  /**
711
- * The run's dependency evidence: the target's manifest and each workspace member's in
712
- * `members` below the target root, read with the position of every dependency each
713
- * declares, what the projects and the commands its scripts and workflow steps run need,
714
- * and the last uses each deletion candidate holds. A member in `unbuilt` holds source only
715
- * in configurations the run could not build. A manifest the program cannot read declares
716
- * nothing.
773
+ * The run's dependency evidence over the target's manifest and those of `members` and
774
+ * `nested` packages below the root: each declared dependency's position, what the projects,
775
+ * scripts and workflow steps need, and each deletion candidate's last uses. A member in
776
+ * `unbuilt` holds source only in configurations the run could not build. An unreadable
777
+ * manifest declares nothing, nor does a nested package's uninstalled dependency, because
778
+ * no run requires a nested package to be installed.
717
779
  */
718
780
  export function dependenciesOf(
719
781
  host: Host,
@@ -722,12 +784,13 @@ export function dependenciesOf(
722
784
  candidates: readonly string[],
723
785
  members: readonly string[] = [],
724
786
  unbuiltMembers: readonly string[] = [],
787
+ nested: readonly string[] = [],
725
788
  ): Dependencies {
726
789
  const root = resolvePath(host.workingDirectory(), targetRoot);
727
790
  const scopeOf = packageScope(host, targetRoot);
728
791
  const manifests = new Map<string, string>([[root, MANIFEST]]);
729
792
  const unbuilt = new Set<string>();
730
- for (const dir of [...members, ...unbuiltMembers]) {
793
+ for (const dir of [...members, ...unbuiltMembers, ...nested]) {
731
794
  const below = relativePath(root, dir);
732
795
  if (below !== undefined && !manifests.has(dir)) {
733
796
  manifests.set(dir, `${below}/${MANIFEST}`);
@@ -742,6 +805,8 @@ export function dependenciesOf(
742
805
  const workflow = new Set(workflowTokens(host, targetRoot));
743
806
  const declared: DeclaredDependency[] = [];
744
807
  const ran = new Set<string>();
808
+ const peers = new Map<string, readonly string[]>();
809
+ const uninstalledHere = new Set(nested);
745
810
  for (const [dir, path] of manifests) {
746
811
  const read = readJSON(host, joinPath(dir, MANIFEST));
747
812
  if (read === undefined || !isRecord(read.value)) {
@@ -749,13 +814,16 @@ export function dependenciesOf(
749
814
  }
750
815
  const tokens = scriptTokensOf(read.value);
751
816
  for (const dependency of declaredIn(read.text, read.value, path, scopeOf(path))) {
752
- declared.push(dependency);
753
817
  const found = installedOf(host, dir, dependency.name);
818
+ if (found === undefined && uninstalledHere.has(dir)) {
819
+ continue;
820
+ }
821
+ declared.push(dependency);
754
822
  if (found === undefined) {
755
823
  continue;
756
824
  }
757
- const runs = (command: string): boolean =>
758
- tokens.has(command) || (dir === root && workflow.has(command));
825
+ peers.set(declarationKey(dependency), found.peers);
826
+ const runs = (command: string): boolean => tokens.has(command) || workflow.has(command);
759
827
  if (found.commands.some(runs)) {
760
828
  ran.add(declarationKey(dependency));
761
829
  }
@@ -765,25 +833,38 @@ export function dependenciesOf(
765
833
  // A declaration several projects compile is one user, and a use any project makes is
766
834
  // a use, so the uses are merged by declaration before anything is counted.
767
835
  const needed = new Set<string>();
836
+ const byName = new Map<string, DeclaredDependency[]>();
837
+ for (const dependency of declared) {
838
+ byName.set(dependency.name, [...(byName.get(dependency.name) ?? []), dependency]);
839
+ }
768
840
  const uses = new Map<string, Set<string>>();
769
841
  for (const project of perProject) {
770
842
  // A specifier the checker left unanswered may resolve to any dependency a manifest at
771
843
  // or above its file declares, and what the configuration needs is never deleted.
772
844
  const unanswered = project.unanswered.flatMap((file) => relativePath(root, file) ?? []);
773
- const unknown = declared
774
- .filter((dependency) => {
775
- const dir = dirnamePath(dependency.position.path);
776
- return unanswered.some((file) => isInside(file, dir === "." ? "" : dir));
777
- })
778
- .map((dependency) => dependency.name);
845
+ const unknownDeclared = declared.filter((dependency) =>
846
+ unanswered.some((file) => isInside(file, manifestDir(dependency))),
847
+ );
848
+ unknownDeclared.forEach((dependency) => needed.add(declarationKey(dependency)));
849
+ const unknown = unknownDeclared.map((dependency) => dependency.name);
779
850
  const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)] as const];
780
851
  for (const [id, used] of [...project.uses, ...held]) {
781
852
  const merged = uses.get(id) ?? new Set<string>();
782
853
  used.forEach((one) => merged.add(one));
783
854
  uses.set(id, merged);
784
855
  }
785
- [...project.packages, ...unknown].forEach((one) => needed.add(one));
856
+ for (const [path, named] of project.at) {
857
+ const below = path === ANYWHERE ? undefined : relativePath(root, path);
858
+ for (const one of named) {
859
+ for (const dependency of byName.get(one) ?? []) {
860
+ if (below === undefined || isInside(below, manifestDir(dependency))) {
861
+ needed.add(declarationKey(dependency));
862
+ }
863
+ }
864
+ }
865
+ }
786
866
  }
867
+ addNeededPeers(declared, peers, needed, ran);
787
868
  const users = new Map<string, number>();
788
869
  for (const used of uses.values()) {
789
870
  used.forEach((one) => users.set(one, (users.get(one) ?? 0) + 1));
package/src/discover.ts CHANGED
@@ -34,7 +34,7 @@ export class DiscoveryError extends Error {
34
34
  const ERROR_CATEGORY: number = DiagnosticCategory.Error;
35
35
 
36
36
  /** Whether one diagnostic is an error, which is what fails a run closed. */
37
- export function isError(diagnostic: Diagnostic): boolean {
37
+ function isError(diagnostic: Diagnostic): boolean {
38
38
  return diagnostic.category === ERROR_CATEGORY;
39
39
  }
40
40
 
@@ -113,7 +113,7 @@ export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
113
113
  }
114
114
 
115
115
  /** One project a run analyzes. */
116
- export interface DiscoveredProject {
116
+ interface DiscoveredProject {
117
117
  /**
118
118
  * The name a run gives the project: the identifier the build matrix declares for
119
119
  * it, or, for a project discovery derived, its configuration file's path below the
@@ -162,7 +162,7 @@ export function isGuess(discovery: Discovery, configFile: string): boolean {
162
162
  }
163
163
 
164
164
  /** Every project discovery found, and where each came from. */
165
- export interface Discovery {
165
+ interface Discovery {
166
166
  /** The projects, in discovery order, each once. */
167
167
  readonly projects: readonly DiscoveredProject[];
168
168
  /**
package/src/edges.ts CHANGED
@@ -39,7 +39,7 @@ const REFERENCE_WANT =
39
39
  "a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
40
40
 
41
41
  /** An edges document the Contract refuses: a malformed input, answered with the usage code. */
42
- export class EdgesError extends ConfigError {
42
+ class EdgesError extends ConfigError {
43
43
  constructor(at: string, text: string, want: string) {
44
44
  super("malformed", EDGES_FILE, `${at}: ${text}: want ${want}`);
45
45
  this.name = "EdgesError";
@@ -174,7 +174,7 @@ export function readEdgeSides(host: Host, targetRoot: string): readonly EdgeSide
174
174
  }
175
175
 
176
176
  /** One side's verdict, as an evaluation names it. */
177
- export type EdgeState = "live" | "dead" | "absent";
177
+ type EdgeState = "live" | "dead" | "absent";
178
178
 
179
179
  /** One edge evaluation: one side of this language, its state, and the finding it holds. */
180
180
  export interface EdgeEvaluation<F extends Finding = Finding> extends EdgeSide {
@@ -184,7 +184,7 @@ export interface EdgeEvaluation<F extends Finding = Finding> extends EdgeSide {
184
184
  }
185
185
 
186
186
  /** The findings a report publishes, and one evaluation per declared side of this language. */
187
- export interface Evaluated<F extends Finding> {
187
+ interface Evaluated<F extends Finding> {
188
188
  readonly findings: readonly F[];
189
189
  /** Ordered by edge, then by side, each compared bytewise. */
190
190
  readonly evaluations: readonly EdgeEvaluation<F>[];