@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.
@@ -31,6 +31,20 @@ export interface ShortNameReading extends MoveReading {
31
31
  readonly package: string;
32
32
  }
33
33
 
34
+ /**
35
+ * One configuration property whose value makes the tool's output import a runtime helper
36
+ * package, so the package is used where the property holds that value.
37
+ */
38
+ export interface HelperReading extends MoveReading {
39
+ /**
40
+ * The value: `true`, or a string the property holds, alone, as an element of an array,
41
+ * or as the first element of an array that is such an element.
42
+ */
43
+ readonly value: true | string;
44
+ /** The package the output imports its helpers from. */
45
+ readonly package: string;
46
+ }
47
+
34
48
  /** One directory a row's globs name that a configuration property moves. */
35
49
  export interface DirectoryMove {
36
50
  /** The placeholder the row's globs and later moves write as `<id>`. */
@@ -71,6 +85,8 @@ export interface ConventionRow {
71
85
  readonly selections?: readonly MoveReading[];
72
86
  /** The members of a `package.json` the tool reads its configuration from. */
73
87
  readonly manifestKeys?: readonly string[];
88
+ /** The settings that make the tool's output import a runtime helper package. */
89
+ readonly helpers?: readonly HelperReading[];
74
90
  }
75
91
 
76
92
  const SCRIPT = "{js,jsx,ts,tsx}";
@@ -105,6 +121,12 @@ const SOLIDSTART_PLUGIN = { module: "@solidjs/start/config", export: "solidStart
105
121
 
106
122
  const NUXT_CONFIG = [`nuxt.config.${ANY_MODULE}`];
107
123
 
124
+ const BABEL_CONFIG = [
125
+ "babel.config.{json,js,cjs,mjs,cts,mts,ts}",
126
+ ".babelrc",
127
+ ".babelrc.{json,js,cjs,mjs,cts}",
128
+ ];
129
+
108
130
  const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
109
131
  const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
110
132
  const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
@@ -132,6 +154,19 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
132
154
  },
133
155
  ],
134
156
  },
157
+ {
158
+ name: "babel",
159
+ package: "@babel/core",
160
+ range: ">=7.0.0 <9.0.0",
161
+ entries: [],
162
+ moves: [],
163
+ helpers: ["@babel/plugin-transform-runtime", "@babel/transform-runtime"].map((value) => ({
164
+ property: "plugins",
165
+ files: BABEL_CONFIG,
166
+ value,
167
+ package: "@babel/runtime",
168
+ })),
169
+ },
135
170
  {
136
171
  name: "eslint",
137
172
  package: "eslint",
@@ -304,6 +339,21 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
304
339
  },
305
340
  ],
306
341
  },
342
+ {
343
+ name: "rolldown",
344
+ package: "rolldown",
345
+ range: ">=1.0.0 <2.0.0",
346
+ entries: [],
347
+ moves: [],
348
+ helpers: [
349
+ {
350
+ property: "transform.helpers.mode",
351
+ files: [`rolldown.config.${ANY_MODULE}`],
352
+ value: "Runtime",
353
+ package: "@oxc-project/runtime",
354
+ },
355
+ ],
356
+ },
307
357
  {
308
358
  name: "solidstart",
309
359
  package: "@solidjs/start",
@@ -350,7 +400,10 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
350
400
  range: ">=7.0.0 <11.0.0",
351
401
  entries: [STRYKER_MODULE],
352
402
  moves: [],
353
- selections: [{ property: "mutate", files: [STRYKER_CONFIG] }],
403
+ selections: [
404
+ { property: "mutate", files: [STRYKER_CONFIG] },
405
+ { property: "ignorePatterns", files: [STRYKER_CONFIG] },
406
+ ],
354
407
  shortNames: [
355
408
  {
356
409
  property: "testRunner",
@@ -481,6 +534,36 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
481
534
  },
482
535
  ],
483
536
  },
