@cplieger/deadset-ts 6.0.1 → 6.1.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.
@@ -143,8 +143,8 @@ function modulesNamed(project, specifiers) {
143
143
  return modules;
144
144
  }
145
145
  /**
146
- * The declaration each entry names in one project. A symbol entry names the
147
- * inventory's declaration whose reference it spells exactly; a module entry names the
146
+ * The declaration each entry names in one project. A symbol entry names every
147
+ * declaration of the inventory whose reference it spells exactly; a module entry names the
148
148
  * declaration its path reaches from the exports of the module its specifier resolves
149
149
  * to; a global entry names the declaration its path reaches from the global scope.
150
150
  * Every alias along a path is followed to the declaration it stands for.
@@ -153,12 +153,14 @@ export function resolveEntries(project, held, entries) {
153
153
  if (entries.length === 0) {
154
154
  return [];
155
155
  }
156
- const byRef = new Map(held.symbols.map((symbol) => [symbol.ref, symbol.id]));
156
+ const byRef = new Map();
157
+ for (const symbol of held.symbols) {
158
+ byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
159
+ }
157
160
  const modules = modulesNamed(project, new Set(entries.flatMap((entry) => (entry.shape === "module" ? [entry.module] : []))));
158
161
  return entries.map((entry) => {
159
162
  if (entry.shape === "symbol") {
160
- const id = byRef.get(entry.symbol);
161
- return { entry, handles: new Set(), ids: new Set(id === undefined ? [] : [id]) };
163
+ return { entry, handles: new Set(), ids: new Set(byRef.get(entry.symbol) ?? []) };
162
164
  }
163
165
  const { components, static: isStatic } = componentsOf(entry.shape === "module" ? entry.name : entry.global);
164
166
  const [first = "", ...rest] = components;
@@ -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: "4.0.0",
5
+ corpusVersion: "4.1.0",
6
6
  result: "pass",
7
- digest: "sha256:8250b61deb71467139bf4cbcaf10802cdabb1a2c2261a11a571fc1ff55bfc3dc",
7
+ digest: "sha256:167f770cdff2776fa7ab28b7ff6121c4d7bdb01d83c5ba0057d9303281614008",
8
8
  },
9
9
  gaps: [],
10
10
  };
@@ -31,6 +31,11 @@ const APP_DIRECTORY_ENTRIES = [
31
31
  ];
32
32
  const SOLIDSTART_PLUGIN = { module: "@solidjs/start/config", export: "solidStart" };
33
33
  const NUXT_CONFIG = [`nuxt.config.${ANY_MODULE}`];
34
+ const BABEL_CONFIG = [
35
+ "babel.config.{json,js,cjs,mjs,cts,mts,ts}",
36
+ ".babelrc",
37
+ ".babelrc.{json,js,cjs,mjs,cts}",
38
+ ];
34
39
  const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
35
40
  const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
36
41
  const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
@@ -57,6 +62,19 @@ export const CONVENTION_ROWS = [
57
62
  },
58
63
  ],
59
64
  },
65
+ {
66
+ name: "babel",
67
+ package: "@babel/core",
68
+ range: ">=7.0.0 <9.0.0",
69
+ entries: [],
70
+ moves: [],
71
+ helpers: ["@babel/plugin-transform-runtime", "@babel/transform-runtime"].map((value) => ({
72
+ property: "plugins",
73
+ files: BABEL_CONFIG,
74
+ value,
75
+ package: "@babel/runtime",
76
+ })),
77
+ },
60
78
  {
61
79
  name: "eslint",
62
80
  package: "eslint",
@@ -229,6 +247,21 @@ export const CONVENTION_ROWS = [
229
247
  },
230
248
  ],
231
249
  },
250
+ {
251
+ name: "rolldown",
252
+ package: "rolldown",
253
+ range: ">=1.0.0 <2.0.0",
254
+ entries: [],
255
+ moves: [],
256
+ helpers: [
257
+ {
258
+ property: "transform.helpers.mode",
259
+ files: [`rolldown.config.${ANY_MODULE}`],
260
+ value: "Runtime",
261
+ package: "@oxc-project/runtime",
262
+ },
263
+ ],
264
+ },
232
265
  {
233
266
  name: "solidstart",
234
267
  package: "@solidjs/start",
@@ -275,7 +308,10 @@ export const CONVENTION_ROWS = [
275
308
  range: ">=7.0.0 <11.0.0",
276
309
  entries: [STRYKER_MODULE],
277
310
  moves: [],
278
- selections: [{ property: "mutate", files: [STRYKER_CONFIG] }],
311
+ selections: [
312
+ { property: "mutate", files: [STRYKER_CONFIG] },
313
+ { property: "ignorePatterns", files: [STRYKER_CONFIG] },
314
+ ],
279
315
  shortNames: [
280
316
  {
281
317
  property: "testRunner",
@@ -406,6 +442,36 @@ export const CONVENTION_ROWS = [
406
442
  },
407
443
  ],
408
444
  },
445
+ {
446
+ name: "swc",
447
+ package: "@swc/core",
448
+ range: ">=1.0.0 <2.0.0",
449
+ entries: [],
450
+ moves: [],
451
+ helpers: [
452
+ {
453
+ property: "jsc.externalHelpers",
454
+ files: [".swcrc"],
455
+ value: true,
456
+ package: "@swc/helpers",
457
+ },
458
+ ],
459
+ },
460
+ {
461
+ name: "vite",
462
+ package: "vite",
463
+ range: ">=8.0.0 <9.0.0",
464
+ entries: [],
465
+ moves: [],
466
+ helpers: [
467
+ {
468
+ property: "oxc.helpers.mode",
469
+ files: [VITE_CONFIG],
470
+ value: "Runtime",
471
+ package: "@oxc-project/runtime",
472
+ },
473
+ ],
474
+ },
409
475
  {
410
476
  name: "vitest",
411
477
  package: "vitest",
@@ -13,6 +13,8 @@ import { globExpression } from "./glob.js";
13
13
  import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
14
14
  import { satisfies } from "./version-range.js";
15
15
  const JSON_EXTENSION = ".json";
16
+ /** A configuration file with no extension that its tool reads as a JSON document. */
17
+ const JSON_RC = /^\.[\w-]+rc$/u;
16
18
  /** The sections an installed package is required by, so its absence fails the run. */
