@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.4

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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +61 -10
  3. package/dist/src/augmentations.js +63 -1
  4. package/dist/src/build-scripts.js +437 -0
  5. package/dist/src/conformance-record.js +1 -1
  6. package/dist/src/contextual-uses.js +56 -9
  7. package/dist/src/convention-rows.js +39 -2
  8. package/dist/src/conventions.js +15 -0
  9. package/dist/src/dependencies.js +98 -1
  10. package/dist/src/discover.js +14 -82
  11. package/dist/src/entry-points.js +9 -18
  12. package/dist/src/exempt.js +8 -0
  13. package/dist/src/findings/completion.js +66 -28
  14. package/dist/src/findings/intra-function.js +4 -2
  15. package/dist/src/findings/reads-and-writes.js +7 -6
  16. package/dist/src/findings/unused-declarations.js +4 -1
  17. package/dist/src/graph.js +5 -3
  18. package/dist/src/intra-function-parts.js +45 -17
  19. package/dist/src/kinds.js +8 -8
  20. package/dist/src/manifest.js +18 -0
  21. package/dist/src/matrix.js +38 -4
  22. package/dist/src/query.js +1 -0
  23. package/dist/src/report.js +23 -4
  24. package/dist/src/reporters/annotations.js +15 -8
  25. package/dist/src/reporters/sarif.js +12 -8
  26. package/dist/src/roots.js +197 -42
  27. package/dist/src/serialization-contract.js +590 -12
  28. package/dist/src/sweep.js +7 -9
  29. package/dist/src/test-globals.js +234 -0
  30. package/dist/src/type-errors.js +11 -3
  31. package/dist/src/unseen-loads.js +107 -0
  32. package/dist/src/verbs/analyze.js +24 -3
  33. package/dist/src/workflow-steps.js +32 -15
  34. package/dist/src/yaml-document.js +355 -0
  35. package/jsr.json +1 -1
  36. package/package.json +2 -2
  37. package/src/analysis.ts +109 -12
  38. package/src/augmentations.ts +77 -2
  39. package/src/build-scripts.ts +564 -0
  40. package/src/conformance-record.ts +1 -1
  41. package/src/contextual-uses.ts +64 -12
  42. package/src/convention-rows.ts +47 -2
  43. package/src/conventions.ts +16 -0
  44. package/src/dependencies.ts +110 -1
  45. package/src/discover.ts +14 -94
  46. package/src/entry-points.ts +10 -25
  47. package/src/exempt.ts +16 -0
  48. package/src/file-facts.ts +3 -0
  49. package/src/findings/boundary.ts +2 -0
  50. package/src/findings/completion.ts +76 -30
  51. package/src/findings/intra-function.ts +4 -2
  52. package/src/findings/reads-and-writes.ts +7 -6
  53. package/src/findings/unused-declarations.ts +6 -1
  54. package/src/graph.ts +5 -3
  55. package/src/intra-function-parts.ts +55 -16
  56. package/src/kinds.ts +8 -8
  57. package/src/manifest.ts +21 -3
  58. package/src/matrix.ts +51 -4
  59. package/src/query.ts +11 -0
  60. package/src/report.ts +56 -16
  61. package/src/reporters/annotations.ts +21 -10
  62. package/src/reporters/reporter.ts +1 -3
  63. package/src/reporters/sarif.ts +16 -8
  64. package/src/roots.ts +226 -46
  65. package/src/serialization-contract.ts +758 -12
  66. package/src/sweep.ts +23 -9
  67. package/src/test-globals.ts +275 -0
  68. package/src/type-errors.ts +14 -3
  69. package/src/unseen-loads.ts +145 -0
  70. package/src/verbs/analyze.ts +28 -3
  71. package/src/workflow-steps.ts +35 -17
  72. package/src/yaml-document.ts +377 -0
package/dist/src/roots.js CHANGED
@@ -7,8 +7,9 @@
7
7
  * decides; the published API only supposes a consumer, so a published export is live
8
8
  * under reachability and still a candidate under reference counting.
9
9
  */
