@cplieger/deadset-ts 5.2.2 → 6.0.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.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +28 -5
  3. package/dist/src/augmentations.js +50 -0
  4. package/dist/src/components.js +15 -15
  5. package/dist/src/conformance-record.js +2 -2
  6. package/dist/src/convention-rows.js +17 -2
  7. package/dist/src/conventions.js +82 -1
  8. package/dist/src/dependencies.js +42 -26
  9. package/dist/src/entry-points.js +28 -14
  10. package/dist/src/exempt.js +1 -0
  11. package/dist/src/findings/dependencies-and-module-machinery.js +2 -2
  12. package/dist/src/findings/emitters.js +47 -12
  13. package/dist/src/findings/interfaces.js +4 -3
  14. package/dist/src/findings/intra-function.js +15 -2
  15. package/dist/src/findings/unused-declarations.js +13 -1
  16. package/dist/src/findings-pass.js +1 -0
  17. package/dist/src/graph.js +6 -6
  18. package/dist/src/implementations.js +1 -1
  19. package/dist/src/indexed-access.js +215 -0
  20. package/dist/src/interface-satisfaction.js +85 -23
  21. package/dist/src/intra-function-parts.js +139 -30
  22. package/dist/src/inventory.js +6 -1
  23. package/dist/src/kinds.js +13 -12
  24. package/dist/src/matrix.js +34 -9
  25. package/dist/src/module-declarations.js +180 -0
  26. package/dist/src/outside-tests.js +17 -15
  27. package/dist/src/overrides.js +70 -9
  28. package/dist/src/query.js +7 -0
  29. package/dist/src/ref.js +1 -0
  30. package/dist/src/references.js +6 -0
  31. package/dist/src/report.js +6 -1
  32. package/dist/src/reporters/sarif.js +3 -1
  33. package/dist/src/reporters/text.js +23 -3
  34. package/dist/src/roots.js +50 -10
  35. package/dist/src/version.js +1 -1
  36. package/dist/src/workflow-steps.js +82 -0
  37. package/jsr.json +1 -1
  38. package/package.json +8 -2
  39. package/src/analysis.ts +40 -4
  40. package/src/augmentations.ts +72 -0
  41. package/src/components.ts +15 -13
  42. package/src/conformance-record.ts +2 -2
  43. package/src/convention-rows.ts +25 -3
  44. package/src/conventions.ts +106 -2
  45. package/src/dependencies.ts +51 -31
  46. package/src/entry-points.ts +32 -19
  47. package/src/exempt.ts +4 -1
  48. package/src/findings/dependencies-and-module-machinery.ts +2 -2
  49. package/src/findings/emitters.ts +71 -12
  50. package/src/findings/interfaces.ts +7 -6
  51. package/src/findings/intra-function.ts +20 -2
  52. package/src/findings/unused-declarations.ts +16 -1
  53. package/src/findings-pass.ts +4 -1
  54. package/src/graph.ts +6 -6
  55. package/src/implementations.ts +1 -1
  56. package/src/indexed-access.ts +295 -0
  57. package/src/interface-satisfaction.ts +118 -27
  58. package/src/intra-function-parts.ts +196 -35
  59. package/src/inventory.ts +8 -0
  60. package/src/kinds.ts +14 -12
  61. package/src/matrix.ts +51 -8
  62. package/src/module-declarations.ts +241 -0
  63. package/src/outside-tests.ts +18 -16
  64. package/src/overrides.ts +80 -14
  65. package/src/query.ts +28 -0
  66. package/src/ref.ts +1 -0
  67. package/src/references.ts +6 -0
  68. package/src/report.ts +11 -1
  69. package/src/reporters/sarif.ts +4 -1
  70. package/src/reporters/text.ts +25 -3
  71. package/src/roots.ts +56 -7
  72. package/src/sweep.ts +12 -0
  73. package/src/version.ts +1 -1
  74. package/src/workflow-steps.ts +104 -0
  75. package/src/workspace-run.ts +3 -4
package/src/roots.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  isQualifiedName,
16
16
  isTypeQueryNode,
17
17
  isTypeReferenceNode,
18
+ isVariableStatement,
18
19
  type Node,
19
20
  type SourceFile,
20
21
  } from "@typescript/native/unstable/ast";
@@ -297,6 +298,7 @@ export function roots<Brand>(
297
298
  options.host,
298
299
  targetRoot,
299
300
  options.parse,
301
+ options.conventions?.selected,
300
302
  );
301
303
  const published: SourceFile[] = [];