537
+ {
538
+ name: "swc",
539
+ package: "@swc/core",
540
+ range: ">=1.0.0 <2.0.0",
541
+ entries: [],
542
+ moves: [],
543
+ helpers: [
544
+ {
545
+ property: "jsc.externalHelpers",
546
+ files: [".swcrc"],
547
+ value: true,
548
+ package: "@swc/helpers",
549
+ },
550
+ ],
551
+ },
552
+ {
553
+ name: "vite",
554
+ package: "vite",
555
+ range: ">=8.0.0 <9.0.0",
556
+ entries: [],
557
+ moves: [],
558
+ helpers: [
559
+ {
560
+ property: "oxc.helpers.mode",
561
+ files: [VITE_CONFIG],
562
+ value: "Runtime",
563
+ package: "@oxc-project/runtime",
564
+ },
565
+ ],
566
+ },
484
567
  {
485
568
  name: "vitest",
486
569
  package: "vitest",
@@ -50,6 +50,7 @@ import {
50
50
  CONVENTION_ROWS,
51
51
  type ConventionRow,
52
52
  type DirectoryMove,
53
+ type HelperReading,
53
54
  type MoveReading,
54
55
  type OptionsCall,
55
56
  type ShortNameReading,
@@ -110,7 +111,10 @@ export interface Conventions {
110
111
  readonly globs: readonly ConventionGlob[];
111
112
  readonly generated: readonly GeneratedDirectory[];
112
113
  readonly failures: readonly ConventionFailure[];
113
- /** The dependencies the applied rows' short names and manifest keys use, by name, each once. */
114
+ /**
115
+ * The dependencies the applied rows' short names, manifest keys and helper settings use,
116
+ * by name, each once.
117
+ */
114
118
  readonly uses: readonly string[];
115
119
  /** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
116
120
  readonly selected: ReadonlyMap<string, Selected>;
@@ -136,6 +140,9 @@ type ParseSource = (fileName: string, text: string) => SourceFile;
136
140
 
137
141
  const JSON_EXTENSION = ".json";
138
142
 
143
+ /** A configuration file with no extension that its tool reads as a JSON document. */
144
+ const JSON_RC = /^\.[\w-]+rc$/u;
145
+
139
146
  /** The sections an installed package is required by, so its absence fails the run. */
140
147
  const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
141
148
 
@@ -533,11 +540,10 @@ function readPath(
533
540
  }
534
541
 
535
542
  /**
536
- * Every string literal one file writes at one property's path inside the objects it is
537
- * read from, as {@link readProperty} reads them: the value itself, or each element of an
538
- * array value, whatever else the path holds.
543
+ * Every value one file writes at one property's path inside the objects it is read from,
544
+ * as {@link readProperty} reads them, whatever else the path holds.
539
545
  */
540
- function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
546
+ function valuesAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
541
547
  const declared = declaredValues(file);
542
548
  const objects =
543
549
  call === undefined
@@ -558,10 +564,8 @@ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): read
558
564
  const held = unwrapped(member.initializer);
559
565
  if (rest.length > 0) {
560
566
  visit(held, rest);
561
- } else if (literalText(held) !== undefined) {
567
+ } else {
562
568
  found.push(held);
563
- } else if (isArrayLiteralExpression(held)) {
564
- found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
565
569
  }
566
570
  }
567
571
  };
@@ -573,6 +577,36 @@ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): read
573
577
  return found;
574
578
  }
575
579
 
580
+ /** The string literals among values: each value itself, or each element of an array value. */
581
+ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
582
+ return valuesAt(file, property, call).flatMap((held) => {
583
+ if (literalText(held) !== undefined) {
584
+ return [held];
585
+ }
586
+ return isArrayLiteralExpression(held)
587
+ ? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
588
+ : [];
589
+ });
590
+ }
591
+
592
+ /** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
593
+ function holdsValue(held: Node, value: true | string): boolean {
594
+ if (value === true) {
595
+ return held.kind === SyntaxKind.TrueKeyword;
596
+ }
597
+ if (literalText(held) === value) {
598
+ return true;
599
+ }
600
+ return (
601
+ isArrayLiteralExpression(held) &&
602
+ held.elements.some((element) => {
603
+ const one = unwrapped(element);
604
+ const first = isArrayLiteralExpression(one) ? one.elements[0] : one;
605
+ return first !== undefined && literalText(first) === value;
606
+ })
607
+ );
608
+ }
609
+
576
610
  /** The strings an applied row's selection keys hold, added per configuration file to `into`. */