10
- import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/ast";
10
+ import { isArrowFunction, isAssertionExpression, isExpressionWithTypeArguments, isFunctionLikeDeclaration, isIdentifier, isMethodDeclaration, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAssignment, isReturnStatement, isSatisfiesExpression, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
11
11
  import { aliasChains } from "./alias-chain.js";
12
+ import { isGlobalBlock } from "./augmentations.js";
12
13
  import { filesNamedBy, outsideChain, relativeSpecifiers, } from "./configuration-files.js";
13
14
  import { conventionEntries } from "./conventions.js";
14
15
  import { entryPoints } from "./entry-points.js";
@@ -260,6 +261,10 @@ export function roots(project, held, targetRoot, options) {
260
261
  published.push(...entryFiles(entry));
261
262
  }
262
263
  }
264
+ if (options.publishedAPI) {
265
+ // A library's bundle hands what its entry exports to every program that loads it.
266
+ published.push(...points.filter((point) => point.rule === "bundler-entry").map((point) => point.file));
267
+ }
263
268
  const publishedNames = new Set(published.map((file) => file.fileName));
264
269
  const patterned = [];
265
270
  for (const pattern of options.entryFiles) {
@@ -439,9 +444,11 @@ const EXPORTING_CONTAINERS = new Set(["file", "namespace"]);
439
444
  * Every declaration a consumer of the library can name: what the published files export,
440
445
  * and below each the members a consumer writes, a namespace's exported ones and any other
441
446
  * declaration's public and protected ones. A declaration a published one's type names is
442
- * published too, and so is an exported declaration a release tag marks. A type name the
443
- * checker leaves unanswered publishes every declaration spelled as it is, as `guessed`. A
444
- * published file is not itself published.
447
+ * published too, and so is an exported declaration a release tag marks, and every
448
+ * declaration a `declare global` block of a published file, or of a file holding a published
449
+ * declaration, holds: the consumer's compiler loads that file's typings and their global
450
+ * scope with it. A type name the checker leaves unanswered publishes every declaration
451
+ * spelled as it is, as `guessed`. A published file is not itself published.
445
452
  */
446
453
  function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
447
454
  const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
@@ -467,46 +474,80 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
467
474
  reach(id);
468
475
  }
469
476
  }
470
- while (pending.length > 0) {
471
- const round = [];
472
- for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
473
- round.push(next);
474
- const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
475
- for (const member of children.get(next) ?? []) {
476
- const reachable = exporting
477
- ? member.exported
478
- : member.visibility !== "private" && member.visibility !== "private-name";
479
- if (member.kind !== "type-parameter" && reachable) {
477
+ const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
478
+ const blocks = held.symbols.filter((one) => isGlobalBlock(one, held, files));
479
+ const fileOf = (id) => {
480
+ let at = byId.get(id);
481
+ while (at !== undefined && at.kind !== "file") {
482
+ at = byId.get(at.parent);
483
+ }
484
+ return at?.id;
485
+ };
486
+ const publishedFiles = published.flatMap((file) => held.declarations.get(nodeKey(file, file)) ?? []);
487
+ const globalsReached = () => {
488
+ const typed = new Set([...publishedFiles, ...[...reached].flatMap((id) => fileOf(id) ?? [])]);
489
+ const before = pending.length;
490
+ for (const block of blocks.filter((one) => typed.has(one.parent))) {
491
+ reach(block.id);
492
+ for (const member of children.get(block.id) ?? []) {
493
+ if (member.kind !== "type-parameter") {
480
494
  reach(member.id);
481
495
  }
482
496
  }
483
497
  }
484
- const named = round.flatMap((id) => {
485
- const symbol = byId.get(id);
486
- return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
487
- });
488
- for (const symbol of inferredTypeSymbols(project, round.flatMap((id) => {
489
- const symbol = byId.get(id);
490
- return symbol === undefined ? [] : nodes.nodesOf(symbol);
491
- }))) {
492
- for (const target of chains.chainOf(symbol)) {
493
- reach(target.id);
494
- }
495
- }
496
- project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
497
- if (symbol === UNANSWERED) {
498
- for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
499
- guessed.add(id);
500
- reach(id);
498
+ return pending.length > before;
499
+ };
500
+ do {
501
+ while (pending.length > 0) {
502
+ const round = [];
503
+ for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
504
+ round.push(next);
505
+ const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
506
+ for (const member of children.get(next) ?? []) {
507
+ const reachable = exporting
508
+ ? member.exported
509
+ : member.visibility !== "private" && member.visibility !== "private-name";
510
+ if (member.kind !== "type-parameter" && reachable) {
511
+ reach(member.id);
512
+ }
501
513
  }
502
514
  }
503
- else if (symbol !== undefined) {
515
+ const named = round.flatMap((id) => {
516
+ const symbol = byId.get(id);
517
+ return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
518
+ });
519
+ const inferred = inferredTypeSymbols(project, round.flatMap((id) => {
520
+ const symbol = byId.get(id);
521
+ return symbol === undefined ? [] : nodes.nodesOf(symbol);
522
+ }));
523
+ for (const symbol of inferred.types) {
504
524
  for (const target of chains.chainOf(symbol)) {
505
525
  reach(target.id);
506
526
  }
507
527
  }
508
- });
509
- }
528
+ for (const symbol of inferred.members) {
529
+ for (const target of chains.chainOf(symbol)) {
530
+ const member = byId.get(target.id);
531
+ if (member?.visibility === "public" || member?.visibility === "protected") {
532
+ reach(target.id);
533
+ }
534
+ }
535
+ }
536
+ project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
537
+ if (symbol === UNANSWERED) {
538
+ for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
539
+ guessed.add(id);
540
+ reach(id);
541
+ }
542
+ }
543
+ else if (symbol !== undefined) {
544
+ for (const target of chains.chainOf(symbol)) {
545
+ reach(target.id);
546
+ }
547
+ }
548
+ });
549
+ }
550
+ } while (globalsReached());
510
551
  return { ids: [...reached], guessed: [...guessed] };