17
19
  const REQUIRED_SECTIONS = ["dependencies", "devDependencies"];
18
20
  const DEPENDENCY_SECTIONS = [
@@ -351,11 +353,10 @@ function readPath(object, keys, values, elements) {
351
353
  return undefined;
352
354
  }
353
355
  /**
354
- * Every string literal one file writes at one property's path inside the objects it is
355
- * read from, as {@link readProperty} reads them: the value itself, or each element of an
356
- * array value, whatever else the path holds.
356
+ * Every value one file writes at one property's path inside the objects it is read from,
357
+ * as {@link readProperty} reads them, whatever else the path holds.
357
358
  */
358
- function stringsAt(file, property, call) {
359
+ function valuesAt(file, property, call) {
359
360
  const declared = declaredValues(file);
360
361
  const objects = call === undefined
361
362
  ? defaultExports(file).map((one) => objectOf(one, declared, true))
@@ -374,12 +375,9 @@ function stringsAt(file, property, call) {
374
375
  if (rest.length > 0) {
375
376
  visit(held, rest);
376
377
  }
377
- else if (literalText(held) !== undefined) {
378
+ else {
378
379
  found.push(held);
379
380
  }
380
- else if (isArrayLiteralExpression(held)) {
381
- found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
382
- }
383
381
  }
384
382
  };
385
383
  for (const object of objects) {
@@ -389,6 +387,32 @@ function stringsAt(file, property, call) {
389
387
  }
390
388
  return found;
391
389
  }
390
+ /** The string literals among values: each value itself, or each element of an array value. */
391
+ function stringsAt(file, property, call) {
392
+ return valuesAt(file, property, call).flatMap((held) => {
393
+ if (literalText(held) !== undefined) {
394
+ return [held];
395
+ }
396
+ return isArrayLiteralExpression(held)
397
+ ? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
398
+ : [];
399
+ });
400
+ }
401
+ /** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
402
+ function holdsValue(held, value) {
403
+ if (value === true) {
404
+ return held.kind === SyntaxKind.TrueKeyword;
405
+ }
406
+ if (literalText(held) === value) {
407
+ return true;
408
+ }
409
+ return (isArrayLiteralExpression(held) &&
410
+ held.elements.some((element) => {
411
+ const one = unwrapped(element);
412
+ const first = isArrayLiteralExpression(one) ? one.elements[0] : one;
413
+ return first !== undefined && literalText(first) === value;
414
+ }));
415
+ }
392
416
  /** The strings an applied row's selection keys hold, added per configuration file to `into`. */
393
417
  function selectedStrings(host, parse, dir, readings, into) {
394
418
  for (const reading of readings) {
@@ -527,7 +551,11 @@ function readingFiles(host, parse, dir, files) {
527
551
  catch {
528
552
  continue;
529
553
  }
530
- found.push({ name, text, file: parse(path, text) });
554
+ found.push({
555
+ name,
556
+ text,
557
+ file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
558
+ });
531
559
  }
532
560
  }
533
561
  return found;
@@ -563,6 +591,17 @@ function shortNameUses(host, parse, dir, manifest, readings) {
563
591
  }
564
592
  return [...used].filter((name) => declared.has(name)).sort(compare);
565
593
  }
594
+ /** The declared dependencies an applied row's helper settings use. */
595
+ function helperUses(host, parse, dir, manifest, readings) {
596
+ const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
597
+ const held = isRecord(manifest) ? manifest[section] : undefined;
598
+ return isRecord(held) ? Object.keys(held) : [];
599
+ }));
600
+ return readings
601
+ .filter((reading) => declared.has(reading.package) &&
602
+ readingFiles(host, parse, dir, reading.files).some(({ file }) => valuesAt(file, reading.property, reading.call).some((held) => holdsValue(held, reading.value))))
603
+ .map((reading) => reading.package);
604
+ }
566
605
  /** Two strings ordered bytewise. */