577
611
  function selectedStrings(
578
612
  host: Host,
@@ -760,7 +794,11 @@ function readingFiles(
760
794
  } catch {
761
795
  continue;
762
796
  }
763
- found.push({ name, text, file: parse(path, text) });
797
+ found.push({
798
+ name,
799
+ text,
800
+ file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
801
+ });
764
802
  }
765
803
  }
766
804
  return found;
@@ -808,6 +846,33 @@ function shortNameUses(
808
846
  return [...used].filter((name) => declared.has(name)).sort(compare);
809
847
  }
810
848
 
849
+ /** The declared dependencies an applied row's helper settings use. */
850
+ function helperUses(
851
+ host: Host,
852
+ parse: ParseSource,
853
+ dir: string,
854
+ manifest: unknown,
855
+ readings: readonly HelperReading[],
856
+ ): readonly string[] {
857
+ const declared = new Set(
858
+ DEPENDENCY_SECTIONS.flatMap((section) => {
859
+ const held = isRecord(manifest) ? manifest[section] : undefined;
860
+ return isRecord(held) ? Object.keys(held) : [];
861
+ }),
862
+ );
863
+ return readings
864
+ .filter(
865
+ (reading) =>
866
+ declared.has(reading.package) &&
867
+ readingFiles(host, parse, dir, reading.files).some(({ file }) =>
868
+ valuesAt(file, reading.property, reading.call).some((held) =>
869
+ holdsValue(held, reading.value),
870
+ ),
871
+ ),
872
+ )
873
+ .map((reading) => reading.package);
874
+ }
875
+
811
876
  /** Two strings ordered bytewise. */