511
552
  }
512
553
  /** The type names declarations of the inventory write, each file walked once, when first asked. */
@@ -544,11 +585,101 @@ function declarationNodes(held, byPath) {
544
585
  /** How deep the type arguments and constituents of an inferred type are followed. */
545
586
  const INFERRED_DEPTH = 4;
546
587
  /**
547
- * The declarations of the types the compiler infers where a declaration writes none: the
548
- * type of a variable or a property with no annotation, and the result of a function or a
549
- * method with no result annotation, each with the types its type arguments and its union
550
- * or intersection constituents name, to a fixed depth. A question the checker leaves
551
- * unanswered names nothing.
588
+ * The values one function body returns, each through the type assertions around it: the
589
+ * value a caller receives carries the operand's type whatever type the assertion or the
590
+ * signature names. A nested function's returns are its own.
591
+ */
592
+ function returnedValues(body) {
593
+ const found = [];
594
+ const add = (value) => {
595
+ for (let at = value; at !== undefined;) {
596
+ found.push(at);
597
+ at =
598
+ isAssertionExpression(at) || isSatisfiesExpression(at) || isParenthesizedExpression(at)
599
+ ? at.expression
600
+ : undefined;
601
+ }
602
+ };
603
+ if (!isFunctionLikeDeclaration(body.parent) || body.parent.body !== body) {
604
+ return found;
605
+ }
606
+ if (isArrowFunction(body.parent) && body.kind !== SyntaxKind.Block) {
607
+ add(body);
608
+ return found;
609
+ }
610
+ const visit = (node) => {
611
+ if (isFunctionLikeDeclaration(node)) {
612
+ return;
613
+ }
614
+ if (isReturnStatement(node) && node.expression !== undefined) {
615
+ add(node.expression);
616
+ }
617
+ node.forEachChild(visit);
618
+ };
619
+ body.forEachChild(visit);
620
+ return found;
621
+ }
622
+ /** The object literal one returned value is, written there or as the initializer of a variable `body` declares. */
623
+ function returnedLiteral(value, body) {
624
+ if (isObjectLiteralExpression(value)) {
625
+ return value;
626
+ }
627
+ if (!isIdentifier(value)) {
628
+ return undefined;
629
+ }
630
+ let found;
631
+ const visit = (node) => {
632
+ if (isVariableDeclaration(node) &&
633
+ isIdentifier(node.name) &&
634
+ node.name.text === value.text &&
635
+ node.initializer !== undefined) {
636
+ let at = node.initializer;
637
+ while (isAssertionExpression(at) ||
638
+ isSatisfiesExpression(at) ||
639
+ isParenthesizedExpression(at)) {
640
+ at = at.expression;
641
+ }
642
+ found = isObjectLiteralExpression(at) ? at : found;
643
+ }
644
+ if (!isFunctionLikeDeclaration(node)) {
645
+ node.forEachChild(visit);
646
+ }
647
+ };
648
+ body.forEachChild(visit);
649
+ return found;
650
+ }
651
+ /**
652
+ * The values one function body returns, and those each method of an object literal it
653
+ * returns returns, to a fixed depth: a caller holding the literal calls its methods, and
654
+ * receives what they return.
655
+ */
656
+ function returnedThroughLiterals(body, depth = 0) {
657
+ const values = returnedValues(body);
658
+ if (depth >= INFERRED_DEPTH) {
659
+ return values;
660
+ }
661
+ const found = [...values];
662
+ for (const value of values) {
663
+ for (const property of returnedLiteral(value, body)?.properties ?? []) {
664
+ const fn = isMethodDeclaration(property)
665
+ ? property
666
+ : isPropertyAssignment(property) && isFunctionLikeDeclaration(property.initializer)
667
+ ? property.initializer
668
+ : undefined;
669
+ if (fn?.body !== undefined) {
670
+ found.push(...returnedThroughLiterals(fn.body, depth + 1));
671
+ }
672
+ }
673
+ }
674
+ return found;
675
+ }
676
+ /**
677
+ * The declarations of the types the compiler infers where a declaration writes none, and
678
+ * of the values a function returns: the type of a variable or a property with no
679
+ * annotation, the result of a function or a method with no result annotation, and the type
680
+ * of each value its body returns, each with the types its type arguments and its union or
681
+ * intersection constituents name, to a fixed depth; and as `members` the properties of each
682
+ * returned value's type. A question the checker leaves unanswered names nothing.
552
683
  */
553
684
  function inferredTypeSymbols(project, declarations) {
554
685
  const { queries } = project;
@@ -557,12 +688,24 @@ function inferredTypeSymbols(project, declarations) {
557
688
  !isFunctionLikeDeclaration(node.initializer ?? node)
558
689
  ? [node.name]
559
690
  : []);
691
+ const returned = declarations.flatMap((node) => {
692
+ const signature = isFunctionLikeDeclaration(node)
693
+ ? node
694
+ : isVariableDeclaration(node) &&
695
+ node.initializer !== undefined &&
696
+ isFunctionLikeDeclaration(node.initializer)
697
+ ? node.initializer
698
+ : undefined;
699
+ return signature?.body === undefined ? [] : returnedThroughLiterals(signature.body);
700
+ });
560
701
  const types = [];
561
- queries.typesAt(valued).forEach((type) => {
702
+ const returnedTypes = [];
703
+ queries.typesAt([...valued, ...returned]).forEach((type, at) => {
562
704
  if (type !== UNANSWERED && type !== undefined) {
563
- types.push(type);
705
+ (at < valued.length ? types : returnedTypes).push(type);
564
706
  }
565
707
  });
708
+ types.push(...returnedTypes);
566
709
  for (const node of declarations) {
567
710
  const signature = isFunctionLikeDeclaration(node)
568
711
  ? node
@@ -604,7 +747,19 @@ function inferredTypeSymbols(project, declarations) {
604
747
  types.forEach((type) => {
605
748
  visit(type, 0);
606
749
  });
607
- return [...found.values()];
750
+ // A returned value carries each property of its type, one an anonymous type declares or
751
+ // an intersection merges included, which no type name reaches.
752
+ const members = new Map();
753
+ for (const type of returnedTypes) {
754
+ const parts = queries.constituents(type);
755
+ for (const one of [type, ...(parts === UNANSWERED ? [] : parts)]) {
756
+ const properties = queries.propertiesOf(one);
757
+ for (const property of properties === UNANSWERED ? [] : properties) {
758
+ members.set(property.id, property);
759
+ }
760
+ }
761
+ }
762
+ return { types: [...found.values()], members: [...members.values()] };
608
763
  }
609
764
  /**
610
765
  * The names of types one declaration's own syntax writes: in a type reference, a