@cplieger/deadset-ts 2.0.0 → 2.2.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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ deadset-ts is a command-line analyzer for TypeScript and JavaScript projects. It
8
8
 
9
9
  deadset-ts is report-only. It never edits source, and it refuses any `--fix` flag.
10
10
 
11
- **Status: pre-release.** This version implements contract 1.7.0 and ships the analyzer's foundation: one compiler session per run over every `tsconfig` project discovered under the target (the explicit scope, every `tsconfig*.json`, each project's `references`), a fail-closed read of every project's diagnostics, configuration decoding against the contract's closed key list with `print-config` and its provenance, `print-projects`, the report-only guard, the symbol inventory of every declaration a project's own files hold down to its class and type members, and the one-pass reference resolution over that inventory. The inventory and the reference pass are library functions the package exports; no verb reports findings yet, so `analyze`, `explain`, `print-roots`, `print-retained` and `describe` exit with a usage message. The one runtime dependency is the `typescript` package at exactly 7.0.2, installed under the `@typescript/native` name until TypeScript 7.1 ships a compiler API the tooling can share.
11
+ **Status: pre-release.** This version implements contract 2.1.0 and ships the analyzer's foundation: one compiler session per run over every `tsconfig` project discovered under the target (the explicit scope, every `tsconfig*.json`, each project's `references`), a fail-closed read of every project's diagnostics, configuration decoding against the contract's closed key list with `print-config` and its provenance, `print-projects`, the report-only guard, the symbol inventory of every declaration a project's own files hold down to its class and type members, and the one-pass reference resolution over that inventory. The inventory and the reference pass are library functions the package exports; no verb reports findings yet, so `analyze`, `explain`, `print-roots`, `print-retained` and `describe` exit with a usage message. The one runtime dependency is the `typescript` package at exactly 7.0.2, installed under the `@typescript/native` name until TypeScript 7.1 ships a compiler API the tooling can share.
12
12
 
13
13
  ## Install
14
14
 
@@ -20,7 +20,7 @@ npx jsr add -D @cplieger/deadset-ts
20
20
  npx @cplieger/deadset-ts version
