@cplieger/deadset-ts 8.0.0-dev.16 → 8.0.0-dev.18

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 (161) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +82 -91
  4. package/dist/src/augmentations.js +24 -25
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +279 -43
  7. package/dist/src/commands/tools.js +5 -9
  8. package/dist/src/component-files.js +3 -2
  9. package/dist/src/conformance-record.js +1 -1
  10. package/dist/src/consumers.js +6 -9
  11. package/dist/src/contextual-uses.js +1 -17
  12. package/dist/src/contract/config.generated.js +4 -1
  13. package/dist/src/contract/contract.generated.js +2 -2
  14. package/dist/src/contract/report.generated.js +6 -2
  15. package/dist/src/dependencies.js +25 -29
  16. package/dist/src/destructuring.js +1 -19
  17. package/dist/src/element-access.js +179 -164
  18. package/dist/src/entries/build-scripts.js +2 -2
  19. package/dist/src/entries/configuration-files.js +3 -7
  20. package/dist/src/entries/conventions.js +2 -2
  21. package/dist/src/entries/html-entries.js +7 -10
  22. package/dist/src/entries/mdx-entries.js +2 -2
  23. package/dist/src/entries/static-module.js +2 -1
  24. package/dist/src/exempt.js +0 -26
  25. package/dist/src/exposure.js +400 -0
  26. package/dist/src/facts.js +4 -4
  27. package/dist/src/findings/completion.js +44 -21
  28. package/dist/src/findings/emitters.js +22 -33
  29. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  30. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  31. package/dist/src/findings/intra-function.js +3 -3
  32. package/dist/src/findings/reads-and-writes.js +71 -15
  33. package/dist/src/findings/unused-declarations.js +2 -22
  34. package/dist/src/findings/visibility-narrowing.js +14 -20
  35. package/dist/src/framework-lifecycle.js +41 -14
  36. package/dist/src/import-bindings.js +73 -28
  37. package/dist/src/index/sites.js +557 -0
  38. package/dist/src/indexed-access.js +523 -478
  39. package/dist/src/interface-satisfaction.js +1 -1
  40. package/dist/src/inventory.js +18 -0
  41. package/dist/src/keyof-comparisons.js +124 -103
  42. package/dist/src/{components.js → liveness/components.js} +1 -1
  43. package/dist/src/{graph.js → liveness/graph.js} +45 -20
  44. package/dist/src/liveness/matrix.js +99 -0
  45. package/dist/src/liveness/sweep.js +558 -0
  46. package/dist/src/load/expected-module.js +2 -2
  47. package/dist/src/load/specifiers.js +2 -4
  48. package/dist/src/load/synthesize.js +5 -0
  49. package/dist/src/load/workspace-resolution.js +2 -2
  50. package/dist/src/module-declarations.js +70 -85
  51. package/dist/src/overrides.js +134 -112
  52. package/dist/src/paths.js +46 -7
  53. package/dist/src/platform-calls.js +2 -2
  54. package/dist/src/platform-symbols.js +13 -19
  55. package/dist/src/query.js +46 -0
  56. package/dist/src/references/read-rule.js +1 -0
  57. package/dist/src/references/read-rules.js +48 -0
  58. package/dist/src/references/sink.js +237 -0
  59. package/dist/src/references/sources.js +450 -0
  60. package/dist/src/references.js +110 -1285
  61. package/dist/src/roots/configured-declarations.js +3 -0
  62. package/dist/src/roots/published-reach.js +4 -4
  63. package/dist/src/roots/root-set.js +1 -1
  64. package/dist/src/roots/unseen-loads.js +38 -10
  65. package/dist/src/serialization-contract.js +8 -3
  66. package/dist/src/stores.js +4 -2
  67. package/dist/src/symbol-keys.js +89 -83
  68. package/dist/src/tests/file-class.js +76 -0
  69. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  70. package/dist/src/{test-support.js → tests/support.js} +2 -11
  71. package/dist/src/type-parameter-reads.js +178 -139
  72. package/dist/src/unimplemented-callees.js +262 -242
  73. package/dist/src/verbs/analyze.js +3 -3
  74. package/dist/src/verbs/explain.js +75 -132
  75. package/dist/src/verbs/print-retained.js +3 -3
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/alias-chain.ts +0 -7
  79. package/src/amd.ts +130 -0
  80. package/src/analysis.ts +125 -128
  81. package/src/augmentations.ts +42 -28
  82. package/src/bindings.ts +460 -0
  83. package/src/calls.ts +338 -46
  84. package/src/commands/tools.ts +5 -11
  85. package/src/component-files.ts +4 -2
  86. package/src/conformance-record.ts +1 -1
  87. package/src/consumers.ts +8 -11
  88. package/src/contextual-uses.ts +2 -22
  89. package/src/contract/config.generated.ts +4 -1
  90. package/src/contract/contract.generated.ts +2 -2
  91. package/src/contract/report.generated.ts +6 -2
  92. package/src/dependencies.ts +27 -28
  93. package/src/destructuring.ts +1 -31
  94. package/src/element-access.ts +188 -182
  95. package/src/entries/build-scripts.ts +2 -2
  96. package/src/entries/configuration-files.ts +11 -8
  97. package/src/entries/conventions.ts +2 -1
  98. package/src/entries/html-entries.ts +15 -14
  99. package/src/entries/mdx-entries.ts +2 -2
  100. package/src/entries/static-module.ts +2 -1
  101. package/src/exempt.ts +31 -46
  102. package/src/exposure.ts +492 -0
  103. package/src/facts.ts +5 -3
  104. package/src/finding.ts +1 -1
  105. package/src/findings/boundary.ts +9 -0
  106. package/src/findings/completion.ts +48 -23
  107. package/src/findings/emitter.ts +1 -1
  108. package/src/findings/emitters.ts +23 -41
  109. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  110. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  111. package/src/findings/interfaces.ts +3 -3
  112. package/src/findings/intra-function.ts +3 -3
  113. package/src/findings/reads-and-writes.ts +99 -26
  114. package/src/findings/unused-declarations.ts +4 -28
  115. package/src/findings/visibility-narrowing.ts +25 -26
  116. package/src/framework-lifecycle.ts +42 -14
  117. package/src/import-bindings.ts +98 -29
  118. package/src/index/sites.ts +781 -0
  119. package/src/indexed-access.ts +570 -527
  120. package/src/interface-satisfaction.ts +2 -2
  121. package/src/inventory.ts +19 -0
  122. package/src/keyof-comparisons.ts +131 -116
  123. package/src/{components.ts → liveness/components.ts} +3 -3
  124. package/src/{graph.ts → liveness/graph.ts} +93 -27
  125. package/src/liveness/matrix.ts +162 -0
  126. package/src/liveness/sweep.ts +855 -0
  127. package/src/load/expected-module.ts +8 -2
  128. package/src/load/specifiers.ts +8 -5
  129. package/src/load/synthesize.ts +6 -0
  130. package/src/load/workspace-resolution.ts +9 -2
  131. package/src/module-declarations.ts +74 -98
  132. package/src/overrides.ts +162 -134
  133. package/src/paths.ts +54 -8
  134. package/src/platform-calls.ts +3 -3
  135. package/src/platform-symbols.ts +11 -27
  136. package/src/query.ts +81 -0
  137. package/src/references/read-rule.ts +60 -0
  138. package/src/references/read-rules.ts +55 -0
  139. package/src/references/sink.ts +314 -0
  140. package/src/references/sources.ts +632 -0
  141. package/src/references.ts +175 -1733
  142. package/src/report.ts +1 -1
  143. package/src/roots/configured-declarations.ts +3 -0
  144. package/src/roots/published-reach.ts +9 -4
  145. package/src/roots/root-set.ts +1 -1
  146. package/src/roots/unseen-loads.ts +49 -11
  147. package/src/serialization-contract.ts +8 -3
  148. package/src/stores.ts +15 -3
  149. package/src/symbol-keys.ts +102 -102
  150. package/src/tests/file-class.ts +135 -0
  151. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  152. package/src/{test-support.ts → tests/support.ts} +9 -20
  153. package/src/type-parameter-reads.ts +187 -158
  154. package/src/unimplemented-callees.ts +297 -280
  155. package/src/verbs/analyze.ts +3 -2
  156. package/src/verbs/explain.ts +83 -158
  157. package/src/verbs/print-retained.ts +3 -3
  158. package/dist/src/matrix.js +0 -273
  159. package/dist/src/sweep.js +0 -222
  160. package/src/matrix.ts +0 -463
  161. package/src/sweep.ts +0 -361