812
877
  function compare(a: string, b: string): number {
813
878
  if (a === b) {
@@ -897,6 +962,9 @@ export function readConventions(
897
962
  for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
898
963
  uses.add(name);
899
964
  }
965
+ for (const name of helperUses(host, parse, manifest.dir, declared, row.helpers ?? [])) {
966
+ uses.add(name);
967
+ }
900
968
  selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
901
969
  const excludes = [
902
970
  ...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
package/src/edges.ts CHANGED
@@ -200,9 +200,10 @@ function compare(a: string, b: string): number {
200
200
 
201
201
  /**
202
202
  * Evaluates every declared side of this language over the run's findings. A side naming a
203
- * symbol the run enumerates nothing under is `absent`; one naming a symbol the run holds a
204
- * finding about is `dead` and carries that finding, pending until a merge reads the other
205
- * side and reported nowhere else, a narrowing finding included; any other is `live`.
203
+ * symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
204
+ * declaration its reference spells. A declaration the run holds a finding about is `dead`
205
+ * and carries that finding, pending until a merge reads the other side and reported
206
+ * nowhere else, a narrowing finding included; any other is `live`.
206
207
  */
207
208
  export function evaluateEdges<F extends Finding>(
208
209
  findings: readonly F[],
@@ -212,10 +213,10 @@ export function evaluateEdges<F extends Finding>(
212
213
  if (sides.length === 0) {
213
214
  return { findings, evaluations: [] };
214
215
  }
215
- const byRef = new Map<string, string>();
216
+ const byRef = new Map<string, string[]>();
216
217
  for (const symbol of symbols) {
217
- if (symbol.kind !== "file" && !byRef.has(symbol.ref)) {
218
- byRef.set(symbol.ref, symbol.id);
218
+ if (symbol.kind !== "file") {
219
+ byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
219
220
  }
220
221
  }
221
222
  const held = new Map<string, number>();
@@ -226,18 +227,20 @@ export function evaluateEdges<F extends Finding>(
226
227
  });
227
228
 
228
229
  const pending = new Set<number>();
229
- const evaluations = sides.map((side): EdgeEvaluation<F> => {
230
- const id = byRef.get(side.symbol);
231
- if (id === undefined) {
232
- return { ...side, state: "absent" };
230
+ const evaluations = sides.flatMap((side): EdgeEvaluation<F>[] => {
231
+ const ids = byRef.get(side.symbol);
232
+ if (ids === undefined) {
233
+ return [{ ...side, state: "absent" }];
233
234
  }
234
- const at = held.get(id);
235
- const finding = at === undefined ? undefined : findings[at];
236
- if (at === undefined || finding === undefined) {
237
- return { ...side, state: "live" };
238
- }
239
- pending.add(at);
240
- return { ...side, state: "dead", finding };
235
+ return ids.map((id) => {
236
+ const at = held.get(id);
237
+ const finding = at === undefined ? undefined : findings[at];
238
+ if (at === undefined || finding === undefined) {
239
+ return { ...side, state: "live" };
240
+ }
241
+ pending.add(at);
242
+ return { ...side, state: "dead", finding };
243
+ });
241
244
  });
242
245
  evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
243
246
  return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
@@ -127,8 +127,7 @@ function dialsOver(
127
127
  * family's findings completed by one step, so every component number is minted once, the
128
128
  * finding each record claims withheld, the rest withheld by the dials, so a root one family
129
129
  * reports withholds what another reports in its component, and every declared edge
130
- * evaluated over what remains, so a pending finding is in its evaluation alone. What the
131
- * minimum confidence withheld is what the same decision at the lowest minimum adds.
130
+ * evaluated over what remains, so a pending finding is in its evaluation alone.
132
131
  */
133
132
  export function decidedFindings(
134
133
  input: EmitterInput,
@@ -138,20 +137,7 @@ export function decidedFindings(
138
137
  const unmarked =
139
138
  suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked, fallenMembers);
140
139
  const would = [...unmarked, ...found];
141
- const decided = dialledOver(input, suppressed, found, unmarked);
142
- const shown =
143
- input.config.analysis.minConfidence === "possible"
144
- ? decided
145
- : dialledOver(
146
- atPossible(input),
147
- {
148
- unmarked: atPossible(suppressed.unmarked),
149
- records: suppressed.records,
150
- },
151
- found,
152
- unmarked,
153
- );
154
- return { ...decided, would, withheld: withheldOf(decided.findings, shown.findings) };
140
+ return { ...dialledOver(input, suppressed, found, unmarked), would };
155
141
  }
156
142
 
157
143
  /** The run's input with the minimum confidence at its lowest, which withholds nothing. */
@@ -173,48 +159,46 @@ function dialledOver(
173
159
  suppressed: Suppressed,
174
160
  found: readonly CompletedFinding[],
175
161
  unmarked: readonly CompletedFinding[],
176
- ): Omit<DecidedFindings, "would" | "withheld"> {
162
+ ): Omit<DecidedFindings, "would"> {
177
163
  const would = [...unmarked, ...found];
178
164
  const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
179
165
  const unanswered = heldKeys(input);
180
166
  const markup = markupKeys(input);
181
- const kept = reportable(
182
- input,
183
- found.filter((finding) => {
184
- const key = declarationKey(finding.symbol.ref, finding.position.path);
185
- return (
186
- !ledger.withheld(finding) &&
187
- !unanswered.has(key) &&
188
- !markup.has(key) &&
189
- !insideSkipped(input.swept.skipped, finding.position)
190
- );
191
- }),
192
- );
167
+ const candidates = found.filter((finding) => {
168
+ const key = declarationKey(finding.symbol.ref, finding.position.path);
169
+ return (
170
+ !ledger.withheld(finding) &&
171
+ !unanswered.has(key) &&
172
+ !markup.has(key) &&
173
+ !insideSkipped(input.swept.skipped, finding.position)
174
+ );
175
+ });
176
+ const kept = reportable(input, candidates);
193
177
  const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
194
- return { findings: evaluated.findings, evaluations: evaluated.evaluations, ledger };
178
+ return {
179
+ findings: evaluated.findings,
180
+ evaluations: evaluated.evaluations,
181
+ ledger,
182
+ withheld: withheldOf(input, candidates),
183
+ };
195
184
  }
196
185
 
197
- /** The findings the lowest minimum would report that the run does not, counted by confidence. */
198
- function withheldOf(
199
- reported: readonly CompletedFinding[],
200
- shown: readonly CompletedFinding[],
201
- ): Withheld {
202
- const kept = new Set(reported.map(findingKey));
186
+ /**
187
+ * The findings the dials withhold only because their confidence is below the minimum, each
188
+ * counted at the confidence this run gives it: the dials at the lowest minimum keep them.
189
+ */
190
+ function withheldOf(input: EmitterInput, candidates: readonly CompletedFinding[]): Withheld {
191
+ const dials = dialsOf(input, candidates);
192
+ const lowest = dialsOf(atPossible(input), candidates);
203
193
  const counts = { certain: 0, probable: 0, possible: 0 };
204
- for (const finding of shown) {
205
- if (!kept.has(findingKey(finding))) {
194
+ for (const finding of candidates) {
195
+ if (dials.withholds(finding) && !lowest.withholds(finding)) {
206
196
  counts[finding.confidence] += 1;
207
197
  }
208
198
  }
209
199
  return counts;
210
200
  }
211
201
 
212
- /** A finding's identity across two decisions of one run: its position, code and subject. */
213
- function findingKey(finding: CompletedFinding): string {
214
- const { path, line, column } = finding.position;
215
- return JSON.stringify([path, line, column, finding.code, finding.symbol.ref]);
216
- }
217
-
218
202
  /** Every finding the run reports, each family's in table order. */
219
203
  export function findingsOf(input: EmitterInput): readonly CompletedFinding[] {
220
204
  return decidedFindings(input).findings;
package/src/inventory.ts CHANGED
@@ -1110,7 +1110,12 @@ function refOf(record: Building, chain: readonly Component[]): string {
1110
1110
  return renderRef(record.module, { of: "module" });
1111
1111
  }
1112
1112
  if (record.kind === "export-alias") {
1113
- return renderRef(record.module, { of: "alias", name: record.component.text });
1113
+ const block = record.parentChain.at(-1);
1114
+ return renderRef(record.module, {
1115
+ of: "alias",
1116
+ name: record.component.text,
1117
+ ...(block === undefined ? {} : { block: block.text }),
1118
+ });
1114
1119
  }
1115
1120
  return renderRef(record.module, {
1116
1121
  of: "declaration",
@@ -26,7 +26,7 @@ import {
26
26
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
27
27
  import { renderPosition } from "./position.ts";
28
28
  import { UNANSWERED } from "./query.ts";
29
- import { nameComponent } from "./ref.ts";
29
+ import { aliasFragment, nameComponent } from "./ref.ts";
30
30
  import type { Reference } from "./references.ts";
31
31
  import type { ProjectView } from "./session.ts";
32
32
 
@@ -110,7 +110,7 @@ interface Named {
110
110
  * The module declarations of an inventory a specifier names by name or pattern, each with
111
111
  * the members that hold the export names given.
112
112
  */
113
- export function namedBy(
113
+ function namedBy(
114
114
  held: Inventory,
115
115
  ): (specifier: string, names: readonly string[]) => readonly Named[] {
116
116
  if (held.modules.size === 0) {
@@ -139,7 +139,7 @@ export function namedBy(
139
139
  const wanted = new Set(
140
140
  names.flatMap((one) => [
141
141
  `${ref}.${nameComponent(one)}`,
142
- `${scope}#${nameComponent(one)}:alias`,
142
+ `${scope}#${aliasFragment(one, name)}`,
143
143
  ]),
144
144
  );
145
145
  const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
@@ -1,10 +1,10 @@
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
 
10
10
  import {
@@ -23,7 +23,7 @@ import { RESOLVED_EXTENSIONS, type ParseFile } from "./configuration-files.ts";
23
23
  import { emittedFrom } from "./emit-map.ts";
24
24
  import type { Host } from "./host.ts";
25
25
  import type { Inventory } from "./inventory.ts";
26
- import { namedBy, namesImported } from "./module-declarations.ts";
26
+ import { namesImported } from "./module-declarations.ts";
27
27
  import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.ts";
28
28
  import { renderPosition } from "./position.ts";
29
29
  import { nameComponent } from "./ref.ts";
@@ -154,7 +154,6 @@ export function outsideTestReferences(
154
154
  exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
155
155
  }
156
156
  }
157
- const declared = namedBy(held);
158
157
  const found: Reference[] = [];
159
158
  for (const { path, file } of tests) {
160
159
  const from = `${path}#`;
@@ -164,11 +163,6 @@ export function outsideTestReferences(
164
163
  const read = (to: string, use: Reference["use"]): void => {
165
164
  found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
166
165
  };
167
- for (const declaration of declared(named.text, named.names)) {
168
- for (const to of [declaration.id, ...declaration.members]) {
169
- read(to, "read");
170
- }
171
- }
172
166
  const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
173
167
  if (written === undefined) {
174
168
  continue;
package/src/overrides.ts CHANGED
@@ -1,9 +1,10 @@
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
 
9
10
  import {
@@ -19,8 +20,9 @@ import {
19
20
  import type { AliasChains } from "./alias-chain.ts";
20
21
  import { memberComponent } from "./class-members.ts";
21
22
  import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
22
- import { SymbolFlags, type Symbol as TSSymbol } from "@typescript/native/unstable/sync";
23
+ import { SymbolFlags, type Symbol as TSSymbol, type Type } from "@typescript/native/unstable/sync";
23
24
  import { UNANSWERED } from "./query.ts";
25
+ import { nameComponent } from "./ref.ts";
24
26
  import type { Reference } from "./references.ts";
25
27
  import type { ProjectView } from "./session.ts";
26
28
 
@@ -60,7 +62,7 @@ function extending<Brand>(
60
62
 
61
63
  /**
62
64
  * The references each base class member makes to its overrides, for the classes of one
63
- * project that extend another class of the target, and each class makes to its methods
65
+ * project that extend another class of the target, and each class makes to its members
64
66
  * that override a member of a class declared outside the target. A member a nearer base
65
67
  * already overrides is reached through that override. A private member is not
66
68
  * overridden. Each reference is placed at the override and made by its file.
@@ -134,7 +136,7 @@ export function overrideReferences<Brand>(
134
136
  const outsideNames = outsideMemberNames(project, bases, outsideBases);
135
137
  for (const [classId, names] of outsideNames) {
136
138
  for (const [component, member] of members.get(classId) ?? []) {
137
- if (member.kind === "method" && names.has(component)) {
139
+ if (names.has(component)) {
138
140
  found.push({
139
141
  from: classId,
140
142
  to: member.id,
@@ -166,7 +168,8 @@ export function overrideReferences<Brand>(
166
168
 
167
169
  /**
168
170
  * Per class of the target with a class declared outside the target in its `extends`
169
- * chain, the names of the members that outside class's instances carry.
171
+ * chain, the member components that outside class's instances and constructor carry, a
172
+ * static one spelled as a static member's component is.
170
173
  */
171
174
  function outsideMemberNames<Brand>(
172
175
  project: ProjectView<Brand>,
@@ -175,10 +178,16 @@ function outsideMemberNames<Brand>(
175
178
  ): ReadonlyMap<string, ReadonlySet<string>> {
176
179
  const direct = new Map<string, ReadonlySet<string>>();
177
180
  for (const [classId, base] of outsideBases) {
178
- const type = project.queries.declaredTypeOf(base);
179
- const properties = type === UNANSWERED ? UNANSWERED : project.queries.propertiesOf(type);
180
- if (properties !== UNANSWERED) {
181
- direct.set(classId, new Set(properties.map((property) => property.escapedName)));
181
+ const instance = project.queries.declaredTypeOf(base);
182
+ const statics = project.queries.typeOfSymbol(base);
183
+ const components = [
184
+ ...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
185
+ ...namesOf(project, statics === UNANSWERED ? undefined : statics).map(
186
+ (name) => `${nameComponent(name)}:static`,
187
+ ),
188
+ ];
189
+ if (components.length > 0) {
190
+ direct.set(classId, new Set(components));
182
191
  }
183
192
  }
184
193
  const names = new Map<string, ReadonlySet<string>>();
@@ -200,3 +209,9 @@ function outsideMemberNames<Brand>(
200
209
  }
201
210
  return names;
202
211
  }
212
+
213
+ /** The names of a type's properties; none where the type or its properties are unanswered. */
214
+ function namesOf<Brand>(project: ProjectView<Brand>, type: Type | undefined): readonly string[] {
215
+ const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
216
+ return properties === UNANSWERED ? [] : properties.map((property) => property.name);
217
+ }
package/src/ref.ts CHANGED
@@ -47,7 +47,7 @@ const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/package\\.json`;
47
47
  export const REF_EXPRESSIONS: readonly string[] = [
48
48
  `^${SCOPE}#$`,
49
49
  `^${SCOPE}#${TS_HEAD}$`,
50
- `^${SCOPE}#${TS_HEAD}:alias$`,
50
+ `^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
51
51
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
52
52
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
53
53
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
@@ -107,8 +107,12 @@ export type Fragment =
107
107
  readonly static: boolean;
108
108
  readonly typeParameter?: string;
109
109
  }
110
- /** One export specifier, a symbol of its own beside the declaration it names. */
111
- | { readonly of: "alias"; readonly name: string }
110
+ /**
111
+ * One export specifier, a symbol of its own beside the declaration it names. One
112
+ * written inside a `declare module` block carries the block's name, which is always
113
+ * quoted, so the aliases two blocks of one file declare under one name stay apart.
114
+ */
115
+ | { readonly of: "alias"; readonly name: string; readonly block?: string }
112
116
  /** One dependency of a manifest, scoped by that manifest. */
113
117
  | { readonly of: "dependency"; readonly name: string; readonly section: DependencySection };
114
118
 
@@ -124,6 +128,10 @@ export function nameComponent(name: string): string {
124
128
  if (IDENTIFIER.test(name) || PRIVATE_NAME.test(name)) {
125
129
  return name;
126
130
  }
131
+ return quoted(name);
132
+ }
133
+
134
+ function quoted(name: string): string {
127
135
  const escaped = name
128
136
  .replaceAll("\\", "\\\\")
129
137
  .replaceAll("'", "\\'")
@@ -142,6 +150,12 @@ export function computedComponent(text: string): string {
142
150
  return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
143
151
  }
144
152
 
153
+ /** The fragment of an export alias, inside the `declare module` block named, if one is. */
154
+ export function aliasFragment(name: string, block?: string): string {
155
+ const head = block === undefined ? "" : `${quoted(block)}.`;
156
+ return `${head}${nameComponent(name)}:alias`;
157
+ }
158
+
145
159
  function spell(component: Component): string {
146
160
  return component.computed ? computedComponent(component.text) : nameComponent(component.text);
147
161
  }
@@ -157,7 +171,7 @@ export function renderRef(module: Module, fragment: Fragment): string {
157
171
  case "module":
158
172
  return `${scope}#`;
159
173
  case "alias":
160
- return `${scope}#${nameComponent(fragment.name)}:alias`;
174
+ return `${scope}#${aliasFragment(fragment.name, fragment.block)}`;
161
175
  case "dependency":
162
176
  return `${scope}#${fragment.name}:${fragment.section}`;
163
177
  case "declaration": {