567
606
  function compare(a, b) {
568
607
  if (a === b) {
@@ -644,6 +683,9 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
644
683
  for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
645
684
  uses.add(name);
646
685
  }
686
+ for (const name of helperUses(host, parse, manifest.dir, declared, row.helpers ?? [])) {
687
+ uses.add(name);
688
+ }
647
689
  selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
648
690
  const excludes = [
649
691
  ...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
package/dist/src/edges.js CHANGED
@@ -154,9 +154,10 @@ function compare(a, b) {
154
154
  }
155
155
  /**
156
156
  * Evaluates every declared side of this language over the run's findings. A side naming a
157
- * symbol the run enumerates nothing under is `absent`; one naming a symbol the run holds a
158
- * finding about is `dead` and carries that finding, pending until a merge reads the other
159
- * side and reported nowhere else, a narrowing finding included; any other is `live`.
157
+ * symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
158
+ * declaration its reference spells. A declaration the run holds a finding about is `dead`
159
+ * and carries that finding, pending until a merge reads the other side and reported
160
+ * nowhere else, a narrowing finding included; any other is `live`.
160
161
  */
161
162
  export function evaluateEdges(findings, sides, symbols) {
162
163
  if (sides.length === 0) {
@@ -164,8 +165,8 @@ export function evaluateEdges(findings, sides, symbols) {
164
165
  }
165
166
  const byRef = new Map();
166
167
  for (const symbol of symbols) {
167
- if (symbol.kind !== "file" && !byRef.has(symbol.ref)) {
168
- byRef.set(symbol.ref, symbol.id);
168
+ if (symbol.kind !== "file") {
169
+ byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
169
170
  }
170
171
  }
171
172
  const held = new Map();
@@ -175,18 +176,20 @@ export function evaluateEdges(findings, sides, symbols) {
175
176
  }
176
177
  });
177
178
  const pending = new Set();
178
- const evaluations = sides.map((side) => {
179
- const id = byRef.get(side.symbol);
180
- if (id === undefined) {
181
- return { ...side, state: "absent" };
179
+ const evaluations = sides.flatMap((side) => {
180
+ const ids = byRef.get(side.symbol);
181
+ if (ids === undefined) {
182
+ return [{ ...side, state: "absent" }];
182
183
  }
183
- const at = held.get(id);
184
- const finding = at === undefined ? undefined : findings[at];
185
- if (at === undefined || finding === undefined) {
186
- return { ...side, state: "live" };
187
- }
188
- pending.add(at);
189
- return { ...side, state: "dead", finding };
184
+ return ids.map((id) => {
185
+ const at = held.get(id);
186
+ const finding = at === undefined ? undefined : findings[at];
187
+ if (at === undefined || finding === undefined) {
188
+ return { ...side, state: "live" };
189
+ }
190
+ pending.add(at);
191
+ return { ...side, state: "dead", finding };
192
+ });
190
193
  });
191
194
  evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
192
195
  return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
@@ -73,21 +73,13 @@ function dialsOver(input, found, suppressed, unmarked) {
73
73
  * family's findings completed by one step, so every component number is minted once, the
74
74
  * finding each record claims withheld, the rest withheld by the dials, so a root one family
75
75
  * reports withholds what another reports in its component, and every declared edge
76
- * evaluated over what remains, so a pending finding is in its evaluation alone. What the
77
- * minimum confidence withheld is what the same decision at the lowest minimum adds.
76
+ * evaluated over what remains, so a pending finding is in its evaluation alone.
78
77
  */
79
78
  export function decidedFindings(input, suppressed = { unmarked: input, records: [] }) {
80
79
  const found = completedOver(input);
81
80
  const unmarked = suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked, fallenMembers);
82
81
  const would = [...unmarked, ...found];
83
- const decided = dialledOver(input, suppressed, found, unmarked);
84
- const shown = input.config.analysis.minConfidence === "possible"
85
- ? decided
86
- : dialledOver(atPossible(input), {
87
- unmarked: atPossible(suppressed.unmarked),
88
- records: suppressed.records,
89
- }, found, unmarked);
90
- return { ...decided, would, withheld: withheldOf(decided.findings, shown.findings) };
82
+ return { ...dialledOver(input, suppressed, found, unmarked), would };
91
83
  }
92
84
  /** The run's input with the minimum confidence at its lowest, which withholds nothing. */
93
85
  function atPossible(input) {
@@ -107,32 +99,37 @@ function dialledOver(input, suppressed, found, unmarked) {
107
99
  const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
108
100
  const unanswered = heldKeys(input);
109
101
  const markup = markupKeys(input);
110
- const kept = reportable(input, found.filter((finding) => {
102
+ const candidates = found.filter((finding) => {
111
103
  const key = declarationKey(finding.symbol.ref, finding.position.path);
112
104
  return (!ledger.withheld(finding) &&
113
105
  !unanswered.has(key) &&
114
106
  !markup.has(key) &&
115
107
  !insideSkipped(input.swept.skipped, finding.position));
116
- }));
108
+ });
109
+ const kept = reportable(input, candidates);
117
110
  const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
118
- return { findings: evaluated.findings, evaluations: evaluated.evaluations, ledger };
111
+ return {
112
+ findings: evaluated.findings,
113
+ evaluations: evaluated.evaluations,
114
+ ledger,
115
+ withheld: withheldOf(input, candidates),
116
+ };
119
117
  }
120
- /** The findings the lowest minimum would report that the run does not, counted by confidence. */
121
- function withheldOf(reported, shown) {
122
- const kept = new Set(reported.map(findingKey));
118
+ /**
119
+ * The findings the dials withhold only because their confidence is below the minimum, each
120
+ * counted at the confidence this run gives it: the dials at the lowest minimum keep them.
121
+ */
122
+ function withheldOf(input, candidates) {
123
+ const dials = dialsOf(input, candidates);
124
+ const lowest = dialsOf(atPossible(input), candidates);
123
125
  const counts = { certain: 0, probable: 0, possible: 0 };
124
- for (const finding of shown) {
125
- if (!kept.has(findingKey(finding))) {
126
+ for (const finding of candidates) {
127
+ if (dials.withholds(finding) && !lowest.withholds(finding)) {
126
128
  counts[finding.confidence] += 1;
127
129
  }
128
130
  }
129
131
  return counts;
130
132
  }
131
- /** A finding's identity across two decisions of one run: its position, code and subject. */
132
- function findingKey(finding) {
133
- const { path, line, column } = finding.position;
134
- return JSON.stringify([path, line, column, finding.code, finding.symbol.ref]);
135
- }
136
133
  /** Every finding the run reports, each family's in table order. */
137
134
  export function findingsOf(input) {
138
135
  return decidedFindings(input).findings;
@@ -787,7 +787,12 @@ function refOf(record, chain) {
787
787
  return renderRef(record.module, { of: "module" });
788
788
  }
789
789
  if (record.kind === "export-alias") {
790
- return renderRef(record.module, { of: "alias", name: record.component.text });
790
+ const block = record.parentChain.at(-1);
791
+ return renderRef(record.module, {
792
+ of: "alias",
793
+ name: record.component.text,
794
+ ...(block === undefined ? {} : { block: block.text }),
795
+ });
791
796
  }
792
797
  return renderRef(record.module, {
793
798
  of: "declaration",
@@ -9,7 +9,7 @@ import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImp
9
9
  import { nodeKey } from "./inventory.js";
10
10
  import { renderPosition } from "./position.js";
11
11
  import { UNANSWERED } from "./query.js";
12
- import { nameComponent } from "./ref.js";
12
+ import { aliasFragment, nameComponent } from "./ref.js";
13
13
  /** Whether a node is a specifier's text: a string literal or a template with no substitution. */
14
14
  function literalText(node) {
15
15
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
@@ -78,7 +78,7 @@ function namesModule(specifier, name) {
78
78
  * The module declarations of an inventory a specifier names by name or pattern, each with
79
79
  * the members that hold the export names given.
80
80
  */
81
- export function namedBy(held) {
81
+ function namedBy(held) {
82
82
  if (held.modules.size === 0) {
83
83
  return () => [];
84
84
  }
@@ -103,7 +103,7 @@ export function namedBy(held) {
103
103
  const scope = ref.slice(0, ref.indexOf("#"));
104
104
  const wanted = new Set(names.flatMap((one) => [
105
105
  `${ref}.${nameComponent(one)}`,
106
- `${scope}#${nameComponent(one)}:alias`,
106
+ `${scope}#${aliasFragment(one, name)}`,
107
107
  ]));
108
108
  const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
109
109
  return [{ id, members: members.map((child) => child.id) }];
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * The test files no compiler configuration of a run holds. The runner executes them all
3
3
  * the same, so each module one names is a test reference: the own file a relative
4
- * specifier names, and the declarations that file exports under the names read from it,
5
- * or a module declaration the specifier names by name or pattern and its members of
6
- * those names. Such a file is read without a program, so what its body references
7
- * beyond the modules it names is not known.
4
+ * specifier names, and the declarations that file exports under the names read from it.
5
+ * No compiler run reads a module declaration for such a file, so its imports name none.
6
+ * Such a file is read without a program, so what its body references beyond the modules
7
+ * it names is not known.
8
8
  */
9
9
  import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
10
10
  import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
11
11
  import { emittedFrom } from "./emit-map.js";
12
- import { namedBy, namesImported } from "./module-declarations.js";
12
+ import { namesImported } from "./module-declarations.js";
13
13
  import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
14
14
  import { renderPosition } from "./position.js";
15
15
  import { nameComponent } from "./ref.js";
@@ -102,7 +102,6 @@ export function outsideTestReferences(tests, held, targetRoot) {
102
102
  exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
103
103
  }
104
104
  }
105
- const declared = namedBy(held);
106
105
  const found = [];
107
106
  for (const { path, file } of tests) {
108
107
  const from = `${path}#`;
@@ -112,11 +111,6 @@ export function outsideTestReferences(tests, held, targetRoot) {
112
111
  const read = (to, use) => {
113
112
  found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
114
113
  };
115
- for (const declaration of declared(named.text, named.names)) {
116
- for (const to of [declaration.id, ...declaration.members]) {
117
- read(to, "read");
118
- }
119
- }
120
114
  const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
121
115
  if (written === undefined) {
122
116
  continue;
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * The overrides of the target's class members. A call through the base class names the
3
3
  * base's member and runs the override of whichever subclass the value is, so the base
4
- * member's declaration references every override. A method overriding a member of a class
5
- * declared outside the target, at any depth of its `extends` chain, is called by outside
6
- * code holding the instance through that class, so its own class references it.
4
+ * member's declaration references every override. A method, an accessor or a property,
5
+ * static or not, overriding a member of a class declared outside the target, at any depth
6
+ * of its `extends` chain, is used by outside code holding the instance or the class
7
+ * through that class, so its own class references it.
7
8
  */
8
9
  import { isClassDeclaration, isClassExpression, isExpressionWithTypeArguments, isPropertyAccessExpression, isQualifiedName, SyntaxKind, } from "@typescript/native/unstable/ast";
9
10
  import { memberComponent } from "./class-members.js";
10
11
  import { nodeKey } from "./inventory.js";
11
12
  import { SymbolFlags } from "@typescript/native/unstable/sync";
12
13
  import { UNANSWERED } from "./query.js";
14
+ import { nameComponent } from "./ref.js";
13
15
  /** The member kinds a subclass overrides. */
14
16
  const OVERRIDABLE = new Set(["method", "class-member"]);
15
17
  /** The node naming the class one `extends` entry names. */
@@ -40,7 +42,7 @@ function extending(project, held) {
40
42
  }
41
43
  /**
42
44
  * The references each base class member makes to its overrides, for the classes of one
43
- * project that extend another class of the target, and each class makes to its methods
45
+ * project that extend another class of the target, and each class makes to its members
44
46
  * that override a member of a class declared outside the target. A member a nearer base
45
47
  * already overrides is reached through that override. A private member is not
46
48
  * overridden. Each reference is placed at the override and made by its file.
@@ -102,7 +104,7 @@ export function overrideReferences(project, held, chains, testFiles) {
102
104
  const outsideNames = outsideMemberNames(project, bases, outsideBases);
103
105
  for (const [classId, names] of outsideNames) {
104
106
  for (const [component, member] of members.get(classId) ?? []) {
105
- if (member.kind === "method" && names.has(component)) {
107
+ if (names.has(component)) {
106
108
  found.push({
107
109
  from: classId,
108
110
  to: member.id,
@@ -133,15 +135,20 @@ export function overrideReferences(project, held, chains, testFiles) {
133
135
  }
134
136
  /**
135
137
  * Per class of the target with a class declared outside the target in its `extends`
136
- * chain, the names of the members that outside class's instances carry.
138
+ * chain, the member components that outside class's instances and constructor carry, a
139
+ * static one spelled as a static member's component is.
137
140
  */
138
141
  function outsideMemberNames(project, bases, outsideBases) {
139
142
  const direct = new Map();
140
143
  for (const [classId, base] of outsideBases) {
141
- const type = project.queries.declaredTypeOf(base);
142
- const properties = type === UNANSWERED ? UNANSWERED : project.queries.propertiesOf(type);
143
- if (properties !== UNANSWERED) {
144
- direct.set(classId, new Set(properties.map((property) => property.escapedName)));
144
+ const instance = project.queries.declaredTypeOf(base);
145
+ const statics = project.queries.typeOfSymbol(base);
146
+ const components = [
147
+ ...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
148
+ ...namesOf(project, statics === UNANSWERED ? undefined : statics).map((name) => `${nameComponent(name)}:static`),
149
+ ];
150
+ if (components.length > 0) {
151
+ direct.set(classId, new Set(components));
145
152
  }
146
153
  }
147
154
  const names = new Map();
@@ -163,3 +170,8 @@ function outsideMemberNames(project, bases, outsideBases) {
163
170
  }
164
171
  return names;
165
172
  }
173
+ /** The names of a type's properties; none where the type or its properties are unanswered. */
174
+ function namesOf(project, type) {
175
+ const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
176
+ return properties === UNANSWERED ? [] : properties.map((property) => property.name);
177
+ }
package/dist/src/ref.js CHANGED
@@ -43,7 +43,7 @@ const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/package\\.json`;
43
43
  export const REF_EXPRESSIONS = [
44
44
  `^${SCOPE}#$`,
45
45
  `^${SCOPE}#${TS_HEAD}$`,
46
- `^${SCOPE}#${TS_HEAD}:alias$`,
46
+ `^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
47
47
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
48
48
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
49
49
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
@@ -66,6 +66,9 @@ export function nameComponent(name) {
66
66
  if (IDENTIFIER.test(name) || PRIVATE_NAME.test(name)) {
67
67
  return name;
68
68
  }
69
+ return quoted(name);
70
+ }
71
+ function quoted(name) {
69
72
  const escaped = name
70
73
  .replaceAll("\\", "\\\\")
71
74
  .replaceAll("'", "\\'")
@@ -82,6 +85,11 @@ export function nameComponent(name) {
82
85
  export function computedComponent(text) {
83
86
  return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
84
87
  }
88
+ /** The fragment of an export alias, inside the `declare module` block named, if one is. */
89
+ export function aliasFragment(name, block) {
90
+ const head = block === undefined ? "" : `${quoted(block)}.`;
91
+ return `${head}${nameComponent(name)}:alias`;
92
+ }
85
93
  function spell(component) {
86
94
  return component.computed ? computedComponent(component.text) : nameComponent(component.text);
87
95
  }
@@ -96,7 +104,7 @@ export function renderRef(module, fragment) {
96
104
  case "module":
97
105
  return `${scope}#`;
98
106
  case "alias":
99
- return `${scope}#${nameComponent(fragment.name)}:alias`;
107
+ return `${scope}#${aliasFragment(fragment.name, fragment.block)}`;
100
108
  case "dependency":
101
109
  return `${scope}#${fragment.name}:${fragment.section}`;
102
110
  case "declaration": {
@@ -4,4 +4,4 @@
4
4
  * independently of the analyzer's own version, which is the one the host answers
5
5
  * from the package the analyzer was installed from.
6
6
  */
7
- export const CONTRACT_VERSION = "6.0.0";
7
+ export const CONTRACT_VERSION = "6.1.0";
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
@@ -207,8 +207,8 @@ function modulesNamed<Brand>(
207
207
  }
208
208
 
209
209
  /**
210
- * The declaration each entry names in one project. A symbol entry names the
211
- * inventory's declaration whose reference it spells exactly; a module entry names the
210
+ * The declaration each entry names in one project. A symbol entry names every
211
+ * declaration of the inventory whose reference it spells exactly; a module entry names the
212
212
  * declaration its path reaches from the exports of the module its specifier resolves
213
213
  * to; a global entry names the declaration its path reaches from the global scope.
214
214
  * Every alias along a path is followed to the declaration it stands for.
@@ -221,15 +221,17 @@ export function resolveEntries<Brand>(
221
221
  if (entries.length === 0) {
222
222
  return [];
223
223
  }
224
- const byRef = new Map(held.symbols.map((symbol) => [symbol.ref, symbol.id]));
224
+ const byRef = new Map<string, string[]>();
225
+ for (const symbol of held.symbols) {
226
+ byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
227
+ }
225
228
  const modules = modulesNamed(
226
229
  project,
227
230
  new Set(entries.flatMap((entry) => (entry.shape === "module" ? [entry.module] : []))),
228
231
  );
229
232
  return entries.map((entry) => {
230
233
  if (entry.shape === "symbol") {
231
- const id = byRef.get(entry.symbol);
232
- return { entry, handles: new Set<string>(), ids: new Set(id === undefined ? [] : [id]) };
234
+ return { entry, handles: new Set<string>(), ids: new Set(byRef.get(entry.symbol) ?? []) };
233
235
  }
234
236
  const { components, static: isStatic } = componentsOf(
235
237
  entry.shape === "module" ? entry.name : entry.global,
@@ -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: "4.0.0",
5
+ corpusVersion: "4.1.0",
6
6
  result: "pass",
7
- digest: "sha256:8250b61deb71467139bf4cbcaf10802cdabb1a2c2261a11a571fc1ff55bfc3dc",
7
+ digest: "sha256:167f770cdff2776fa7ab28b7ff6121c4d7bdb01d83c5ba0057d9303281614008",
8
8
  },
9
9
  gaps: [],
10
10
  } as const;