@@ -41,12 +41,8 @@ import {
41
41
  type SourceFile,
42
42
  } from "@typescript/native/ast";
43
43
  import type { Type } from "@typescript/native/sync";
44
- import type { AliasChains } from "./alias-chain.ts";
45
- import { nodeKey, type Inventory } from "./inventory.ts";
46
- import { renderPosition } from "./position.ts";
47
44
  import { UNANSWERED } from "./query.ts";
48
- import type { Reference } from "./references.ts";
49
- import type { ProjectView } from "./session.ts";
45
+ import type { Read, ReadRule } from "./references/read-rule.ts";
50
46
  import { parenthesesRemoved } from "./unwrap.ts";
51
47
 
52
48
  type Use = "read" | "write" | "both";
@@ -242,190 +238,200 @@ interface Candidate {
242
238
  }
243
239
 
244
240
  /**
245
- * The references the element accesses of one project's own files make to the members their
246
- * index's literal type names, each made by the declaration holding the access or the call.
241
+ * The reads the element accesses of a side's files make of the members their index's literal
242
+ * type names, each made by the declaration holding the access or the call. A type the checker
243
+ * leaves unanswered holds what its answer could have read.
247
244
  */
248
- export function elementAccessReferences<Brand>(
249
- project: ProjectView<Brand>,
250
- held: Inventory,
251
- chains: AliasChains,
252
- root: string,
253
- testFiles: ReadonlySet<string>,
254
- ): readonly Reference[] {
255
- const queries = project.queries;
256
- const candidates: Candidate[] = [];
257
- const calls = new Map<Node, { readonly file: SourceFile; readonly from: string }>();
258
- const names: Identifier[] = [];
259
-
260
- for (const file of project.ownSourceFiles()) {
261
- const fileId = held.declarations.get(nodeKey(file, file));
262
- if (fileId === undefined) {
263
- continue;
264
- }
265
- const visit = (node: Node, from: string): void => {
266
- const holder = held.declarations.get(nodeKey(file, node)) ?? from;
267
- if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
268
- candidates.push({ file, from: holder, access: node, use: useOf(node) });
269
- }
270
- if (isCallExpression(node)) {
271
- calls.set(node, { file, from: holder });
272
- }
273
- if (isIdentifier(node)) {
274
- names.push(node);
275
- }
276
- node.forEachChild((child) => {
277
- visit(child, holder);
278
- });
279
- };
280
- file.forEachChild((child) => {
281
- visit(child, fileId);
282
- });
283
- }
284
- if (candidates.length === 0) {
285
- return [];
286
- }
287
-
288
- // A parameter index is decided at the calls only where the function is a callable every
289
- // use of which is a call, so the symbols of the parameters and of every name are read.
290
- const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
291
- const byCallable = new Map<Identifier, Candidate[]>();
292
- for (const candidate of candidates) {
293
- const index = parenthesesRemoved(candidate.access.argumentExpression);
294
- const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
295
- const callable = fn === undefined ? undefined : callableOf(fn);
296
- if (callable === undefined || !isIdentifier(index)) {
297
- continue;
298
- }
299
- const at = callable.parameters.findIndex(
300
- (parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text,
301
- );
302
- if (at < 0 || assigns(callable.body, index.text)) {
303
- continue;
304
- }
305
- parameterOf.set(candidate, { callable, at });
306
- byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
307
- }
308
- const callableNames = [...byCallable.keys()];
309
- const spelled = new Set(callableNames.map((name) => name.text));
310
- const sameName = names.filter((name) => spelled.has(name.text));
311
- const symbols = queries.symbolsAt([...callableNames, ...sameName]);
312
- const symbolOf = new Map<Node, unknown>();
313
- [...callableNames, ...sameName].forEach((node, at) => {
314
- const symbol = symbols[at];
315
- if (symbol !== UNANSWERED && symbol !== undefined) {
316
- symbolOf.set(node, symbol);
317
- }
318
- });
319
- const callsOf = new Map<Node, CallExpression[] | undefined>();
320
- for (const name of callableNames) {
321
- const symbol = symbolOf.get(name);
322
- let found: CallExpression[] | undefined = symbol === undefined ? undefined : [];
323
- for (const use of sameName) {
324
- if (found === undefined) {
325
- break;
326
- }
327
- if (use === name || symbolOf.get(use) !== symbol) {
328
- continue;
329
- }
330
- const parent = use.parent;
331
- found =
332
- isCallExpression(parent) && parent.expression === use ? [...found, parent] : undefined;
333
- }
334
- callsOf.set(name, found);
335
- }
336
-
337
- const lookups: Lookup[] = [];
338
- for (const candidate of candidates) {
339
- const deferred = parameterOf.get(candidate);
340
- const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
341
- if (deferred === undefined || sites === undefined) {
342
- lookups.push({
343
- file: candidate.file,
344
- from: candidate.from,
345
- at: candidate.access.argumentExpression,
346
- object: candidate.access.expression,
347
- index: candidate.access.argumentExpression,
348
- use: candidate.use,
349
- });
350
- continue;
351
- }
352
- // An object that is a parameter too is read as the argument each call passes there.
353
- const object = parenthesesRemoved(candidate.access.expression);
354
- const objectAt = isIdentifier(object)
355
- ? deferred.callable.parameters.findIndex(
356
- (parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
357
- )
358
- : -1;
359
- for (const call of sites) {
360
- const parameter = deferred.callable.parameters[deferred.at];
361
- const index = call.arguments[deferred.at] ?? parameter?.initializer;
362
- const site = calls.get(call);
363
- if (index === undefined || site === undefined) {
364
- continue;
365
- }
366
- lookups.push({
367
- file: site.file,
368
- from: site.from,
369
- at: call,
370
- object:
371
- (objectAt < 0 ? undefined : call.arguments[objectAt]) ?? candidate.access.expression,
372
- index,
373
- use: candidate.use,
374
- });
375
- }
376
- }
377
-
378
- const nodes = lookups.flatMap((one) => [one.index, one.object]);
379
- const types = queries.typesAt(nodes);
380
- const typeOf = (node: Node, at: number): Type | undefined => {
381
- const type = types[at];
382
- return nodes[at] === node && type !== UNANSWERED ? type : undefined;
383
- };
384
- // A type parameter's body serves every instantiation, so it reads what its constraint admits.
385
- const keysOf = (type: Type): Type => {
386
- if (!type.isTypeParameter()) {
387
- return type;
388
- }
389
- const constraint = queries.constraintOf(type);
390
- return constraint === UNANSWERED || constraint === undefined ? type : constraint;
391
- };
392
- const partsOf = (type: Type): readonly Type[] => {
393
- const parts = queries.constituents(type);
394
- return parts === UNANSWERED ? [type] : parts;
395
- };
245
+ export const elementAccess: ReadRule = {
246
+ start() {
247
+ const candidates: Candidate[] = [];
248
+ const calls = new Map<Node, { readonly file: SourceFile; readonly from: string }>();
249
+ const names: Identifier[] = [];
250
+ return {
251
+ visit(node, { file, from }) {
252
+ if (isElementAccessExpression(node) && !isLiteralIndex(node.argumentExpression)) {
253
+ candidates.push({ file, from, access: node, use: useOf(node) });
254
+ }
255
+ if (isCallExpression(node)) {
256
+ calls.set(node, { file, from });
257
+ }
258
+ if (isIdentifier(node)) {
259
+ names.push(node);
260
+ }
261
+ },
262
+ reads({ project, chains }) {
263
+ const found: Read[] = [];
264
+ if (candidates.length === 0) {
265
+ return found;
266
+ }
267
+ const queries = project.queries;
268
+ // A parameter index is decided at the calls only where the function is a callable every
269
+ // use of which is a call, so the symbols of the parameters and of every name are read.
270
+ const parameterOf = new Map<Candidate, { callable: Callable; at: number }>();
271
+ const byCallable = new Map<Identifier, Candidate[]>();
272
+ for (const candidate of candidates) {
273
+ const index = parenthesesRemoved(candidate.access.argumentExpression);
274
+ const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
275
+ const callable = fn === undefined ? undefined : callableOf(fn);
276
+ if (callable === undefined || !isIdentifier(index)) {
277
+ continue;
278
+ }
279
+ const at = callable.parameters.findIndex(
280
+ (parameter) => isIdentifier(parameter.name) && parameter.name.text === index.text,
281
+ );
282
+ if (at < 0 || assigns(callable.body, index.text)) {
283
+ continue;
284
+ }
285
+ parameterOf.set(candidate, { callable, at });
286
+ byCallable.set(callable.name, [...(byCallable.get(callable.name) ?? []), candidate]);
287
+ }
288
+ const callableNames = [...byCallable.keys()];
289
+ const spelled = new Set(callableNames.map((name) => name.text));
290
+ const sameName = names.filter((name) => spelled.has(name.text));
291
+ const asked = [...callableNames, ...sameName];
292
+ const symbols = queries.symbolsAt(asked);
293
+ const symbolOf = new Map<Node, unknown>();
294
+ asked.forEach((node, at) => {
295
+ const symbol = symbols[at];
296
+ if (symbol !== undefined) {
297
+ symbolOf.set(node, symbol);
298
+ }
299
+ });
300
+ const callsOf = new Map<Node, CallExpression[] | undefined>();
301
+ for (const name of callableNames) {
302
+ const symbol = symbolOf.get(name);
303
+ let sites: CallExpression[] | undefined =
304
+ symbol === undefined || symbol === UNANSWERED ? undefined : [];
305
+ for (const use of sameName) {
306
+ if (sites === undefined) {
307
+ break;
308
+ }
309
+ const other = symbolOf.get(use);
310
+ if (use === name || (other !== symbol && other !== UNANSWERED)) {
311
+ continue;
312
+ }
313
+ // A use the checker could not resolve may be the function's, read where it is not called.
314
+ const parent = use.parent;
315
+ sites =
316
+ other !== UNANSWERED && isCallExpression(parent) && parent.expression === use
317
+ ? [...sites, parent]
318
+ : undefined;
319
+ }
320
+ callsOf.set(name, sites);
321
+ }
396
322
 
397
- const found: Reference[] = [];
398
- lookups.forEach((lookup, at) => {
399
- const indexType = typeOf(lookup.index, 2 * at);
400
- const objectType = typeOf(lookup.object, 2 * at + 1);
401
- const literals = indexType === undefined ? undefined : literalNames(partsOf(keysOf(indexType)));
402
- if (objectType === undefined || literals === undefined) {
403
- return;
404
- }
405
- const position = renderPosition(lookup.file, root, lookup.at.getStart());
406
- for (const part of partsOf(objectType)) {
407
- const properties = queries.propertiesOf(part);
408
- for (const property of properties === UNANSWERED ? [] : properties) {
409
- if (!literals.has(property.escapedName)) {
410
- continue;
323
+ const lookups: Lookup[] = [];
324
+ for (const candidate of candidates) {
325
+ const deferred = parameterOf.get(candidate);
326
+ const sites = deferred === undefined ? undefined : callsOf.get(deferred.callable.name);
327
+ if (deferred === undefined || sites === undefined) {
328
+ lookups.push({
329
+ file: candidate.file,
330
+ from: candidate.from,
331
+ at: candidate.access.argumentExpression,
332
+ object: candidate.access.expression,
333
+ index: candidate.access.argumentExpression,
334
+ use: candidate.use,
335
+ });
336
+ continue;
337
+ }
338
+ // An object that is a parameter too is read as the argument each call passes there.
339
+ const object = parenthesesRemoved(candidate.access.expression);
340
+ const objectAt = isIdentifier(object)
341
+ ? deferred.callable.parameters.findIndex(
342
+ (parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text,
343
+ )
344
+ : -1;
345
+ for (const call of sites) {
346
+ const parameter = deferred.callable.parameters[deferred.at];
347
+ const index = call.arguments[deferred.at] ?? parameter?.initializer;
348
+ const site = calls.get(call);
349
+ if (index === undefined || site === undefined) {
350
+ continue;
351
+ }
352
+ lookups.push({
353
+ file: site.file,
354
+ from: site.from,
355
+ at: call,
356
+ object:
357
+ (objectAt < 0 ? undefined : call.arguments[objectAt]) ??
358
+ candidate.access.expression,
359
+ index,
360
+ use: candidate.use,
361
+ });
362
+ }
411
363
  }
412
- for (const to of chains.declarationsOf(property)) {
413
- for (const use of lookup.use === "both" ? (["read", "write"] as const) : [lookup.use]) {
364
+
365
+ const nodes = lookups.flatMap((one) => [one.index, one.object]);
366
+ const types = queries.typesAt(nodes);
367
+ // A type parameter's body serves every instantiation, so it reads what its constraint admits.
368
+ const keysOf = (type: Type): Type => {
369
+ if (!type.isTypeParameter()) {
370
+ return type;
371
+ }
372
+ const constraint = queries.constraintOf(type);
373
+ return constraint === UNANSWERED || constraint === undefined ? type : constraint;
374
+ };
375
+ const partsOf = (type: Type): readonly Type[] => {
376
+ const parts = queries.constituents(type);
377
+ return parts === UNANSWERED ? [type] : parts;
378
+ };
379
+ lookups.forEach((lookup, at) => {
380
+ const indexType = types[2 * at];
381
+ const objectType = types[2 * at + 1];
382
+ const literals =
383
+ indexType === undefined || indexType === UNANSWERED
384
+ ? undefined
385
+ : literalNames(partsOf(keysOf(indexType)));
386
+ if (indexType === UNANSWERED || objectType === UNANSWERED) {
414
387
  found.push({
388
+ kind: "held",
415
389
  from: lookup.from,
416
- to,
417
- position,
418
- use,
419
- resolution: "keyed",
420
- test: testFiles.has(position.path),
390
+ ...(literals === undefined ? {} : { names: [...literals] }),
391
+ ...(objectType === UNANSWERED || objectType === undefined
392
+ ? literals === undefined
393
+ ? { members: true as const }
394
+ : {}
395
+ : { types: partsOf(objectType) }),
421
396
  });
397
+ return;
422
398
  }
423
- }
424
- }
425
- }
426
- });
427
- return found;
428
- }
399
+ if (objectType === undefined || literals === undefined) {
400
+ return;
401
+ }
402
+ for (const part of partsOf(objectType)) {
403
+ const properties = queries.propertiesOf(part);
404
+ if (properties === UNANSWERED) {
405
+ found.push({ kind: "held", from: lookup.from, names: [...literals] });
406
+ continue;
407
+ }
408
+ for (const property of properties) {
409
+ if (!literals.has(property.escapedName)) {
410
+ continue;
411
+ }
412
+ for (const to of chains.declarationsOf(property)) {
413
+ for (const use of lookup.use === "both"
414
+ ? (["read", "write"] as const)
415
+ : [lookup.use]) {
416
+ found.push({
417
+ kind: "named",
418
+ from: lookup.from,
419
+ to,
420
+ file: lookup.file,
421
+ at: lookup.at,
422
+ use,
423
+ via: "keyed",
424
+ });
425
+ }
426
+ }
427
+ }
428
+ }
429
+ });
430
+ return found;
431
+ },
432
+ };
433
+ },
434
+ };
429
435
 
430
436
  /** The names a union of literal types spells, or none where a constituent is no string or number literal. */
431
437
  function literalNames(parts: readonly Type[]): ReadonlySet<string> | undefined {
@@ -24,7 +24,7 @@ import {
24
24
  import { declarationOf, membersNamed, staticModule, type Value } from "./static-module.ts";
25
25
  import { filesNamedBy, type ParseFile } from "./configuration-files.ts";
26
26
  import type { Host } from "../host.ts";
27
- import { ancestors, dirnamePath, relativePath, resolvePath } from "../paths.ts";
27
+ import { ancestors, dirnamePath, isExplicitPath, relativePath, resolvePath } from "../paths.ts";
28
28
  import type { SourceFiles } from "../source-files.ts";
29
29
  import { bytewise } from "../text/compare.ts";
30
30
  import { unwrapped } from "../unwrap.ts";
@@ -93,7 +93,7 @@ function lastMember(object: Value, key: string): Value | undefined {
93
93
  function pathOf(value: Value, dir: string, scriptDir: string): EntryPath | undefined {
94
94
  if (value.kind === "text") {
95
95
  const { text } = value;
96
- const request = !text.startsWith("./") && !text.startsWith("../") && !text.startsWith("/");
96
+ const request = !isExplicitPath(text);
97
97
  return { path: resolvePath(dir, text), text, request };
98
98
  }
99
99
  if (value.kind === "directory") {
@@ -15,7 +15,15 @@ import {
15
15
  } from "@typescript/native/ast";
16
16
  import { relativeSpecifiers } from "./static-module.ts";
17
17
  import type { Host } from "../host.ts";
18
- import { ancestors, dirnamePath, joinPath, relativePath, resolvePath } from "../paths.ts";
18
+ import {
19
+ ancestors,
20
+ dirnamePath,
21
+ isAbsolutePath,
22
+ joinPath,
23
+ relativePath,
24
+ resolvePath,
25
+ startsRelativePath,
26
+ } from "../paths.ts";
19
27
  import type { SourceFiles } from "../source-files.ts";
20
28
  import { bytewise } from "../text/compare.ts";
21
29
  import { isRecord } from "../json-document.ts";
@@ -50,9 +58,6 @@ const MODULE_FORMS: readonly { readonly name: RegExp; readonly form: string }[]
50
58
  /** A JSON configuration file's name. */
51
59
  const DOCUMENT_NAME = /^(?:[\w-]+(?:\.[\w-]+)?\.config\.json|\.[\w-]+rc(?:\.json)?)$/u;
52
60
 
53
- /** The prefixes a string names a file with, relative to the file that writes it. */
54
- const RELATIVE_PREFIXES = ["./", "../"] as const;
55
-
56
61
  /** The JavaScript extensions a relative specifier from a configuration file stands for TypeScript under. */
57
62
  const COUNTERPARTS: readonly (readonly [string, readonly string[]])[] = [
58
63
  [".js", [".ts", ".tsx"]],
@@ -270,9 +275,7 @@ export function configurationFiles(
270
275
  }
271
276
  document.manifestDirs.push(next.manifestDir);
272
277
  for (const text of document.strings) {
273
- const path = RELATIVE_PREFIXES.some((prefix) => text.startsWith(prefix))
274
- ? resolvePath(dirnamePath(next.path), text)
275
- : undefined;
278
+ const path = startsRelativePath(text) ? resolvePath(dirnamePath(next.path), text) : undefined;
276
279
  if (
277
280
  path?.endsWith(".json") === true &&
278
281
  isProjectPath(root, path) &&
@@ -425,7 +428,7 @@ export function dependenciesNamed(text: string, declared: readonly string[]): re
425
428
  * none.
426
429
  */
427
430
  export function filesNamedBy(files: SourceFiles, dir: string, text: string): readonly SourceFile[] {
428
- if (text === "" || text.startsWith("/") || /[*?[{]/u.test(text)) {
431
+ if (text === "" || isAbsolutePath(text) || /[*?[{]/u.test(text)) {
429
432
  return [];
430
433
  }
431
434
  const path = resolvePath(dir, text);
@@ -42,6 +42,7 @@ import {
42
42
  import type { Host } from "../host.ts";
43
43
  import {
44
44
  ancestors,
45
+ cannotStartPackageName,
45
46
  dirnamePath,
46
47
  joinPath,
47
48
  normalizePath,
@@ -683,7 +684,7 @@ function prefixedPackage(value: string, reading: PrefixedNameReading): string |
683
684
  const { prefix, marker } = reading;
684
685
  let name: string;
685
686
  if (marker === undefined) {
686
- if (value.includes(":") || value.startsWith(".") || value.startsWith("/")) {
687
+ if (value.includes(":") || cannotStartPackageName(value)) {
687
688
  return undefined;
688
689
  }
689
690
  const parts = value.split("/");
@@ -12,7 +12,15 @@ import {
12
12
  } from "@typescript/native/ast";
13
13
  import { resolveRelativeSpecifier, type ParseFile } from "./configuration-files.ts";
14
14
  import type { Host } from "../host.ts";
15
- import { ancestors, dirnamePath, joinPath, resolvePath } from "../paths.ts";
15
+ import {
16
+ ancestors,
17
+ dirnamePath,
18
+ hasURLScheme,
19
+ isAbsolutePath,
20
+ isExplicitPath,
21
+ joinPath,
22
+ resolvePath,
23
+ } from "../paths.ts";
16
24
  import type { SourceFiles } from "../source-files.ts";
17
25
  import { filesBelow } from "../fs/tree.ts";
18
26
  import { holdsManifest } from "../fs/manifest.ts";
@@ -32,15 +40,9 @@ const SCRIPT_END = /<\/script\s*>/iu;
32
40
  /** One attribute: a name, then a double-quoted, single-quoted or unquoted value. */
33
41
  const ATTRIBUTE = /([^\s"'=<>/]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu;
34
42
 
35
- /** A value that names a resource on another origin, or by another scheme. */
36
- const ELSEWHERE = /^(?:\/\/|[A-Za-z][A-Za-z\d+.-]*:)/u;
37
-
38
43
  /** The `type` values a classic script carries, compared in lowercase. */
39
44
  const CLASSIC_TYPES: ReadonlySet<string> = new Set(["text/javascript", "application/javascript"]);
40
45
 
41
- /** The prefixes an inline script's specifier names a file of the target with. */
42
- const FILE_PREFIXES = ["/", "./", "../"] as const;
43
-
44
46
  /** Each attribute of one start tag's attribute text, by its lowercase name. */
45
47
  function attributes(text: string): ReadonlyMap<string, string> {
46
48
  const found = new Map<string, string>();
@@ -94,9 +96,7 @@ function scriptValues(parse: ParseFile | undefined, path: string, text: string):
94
96
  const end = rest.search(SCRIPT_END);
95
97
  const content = end === -1 ? rest : rest.slice(0, end);
96
98
  values.push(
97
- ...specifiersOf(parse, `${path}.${String(start)}.ts`, content).filter((specifier) =>
98
- FILE_PREFIXES.some((prefix) => specifier.startsWith(prefix)),
99
- ),
99
+ ...specifiersOf(parse, `${path}.${String(start)}.ts`, content).filter(isExplicitPath),
100
100
  );
101
101
  }
102
102
  return values;
@@ -132,18 +132,19 @@ export function htmlEntries(
132
132
  }
133
133
  const dir = dirnamePath(path);
134
134
  for (const value of scriptValues(parse, path, text)) {
135
- if (ELSEWHERE.test(value)) {
135
+ // A value with a scheme, or one starting `//`, names a resource on another origin.
136
+ if (value.startsWith("//") || hasURLScheme(value)) {
136
137
  continue;
137
138
  }
138
- const base = value.startsWith("/") ? manifestDirectory(host, root, dir) : dir;
139
- const relative = value.startsWith("/") ? `.${value}` : value;
139
+ const base = isAbsolutePath(value) ? manifestDirectory(host, root, dir) : dir;
140
+ const relative = isAbsolutePath(value) ? `.${value}` : value;
140
141
  const reached = resolveRelativeSpecifier(host, base, relative);
141
142
  const file = reached === undefined ? undefined : files.byName.get(reached);
142
143
  if (file !== undefined) {
143
144
  found.push({ file, source: value });
144
145
  continue;
145
146
  }
146
- const served = value.startsWith("/")
147
+ const served = isAbsolutePath(value)
147
148
  ? files.outputDirs.filter((outDir) => outDir === dir)
148
149
  : [];
149
150
  const emitting = [base, ...served].flatMap((against) =>
@@ -7,7 +7,7 @@
7
7
  import type { SourceFile } from "@typescript/native/ast";
8
8
  import { resolveRelativeSpecifier } from "./configuration-files.ts";
9
9
  import type { Host } from "../host.ts";
10
- import { dirnamePath, joinPath, resolvePath } from "../paths.ts";
10
+ import { dirnamePath, joinPath, resolvePath, startsRelativePath } from "../paths.ts";
11
11
  import type { SourceFiles } from "../source-files.ts";
12
12
  import { filesBelow } from "../fs/tree.ts";
13
13
 
@@ -154,7 +154,7 @@ function importedBy(
154
154
  }
155
155
  const matched = open === undefined ? IMPORT_LINE.exec(line) : null;
156
156
  const specifier = matched?.[2];
157
- if (specifier?.startsWith("./") === true || specifier?.startsWith("../") === true) {
157
+ if (specifier !== undefined && startsRelativePath(specifier)) {
158
158
  found.push({ specifier, names: clauseNames(matched?.[1] ?? "") });
159
159
  }
160
160
  }
@@ -62,6 +62,7 @@ import {
62
62
  } from "@typescript/native/ast";
63
63
  import { commonJSModuleValue, type ModuleValue } from "../inventory/commonjs.ts";
64
64
  import { callsPlatformBy, lexicalBinding, writtenName, type BindingOf } from "../platform-calls.ts";
65
+ import { startsRelativePath } from "../paths.ts";
65
66
  import { unwrapped } from "../unwrap.ts";
66
67
 
67
68
  /** Where a value's text starts in its file, or undefined in a document read with no positions. */
@@ -589,7 +590,7 @@ export function relativeSpecifiers(file: SourceFile): readonly string[] {
589
590
  }
590
591
  const found: string[] = [];
591
592
  const named = (specifier: string | undefined): void => {
592
- if (specifier?.startsWith("./") === true || specifier?.startsWith("../") === true) {
593
+ if (specifier !== undefined && startsRelativePath(specifier)) {
593
594
  found.push(specifier);
594
595
  }
595
596
  };