21
21
  ```
22
22
 
23
- The npm package carries the command and the library as JavaScript that the compiler emits at publish, so `npx deadset-ts` and `import("@cplieger/deadset-ts")` both run on plain Node.js 24 or later, with no loader and no flag. It carries the TypeScript source as well, which is what a TypeScript consumer's own compiler resolves. That consumer sets `allowImportingTsExtensions`, because the source names each import with its `.ts` extension. The JSR package ships source only, and Deno compiles it.
23
+ The npm package carries the command and the library as JavaScript that the compiler emits at publish, so `npx deadset-ts` and `import("@cplieger/deadset-ts")` both run on plain Node.js 24 or later, with no loader and no flag. It carries the TypeScript source as well, which is what a TypeScript consumer's own compiler resolves. That consumer sets `allowImportingTsExtensions`, because the source names each import with its `.ts` extension. The JSR package ships source only, and Deno compiles it: a Deno program imports and type-checks the library, but the compiler's synchronous session does not open under Deno, so running an analysis needs Node.
24
24
 
25
25
  ## Usage
26
26
 
@@ -70,7 +70,7 @@ The symbol inventory:
70
70
  The reference pass:
71
71
 
72
72
  - `references(project, held, targetRoot, options)`: every reference from one project's own files to a declaration of its inventory, in position order, with the rules that classified files as test files.
73
- - `DEFAULT_BATCH_CAP`: the batch size a run takes when the configuration names none.
73
+ - `DEFAULT_BATCH_CAP`: the batch size the reference pass takes when its caller passes none; it is an option of the pass, not a configuration key. [Batch-cap calibration](docs/batch-cap-calibration.md) is the measurement that set it.
74
74
  - `Reference`, `References`, `ReferenceOptions`, `ReferenceCost`, `TestFileRule`, `Resolution`, `Use`: one reference, one project's set of them, how a run resolves them, what that cost, one test-file rule, which accessor answered a reference, and whether it reads or writes.
75
75
 
76
76
  Positions:
@@ -1,4 +1,4 @@
1
- import { isClassDeclaration, isComputedPropertyName, isEnumDeclaration, isEnumMember, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isIntersectionTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isModuleBlock, isModuleDeclaration, isNamespaceExport, isPrivateIdentifier, isPropertyDeclaration, isPropertySignatureDeclaration, isSetAccessorDeclaration, isTypeAliasDeclaration, isTypeLiteralNode, isUnionTypeNode, isVariableStatement, ModifierFlags, } from "@typescript/native/unstable/ast";
1
+ import { isClassDeclaration, isComputedPropertyName, isEnumDeclaration, isEnumMember, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isIntersectionTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isModuleBlock, isModuleDeclaration, isNamespaceExport, isParenthesizedTypeNode, isPrivateIdentifier, isPropertyDeclaration, isPropertySignatureDeclaration, isSetAccessorDeclaration, isTypeAliasDeclaration, isTypeLiteralNode, isUnionTypeNode, isVariableStatement, ModifierFlags, } from "@typescript/native/unstable/ast";
2
2
  import { dirnamePath, joinPath, relativePath } from "./paths.js";
3
3
  import { byPosition, positionKey, renderPosition } from "./position.js";
4
4
  import { computedComponent, nameComponent, renderRef } from "./ref.js";
@@ -104,6 +104,27 @@ function isStringOrNumericName(name) {
104
104
  function memberKey(component, isStatic) {
105
105
  return `${component.computed ? "[" : ""}${component.text}${isStatic ? ":static" : ""}`;
106
106
  }
107
+ /** The type one node is, through however many parentheses are written around it. */
108
+ function unparenthesizedType(type) {
109
+ let held = type;
110
+ while (isParenthesizedTypeNode(held)) {
111
+ held = held.type;
112
+ }
113
+ return held;
114
+ }
115
+ /**
116
+ * The direct constituents of one type. Parentheses are syntax and are not a
117
+ * constituent, so a parenthesized type contributes the constituents it holds: the
118
+ * constituents of `(A | B) & C` are `A`, `B` and `C`. A union or an intersection
119
+ * written without them stays one constituent.
120
+ */
121
+ function constituentsOf(type) {
122
+ const held = unparenthesizedType(type);
123
+ if (!isUnionTypeNode(held) && !isIntersectionTypeNode(held)) {
124
+ return [held];
125
+ }
126
+ return held.types.flatMap((member) => isParenthesizedTypeNode(member) ? constituentsOf(member) : [member]);
127
+ }
107
128
  /**
108
129
  * The members one type alias declares: the members of the object type it writes, and
109
130
  * of every object type that is a direct constituent of a union or an intersection it
@@ -115,11 +136,7 @@ function memberKey(component, isStatic) {
115
136
  * neither of them is the one a reference to it would name.
116
137
  */
117
138
  function aliasMembers(type) {
118
- const literals = isTypeLiteralNode(type)
119
- ? [type]
120
- : isUnionTypeNode(type) || isIntersectionTypeNode(type)
121
- ? type.types.filter(isTypeLiteralNode)
122
- : [];
139
+ const literals = constituentsOf(type).filter(isTypeLiteralNode);
123
140
  if (literals.length < 2) {
124
141
  return literals[0]?.members ?? [];
125
142
  }
@@ -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 = "1.7.0";
7
+ export const CONTRACT_VERSION = "2.1.0";
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
package/src/inventory.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  isModuleBlock,
16
16
  isModuleDeclaration,
17
17
  isNamespaceExport,
18
+ isParenthesizedTypeNode,
18
19
  isPrivateIdentifier,
19
20
  isPropertyDeclaration,
20
21
  isPropertySignatureDeclaration,
@@ -327,6 +328,31 @@ function memberKey(component: Component, isStatic: boolean): string {
327
328
  return `${component.computed ? "[" : ""}${component.text}${isStatic ? ":static" : ""}`;
328
329
  }
329
330
 
331
+ /** The type one node is, through however many parentheses are written around it. */
332
+ function unparenthesizedType(type: Node): Node {
333
+ let held = type;
334
+ while (isParenthesizedTypeNode(held)) {
335
+ held = held.type;
336
+ }
337
+ return held;
338
+ }
339
+
340
+ /**
341
+ * The direct constituents of one type. Parentheses are syntax and are not a
342
+ * constituent, so a parenthesized type contributes the constituents it holds: the
343
+ * constituents of `(A | B) & C` are `A`, `B` and `C`. A union or an intersection
344
+ * written without them stays one constituent.
345
+ */
346
+ function constituentsOf(type: Node): readonly Node[] {
347
+ const held = unparenthesizedType(type);
348
+ if (!isUnionTypeNode(held) && !isIntersectionTypeNode(held)) {
349
+ return [held];
350
+ }
351
+ return held.types.flatMap((member) =>
352
+ isParenthesizedTypeNode(member) ? constituentsOf(member) : [member],
353
+ );
354
+ }
355
+
330
356
  /**
331
357
  * The members one type alias declares: the members of the object type it writes, and
332
358
  * of every object type that is a direct constituent of a union or an intersection it
@@ -338,11 +364,7 @@ function memberKey(component: Component, isStatic: boolean): string {
338
364
  * neither of them is the one a reference to it would name.
339
365
  */
340
366
  function aliasMembers(type: Node): readonly Node[] {
341
- const literals = isTypeLiteralNode(type)
342
- ? [type]
343
- : isUnionTypeNode(type) || isIntersectionTypeNode(type)
344
- ? type.types.filter(isTypeLiteralNode)
345
- : [];
367
+ const literals = constituentsOf(type).filter(isTypeLiteralNode);
346
368
  if (literals.length < 2) {
347
369
  return literals[0]?.members ?? [];
348
370
  }
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 = "1.7.0";
7
+ export const CONTRACT_VERSION = "2.1.0";