302
304
  const entries = [
@@ -379,7 +381,7 @@ export function roots<Brand>(
379
381
  }
380
382
 
381
383
  const api = options.publishedAPI
382
- ? publishedAPI(project, published, exportsOf, held, files.byPath)
384
+ ? publishedAPI(project, published, exportsOf, held, files.byPath, releaseTagged(held, files))
383
385
  : { ids: [], guessed: [] };
384
386
  for (const id of api.ids) {
385
387
  add(id, "published-api", "");
@@ -453,16 +455,59 @@ function exportTables<Brand>(
453
455
  return { tables, answered };
454
456
  }
455
457
 
458
+ /** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
459
+ const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
460
+
461
+ /**
462
+ * Whether the documentation comment of the statement that starts at `start`, the last
463
+ * `/** *\/` comment before it with only white space between the two, holds a release tag.
464
+ */
465
+ function releaseTagIn(text: string, from: number, start: number): boolean {
466
+ const leading = text.slice(from, start);
467
+ const open = leading.lastIndexOf("/**");
468
+ const close = open < 0 ? -1 : leading.indexOf("*/", open + 3);
469
+ if (close < 0 || leading.slice(close + 2).trim() !== "") {
470
+ return false;
471
+ }
472
+ return RELEASE_TAG.test(leading.slice(open + 3, close));
473
+ }
474
+
475
+ /** The exported top-level declarations of the program's files a release tag marks. */
476
+ function releaseTagged(
477
+ held: Inventory,
478
+ files: { readonly byPath: ReadonlyMap<string, SourceFile> },
479
+ ): readonly string[] {
480
+ const exported = new Set(held.symbols.filter((symbol) => symbol.exported).map((one) => one.id));
481
+ const found: string[] = [];
482
+ for (const file of files.byPath.values()) {
483
+ for (const statement of file.statements) {
484
+ if (!releaseTagIn(file.text, statement.pos, statement.getStart())) {
485
+ continue;
486
+ }
487
+ const nodes: readonly Node[] = isVariableStatement(statement)
488
+ ? statement.declarationList.declarations
489
+ : [statement];
490
+ for (const node of nodes) {
491
+ const id = held.declarations.get(nodeKey(file, node));
492
+ if (id !== undefined && exported.has(id)) {
493
+ found.push(id);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ return found;
499
+ }
500
+
456
501
  /** The kinds of container whose export table, not its members' visibility, says what it publishes. */
457
502
  const EXPORTING_CONTAINERS: ReadonlySet<string> = new Set(["file", "namespace"]);
458
503
 
459
504
  /**
460
- * Every declaration a consumer of the library can name: what the published files export
461
- * and, below each, the members a consumer writes: a namespace's exported ones, any other
462
- * declaration's public and protected ones, never a private one or a type parameter. A
463
- * declaration a published one's type names is published too, exported or not. A type name
464
- * the checker leaves unanswered publishes every declaration spelled as it is, and those are
465
- * `guessed`. A published file is not itself published.
505
+ * Every declaration a consumer of the library can name: what the published files export,
506
+ * and below each the members a consumer writes, a namespace's exported ones and any other
507
+ * declaration's public and protected ones. A declaration a published one's type names is
508
+ * published too, and so is an exported declaration a release tag marks. A type name the
509
+ * checker leaves unanswered publishes every declaration spelled as it is, as `guessed`. A
510
+ * published file is not itself published.
466
511
  */
467
512
  function publishedAPI<Brand>(
468
513
  project: ProjectView<Brand>,
@@ -470,6 +515,7 @@ function publishedAPI<Brand>(
470
515
  exportsOf: ReadonlyMap<string, readonly string[]>,
471
516
  held: Inventory,
472
517
  byPath: ReadonlyMap<string, SourceFile>,
518
+ tagged: readonly string[],
473
519
  ): { readonly ids: readonly string[]; readonly guessed: readonly string[] } {
474
520
  const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
475
521
  const children = childrenOf(held);
@@ -484,6 +530,9 @@ function publishedAPI<Brand>(
484
530
  pending.push(id);
485
531
  }
486
532
  };
533
+ for (const id of tagged) {
534
+ reach(id);
535
+ }
487
536
  for (const file of published) {
488
537
  for (const id of exportsOf.get(file.fileName) ?? []) {
489
538
  if (byId.get(id)?.kind === "file") {
package/src/sweep.ts CHANGED
@@ -27,6 +27,18 @@ export interface Exemption {
27
27
  * record holds only while that file is live.
28
28
  */
29
29
  readonly whileLive?: string;
30
+ /**
31
+ * The interface methods the retained member implements, and whether its body is empty.
32
+ * The record holds only while one of them is called through its interface.
33
+ */
34
+ readonly implementing?: Implementing;
35
+ }
36
+
37
+ /** The interface methods one retained class member implements, and whether its body is empty. */
38
+ export interface Implementing {
39
+ /** The declarations of the interface's methods the member answers, by identifier. */
40
+ readonly methods: readonly string[];
41
+ readonly empty: boolean;
30
42
  }
31
43
 
32
44
  /**
package/src/version.ts CHANGED
@@ -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 = "5.3.0";
7
+ export const CONTRACT_VERSION = "6.0.0";
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The source files a CI workflow's `run:` steps name by their path from the target root.
3
+ * A workflow file is read as text, line by line, with no YAML parsed.
4
+ */
5
+
6
+ import type { SourceFile } from "@typescript/native/unstable/ast";
7
+ import type { Host } from "./host.ts";
8
+ import { scriptTokens } from "./manifest.ts";
9
+ import { joinPath, resolvePath } from "./paths.ts";
10
+ import type { SourceFiles } from "./source-files.ts";
11
+
12
+ /** One file a workflow step names. */
13
+ interface WorkflowEntry {
14
+ readonly file: SourceFile;
15
+ /** The token that named it, as the command writes it. */
16
+ readonly source: string;
17
+ }
18
+
19
+ const WORKFLOWS = ".github/workflows";
20
+
21
+ /** A line holding a run command: indentation and one sequence marker before `run:`. */
22
+ const RUN_LINE = /^(\s*(?:- )?)run:(.*)$/u;
23
+
24
+ const BLOCK_SCALAR = /^[|>][+-]?$/u;
25
+
26
+ const QUOTED = /^(["'])(.*)\1$/su;
27
+
28
+ /** A token holding one of these is computed or a pattern, so it names no file. */
29
+ const COMPUTED = /[${*?[]/u;
30
+
31
+ const INDENT = /^\s*/u;
32
+
33
+ function workflowFiles(host: Host, dir: string): readonly string[] {
34
+ let entries;
35
+ try {
36
+ entries = host.readDirectory(dir);
37
+ } catch {
38
+ return [];
39
+ }
40
+ return entries
41
+ .filter((entry) => !entry.directory && /\.ya?ml$/u.test(entry.name))
42
+ .map((entry) => joinPath(dir, entry.name))
43
+ .sort();
44
+ }
45
+
46
+ /** Every command line one workflow file's `run:` steps hold. */
47
+ export function runCommands(text: string): readonly string[] {
48
+ const lines = text.split(/\r?\n/u);
49
+ const found: string[] = [];
50
+ for (let at = 0; at < lines.length; at += 1) {
51
+ const match = RUN_LINE.exec(lines[at] ?? "");
52
+ if (match === null) {
53
+ continue;
54
+ }
55
+ const column = (match[1] ?? "").length;
56
+ const value = (match[2] ?? "").trim().replace(QUOTED, "$2");
57
+ if (!BLOCK_SCALAR.test(value)) {
58
+ found.push(value);
59
+ continue;
60
+ }
61
+ for (let next = at + 1; next < lines.length; next += 1) {
62
+ const line = lines[next] ?? "";
63
+ if (line.trim() !== "" && (INDENT.exec(line)?.[0].length ?? 0) <= column) {
64
+ break;
65
+ }
66
+ found.push(line);
67
+ }
68
+ }
69
+ return found;
70
+ }
71
+
72
+ /**
73
+ * The own files the `run:` steps of every workflow file in the target root's
74
+ * `.github/workflows` name: a token equal to a file's path below the target root, with or
75
+ * without a leading `./`, whatever directory the step runs in.
76
+ */
77
+ export function workflowEntries(
78
+ host: Host,
79
+ targetRoot: string,
80
+ files: SourceFiles,
81
+ ): readonly WorkflowEntry[] {
82
+ const dir = joinPath(resolvePath(host.workingDirectory(), targetRoot), WORKFLOWS);
83
+ const found: WorkflowEntry[] = [];
84
+ for (const path of workflowFiles(host, dir)) {
85
+ let text: string;
86
+ try {
87
+ text = host.readFile(path);
88
+ } catch {
89
+ continue;
90
+ }
91
+ for (const command of runCommands(text)) {
92
+ for (const token of scriptTokens(command)) {
93
+ if (COMPUTED.test(token)) {
94
+ continue;
95
+ }
96
+ const file = files.byPath.get(token.startsWith("./") ? token.slice(2) : token);
97
+ if (file !== undefined) {
98
+ found.push({ file, source: token });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ return found;
104
+ }
@@ -41,10 +41,9 @@ export interface WorkspaceRun {
41
41
  referenceOnly(configFile: string, compiled?: readonly string[]): (file: string) => boolean;
42
42
  /**
43
43
  * Whether one file a target configuration's program holds is the target's own. A
44
- * workspace package's file is by its path alone, whichever import reached it: the
45
- * compiler's answer for a file reached both through `node_modules` and from source
46
- * depends on which import it followed first. Any other file is unless the compiler
47
- * reached it as a library's.
44
+ * workspace package's file is by its path alone: the run reads another package from
45
+ * its source, so the compiler holds that package's files as the importer's own, not
46
+ * as a library's. Any other file is unless the compiler reached it as a library's.
48
47
  */
49
48
  ownFile(file: string, fromExternalLibrary: boolean): boolean;
50
49
  /** The target's own manifest, its targets read back to source. */