@cplieger/deadset-ts 7.2.0-dev.1 → 7.2.0-dev.2

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
@@ -1,6 +1,6 @@
1
1
  # deadset-ts
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@cplieger/deadset-ts)](https://www.npmjs.com/package/@cplieger/deadset-ts) [![JSR](https://jsr.io/badges/@cplieger/deadset-ts)](https://jsr.io/@cplieger/deadset-ts)
3
+ [![npm](https://img.shields.io/npm/v/@cplieger/deadset-ts)](https://www.npmjs.com/package/@cplieger/deadset-ts) [![JSR](https://jsr.io/badges/@cplieger/deadset-ts)](https://jsr.io/@cplieger/deadset-ts) [![Mutation (TS)](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/cplieger/deadset-ts/badges/mutation-ts.json)](https://github.com/cplieger/deadset-ts/issues?q=label%3Astryker-tracker)
4
4
 
5
5
  deadset-ts finds dead code in TypeScript projects, down to class members, type members and dead stores inside functions, and writes a report your CI can fail on.
6
6
 
@@ -466,15 +466,11 @@ function skipWhitespace(text, at) {
466
466
  */
467
467
  function membersAt(text, at) {
468
468
  const found = new Map();
469
- if (text.charAt(at) !== "{") {
470
- return found;
471
- }
472
469
  let cursor = skipWhitespace(text, at + 1);
473
470
  while (text.charAt(cursor) === '"') {
474
471
  const keyEnd = valueEnd(text, cursor);
475
472
  const key = JSON.parse(text.slice(cursor, keyEnd));
476
473
  const value = skipWhitespace(text, skipWhitespace(text, keyEnd) + 1);
477
- found.delete(key);
478
474
  found.set(key, { key: cursor, value });
479
475
  cursor = skipWhitespace(text, valueEnd(text, value));
480
476
  if (text.charAt(cursor) === ",") {
@@ -47,7 +47,7 @@ export function deprecatedDeclarations(project, held) {
47
47
  }
48
48
  }
49
49
  const id = held.declarations.get(nodeKey(file, node));
50
- if (id !== undefined && node !== file) {
50
+ if (id !== undefined) {
51
51
  named.push({ id, handle: project.handle(nameNode(node)) });
52
52
  }
53
53
  node.forEachChild(visit);
@@ -145,7 +145,7 @@ function findingComponents(swept, cascade) {
145
145
  },
146
146
  ];
147
147
  })
148
- .sort((a, b) => byPosition(a.position, b.position) || (a.ref === b.ref ? 0 : a.ref < b.ref ? -1 : 1));
148
+ .sort((a, b) => byPosition(a.position, b.position));
149
149
  return (id) => {
150
150
  const component = held.get(id);
151
151
  return component === undefined
@@ -10,13 +10,6 @@ const UNCALLED_MESSAGE = "no call site invokes or selects the method through the
10
10
  function positionOf(symbol) {
11
11
  return { ...symbol.position, endLine: Math.max(symbol.position.line, symbol.endLine) };
12
12
  }
13
- /** Two strings ordered bytewise. */
14
- function compare(a, b) {
15
- if (a === b) {
16
- return 0;
17
- }
18
- return a < b ? -1 : 1;
19
- }
20
13
  /** One run's declarations and candidates, indexed for the two kinds. */
21
14
  function indexed(input) {
22
15
  const union = input.swept.matrix.union;
@@ -31,7 +24,7 @@ function indexed(input) {
31
24
  .map(symbolOf)
32
25
  .filter((symbol) => symbol !== undefined)
33
26
  .map((symbol) => ({ ref: symbol.ref, name: symbol.name, position: positionOf(symbol) }))
34
- .sort((a, b) => byPosition(a.position, b.position) || compare(a.ref, b.ref)),
27
+ .sort((a, b) => byPosition(a.position, b.position)),
35
28
  };
36
29
  }
37
30
  /** The finding one code makes about one declaration. */
@@ -39,19 +39,17 @@ const WORDS = {
39
39
  const TEST_OF_DEAD_CODE = "every target symbol this test references is reported dead";
40
40
  /**
41
41
  * The code one candidate is reported under, or none, each symbol once under the most
42
- * specific code: a test of dead code first; no other test-file declaration, member of a
43
- * dead container or enum member nothing names; a test-support declaration test code
44
- * references as test-only use; then deprecation, a test reference, and the unreferenced
45
- * codes, an unimportable unused export being the unreachable kind's.
42
+ * specific code: a test of dead code first; no other test-file declaration or enum
43
+ * member nothing names; a test-support declaration test code references as test-only
44
+ * use; then deprecation, a test reference, and the unreferenced codes, an unimportable
45
+ * unused export being the unreachable kind's.
46
46
  */
47
47
  function codeOf(judged) {
48
48
  const { candidate, symbol } = judged;
49
49
  if (candidate.testOfDeadCode) {
50
50
  return "DS1005";
51
51
  }
52
- if ((judged.test && !candidate.unreferencedTest) ||
53
- judged.containerDead ||
54
- NOT_THIS_FAMILY.has(symbol.kind)) {
52
+ if ((judged.test && !candidate.unreferencedTest) || NOT_THIS_FAMILY.has(symbol.kind)) {
55
53
  return undefined;
56
54
  }
57
55
  const referenced = candidate.productionRefs + candidate.testRefs > 0;
@@ -192,7 +190,6 @@ function familyFindings(input, fallen) {
192
190
  candidate,
193
191
  symbol,
194
192
  test: union.test[at] === true,
195
- containerDead: containerDead && !fallen,
196
193
  deprecated: input.deprecated.has(symbol.id),
197
194
  unreachable: unreachable.has(symbol.id),
198
195
  };
@@ -102,9 +102,6 @@ function manifestDirectory(host, root, dir) {
102
102
  if (host.kindOf(joinPath(at, "package.json")) === "file") {
103
103
  return at;
104
104
  }
105
- if (at === root) {
106
- break;
107
- }
108
105
  }
109
106
  return root;
110
107
  }
@@ -303,7 +303,6 @@ function readsBinding(use, declared, ids) {
303
303
  export function projectParts(project, held, references, targetRoot) {
304
304
  const files = project.ownSourceFiles();
305
305
  const extended = extendedNames(files);
306
- const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
307
306
  const parts = [];
308
307
  const signatures = [];
309
308
  const calls = new Map();
@@ -313,12 +312,7 @@ export function projectParts(project, held, references, targetRoot) {
313
312
  const contextual = [];
314
313
  for (const file of files) {
315
314
  // A parameter property's own parts stay with the parameter when its member goes.
316
- const declarationAt = (node) => {
317
- const id = held.declarations.get(nodeKey(file, node));
318
- return id === undefined || kinds.get(id) === "file" || isParameterProperty(node)
319
- ? undefined
320
- : id;
321
- };
315
+ const declarationAt = (node) => isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
322
316
  // A part no declaration holds belongs to the file, which is judged while the file is live.
323
317
  const fileId = held.declarations.get(nodeKey(file, file));
324
318
  const holderOf = (node) => {
@@ -560,10 +554,6 @@ function valueSignature(node) {
560
554
  function parameterNames(signature) {
561
555
  const found = [];
562
556
  const patternNames = (pattern, at) => {
563
- if (isIdentifier(pattern)) {
564
- found.push({ name: pattern, at, pattern: true, exempt: false });
565
- return;
566
- }
567
557
  const rest = isObjectBindingPattern(pattern) &&
568
558
  pattern.elements.some((element) => element.dotDotDotToken !== undefined);
569
559
  for (const element of pattern.elements) {
@@ -741,9 +731,7 @@ function deadStores(file, root, block, holder, locals, ids) {
741
731
  for (const write of writes) {
742
732
  // The rest of the write's own statement runs after it: a later declarator may read the local.
743
733
  const own = statements[write.at];
744
- const sameStatement = own !== undefined && write.target === local.name
745
- ? usesIn(own).filter((use) => use !== local.name)
746
- : [];
734
+ const sameStatement = own !== undefined && write.target === local.name ? usesIn(own) : [];
747
735
  if (sameStatement.length > 0) {
748
736
  continue;
749
737
  }
@@ -733,9 +733,7 @@ export function inventory(project, host, targetRoot) {
733
733
  });
734
734
  }
735
735
  const byId = new Map(building.map((record) => [positionKey(record.position), record]));
736
- const symbols = building
737
- .map(render)
738
- .sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
736
+ const symbols = building.map(render).sort((a, b) => byPosition(a.position, b.position));
739
737
  return {
740
738
  symbols,
741
739
  declarations: new Map([...byNode].map(([key, record]) => [key, positionKey(record.position)])),
@@ -7,13 +7,6 @@ import { componentsOf } from "./components.js";
7
7
  import { graphOf, OUTSIDE } from "./graph.js";
8
8
  import { byPosition } from "./position.js";
9
9
  import { reachedWithTheTests, sweep, } from "./sweep.js";
10
- /** Two strings ordered bytewise. */
11
- function compare(a, b) {
12
- if (a === b) {
13
- return 0;
14
- }
15
- return a < b ? -1 : 1;
16
- }
17
10
  /**
18
11
  * What makes one reference one reference, whichever configuration made it. A consumer's
19
12
  * position is rendered against its own root, so the consumer is part of what it names.
@@ -76,7 +69,7 @@ export function matrixOf(per) {
76
69
  references.push(reference);
77
70
  }
78
71
  });
79
- symbols.sort((a, b) => byPosition(a.position, b.position) || compare(a.ref, b.ref));
72
+ symbols.sort((a, b) => byPosition(a.position, b.position));
80
73
  return {
81
74
  configurations: per.map((one) => one.configuration),
82
75
  graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles)),
@@ -79,9 +79,6 @@ function namesModule(specifier, name) {
79
79
  * the members that hold the export names given.
80
80
  */
81
81
  function namedBy(held) {
82
- if (held.modules.size === 0) {
83
- return () => [];
84
- }
85
82
  const refs = new Map();
86
83
  const children = new Map();
87
84
  for (const symbol of held.symbols) {
@@ -84,7 +84,8 @@ function declaringManifest(host, dir, targetRoot, name) {
84
84
  }
85
85
  }
86
86
  }
87
- if (at === targetRoot || relativePath(targetRoot, at) === undefined || dirnamePath(at) === at) {
87
+ // The target root is not below itself, so the walk stops after reading its manifest.
88
+ if (relativePath(targetRoot, at) === undefined) {
88
89
  return undefined;
89
90
  }
90
91
  }
@@ -19,7 +19,6 @@ const DEPENDENCY_MEMBERS = [
19
19
  "optionalDependencies",
20
20
  ];
21
21
  const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
22
- const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
23
22
  function isRecord(value) {
24
23
  return typeof value === "object" && value !== null && !Array.isArray(value);
25
24
  }
@@ -39,7 +38,7 @@ export function packageNameOf(specifier) {
39
38
  return first;
40
39
  }
41
40
  function inside(dir, path) {
42
- return path === dir || relativePath(dir, path) !== undefined;
41
+ return relativePath(dir, path) !== undefined;
43
42
  }
44
43
  /** The `exports` entry one subpath selects, with what a pattern's `*` matched. */
45
44
  function exportsEntry(exportsField, subpath) {
@@ -199,12 +198,10 @@ export function workspaceResolver(host, workspace) {
199
198
  return found;
200
199
  }
201
200
  }
202
- if (OUTPUT.test(path)) {
203
- const beside = emittedFrom(path, { sourceDirs: [] });
204
- const found = beside.find((candidate) => host.kindOf(candidate) === "file");
205
- if (found !== undefined) {
206
- return found;
207
- }
201
+ const beside = emittedFrom(path, { sourceDirs: [] });
202
+ const found = beside.find((candidate) => host.kindOf(candidate) === "file");
203
+ if (found !== undefined) {
204
+ return found;
208
205
  }
209
206
  return compiled(member, path) && host.kindOf(path) === "file" ? path : undefined;
210
207
  };
@@ -198,14 +198,14 @@ function memberDirectories(host, root, patterns) {
198
198
  }
199
199
  return [...included].filter((dir) => {
200
200
  const below = relativePath(root, dir);
201
- const segments = below === undefined || below === "." || below === "" ? [] : below.split("/");
201
+ const segments = below === undefined ? [] : below.split("/");
202
202
  return dir === root || !excluded.some((pattern) => pathMatches(pattern, segments));
203
203
  });
204
204
  }
205
205
  /** Whether a workspace's patterns name one directory below its root, read without listing any. */
206
206
  function namesDirectory(root, patterns, dir) {
207
207
  const below = relativePath(root, dir);
208
- if (below === undefined || below === "" || below.startsWith("..")) {
208
+ if (below === undefined || below.startsWith("..")) {
209
209
  return false;
210
210
  }
211
211
  const path = below.split("/");
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "7.2.0-dev.1",
3
+ "version": "7.2.0-dev.2",
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": "7.2.0-dev.1",
3
+ "version": "7.2.0-dev.2",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
@@ -40,7 +40,7 @@
40
40
  "@stryker-mutator/core": "10.0.0",
41
41
  "@stryker-mutator/typescript-checker": "10.0.0",
42
42
  "@stryker-mutator/vitest-runner": "10.0.0",
43
- "@types/node": "24.19.1",
43
+ "@types/node": "24.19.2",
44
44
  "eslint": "10.12.0",
45
45
  "fast-check": "4.10.2",
46
46
  "prettier": "3.9.9",
@@ -616,15 +616,11 @@ function skipWhitespace(text: string, at: number): number {
616
616
  */
617
617
  function membersAt(text: string, at: number): Map<string, { key: number; value: number }> {
618
618
  const found = new Map<string, { key: number; value: number }>();
619
- if (text.charAt(at) !== "{") {
620
- return found;
621
- }
622
619
  let cursor = skipWhitespace(text, at + 1);
623
620
  while (text.charAt(cursor) === '"') {
624
621
  const keyEnd = valueEnd(text, cursor);
625
622
  const key = JSON.parse(text.slice(cursor, keyEnd)) as string;
626
623
  const value = skipWhitespace(text, skipWhitespace(text, keyEnd) + 1);
627
- found.delete(key);
628
624
  found.set(key, { key: cursor, value });
629
625
  cursor = skipWhitespace(text, valueEnd(text, value));
630
626
  if (text.charAt(cursor) === ",") {
@@ -73,7 +73,7 @@ export function deprecatedDeclarations<Brand>(
73
73
  }
74
74
  }
75
75
  const id = held.declarations.get(nodeKey(file, node));
76
- if (id !== undefined && node !== file) {
76
+ if (id !== undefined) {
77
77
  named.push({ id, handle: project.handle(nameNode(node)) });
78
78
  }
79
79
  node.forEachChild(visit);
@@ -181,10 +181,7 @@ function findingComponents(
181
181
  },
182
182
  ];
183
183
  })
184
- .sort(
185
- (a, b) =>
186
- byPosition(a.position, b.position) || (a.ref === b.ref ? 0 : a.ref < b.ref ? -1 : 1),
187
- );
184
+ .sort((a, b) => byPosition(a.position, b.position));
188
185
  return (id) => {
189
186
  const component = held.get(id);
190
187
  return component === undefined
@@ -19,14 +19,6 @@ function positionOf(symbol: InventorySymbol): FindingPosition {
19
19
  return { ...symbol.position, endLine: Math.max(symbol.position.line, symbol.endLine) };
20
20
  }
21
21
 
22
- /** Two strings ordered bytewise. */
23
- function compare(a: string, b: string): number {
24
- if (a === b) {
25
- return 0;
26
- }
27
- return a < b ? -1 : 1;
28
- }
29
-
30
22
  /** One run's declarations and candidates, indexed for the two kinds. */
31
23
  function indexed(input: EmitterInput): {
32
24
  readonly symbolOf: (id: string) => InventorySymbol | undefined;
@@ -49,7 +41,7 @@ function indexed(input: EmitterInput): {
49
41
  .map(symbolOf)
50
42
  .filter((symbol): symbol is InventorySymbol => symbol !== undefined)
51
43
  .map((symbol) => ({ ref: symbol.ref, name: symbol.name, position: positionOf(symbol) }))
52
- .sort((a, b) => byPosition(a.position, b.position) || compare(a.ref, b.ref)),
44
+ .sort((a, b) => byPosition(a.position, b.position)),
53
45
  };
54
46
  }
55
47
 
@@ -52,8 +52,6 @@ interface Judged {
52
52
  readonly symbol: InventorySymbol;
53
53
  /** Whether a test file declares it. */
54
54
  readonly test: boolean;
55
- /** Whether its container is itself dead, so it falls with the container. */
56
- readonly containerDead: boolean;
57
55
  readonly deprecated: boolean;
58
56
  /** Whether the unreachable-export kind reports it. */
59
57
  readonly unreachable: boolean;
@@ -61,21 +59,17 @@ interface Judged {
61
59
 
62
60
  /**
63
61
  * The code one candidate is reported under, or none, each symbol once under the most
64
- * specific code: a test of dead code first; no other test-file declaration, member of a
65
- * dead container or enum member nothing names; a test-support declaration test code
66
- * references as test-only use; then deprecation, a test reference, and the unreferenced
67
- * codes, an unimportable unused export being the unreachable kind's.
62
+ * specific code: a test of dead code first; no other test-file declaration or enum
63
+ * member nothing names; a test-support declaration test code references as test-only
64
+ * use; then deprecation, a test reference, and the unreferenced codes, an unimportable
65
+ * unused export being the unreachable kind's.
68
66
  */
69
67
  function codeOf(judged: Judged): string | undefined {
70
68
  const { candidate, symbol } = judged;
71
69
  if (candidate.testOfDeadCode) {
72
70
  return "DS1005";
73
71
  }
74
- if (
75
- (judged.test && !candidate.unreferencedTest) ||
76
- judged.containerDead ||
77
- NOT_THIS_FAMILY.has(symbol.kind)
78
- ) {
72
+ if ((judged.test && !candidate.unreferencedTest) || NOT_THIS_FAMILY.has(symbol.kind)) {
79
73
  return undefined;
80
74
  }
81
75
  const referenced = candidate.productionRefs + candidate.testRefs > 0;
@@ -233,7 +227,6 @@ function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
233
227
  candidate,
234
228
  symbol,
235
229
  test: union.test[at] === true,
236
- containerDead: containerDead && !fallen,
237
230
  deprecated: input.deprecated.has(symbol.id),
238
231
  unreachable: unreachable.has(symbol.id),
239
232
  };
@@ -131,9 +131,6 @@ function manifestDirectory(host: Host, root: string, dir: string): string {
131
131
  if (host.kindOf(joinPath(at, "package.json")) === "file") {
132
132
  return at;
133
133
  }
134
- if (at === root) {
135
- break;
136
- }
137
134
  }
138
135
  return root;
139
136
  }
@@ -529,7 +529,6 @@ export function projectParts<Brand>(
529
529
  ): ProjectParts {
530
530
  const files = project.ownSourceFiles();
531
531
  const extended = extendedNames(files);
532
- const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
533
532
  const parts: PartFact[] = [];
534
533
  const signatures: SignatureFact[] = [];
535
534
  const calls = new Map<string, CallUse>();
@@ -540,12 +539,8 @@ export function projectParts<Brand>(
540
539
 
541
540
  for (const file of files) {
542
541
  // A parameter property's own parts stay with the parameter when its member goes.
543
- const declarationAt = (node: Node): string | undefined => {
544
- const id = held.declarations.get(nodeKey(file, node));
545
- return id === undefined || kinds.get(id) === "file" || isParameterProperty(node)
546
- ? undefined
547
- : id;
548
- };
542
+ const declarationAt = (node: Node): string | undefined =>
543
+ isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
549
544
  // A part no declaration holds belongs to the file, which is judged while the file is live.
550
545
  const fileId = held.declarations.get(nodeKey(file, file));
551
546
  const holderOf = (node: Node): string | undefined => {
@@ -804,11 +799,7 @@ function valueSignature(node: Node): SignatureNode | undefined {
804
799
  */
805
800
  function parameterNames(signature: SignatureNode): ParameterName[] {
806
801
  const found: ParameterName[] = [];
807
- const patternNames = (pattern: BindingName, at: number): void => {
808
- if (isIdentifier(pattern)) {
809
- found.push({ name: pattern, at, pattern: true, exempt: false });
810
- return;
811
- }
802
+ const patternNames = (pattern: Exclude<BindingName, Identifier>, at: number): void => {
812
803
  const rest =
813
804
  isObjectBindingPattern(pattern) &&
814
805
  pattern.elements.some((element) => element.dotDotDotToken !== undefined);
@@ -1039,10 +1030,7 @@ function deadStores(
1039
1030
  for (const write of writes) {
1040
1031
  // The rest of the write's own statement runs after it: a later declarator may read the local.
1041
1032
  const own = statements[write.at];
1042
- const sameStatement =
1043
- own !== undefined && write.target === local.name
1044
- ? usesIn(own).filter((use) => use !== local.name)
1045
- : [];
1033
+ const sameStatement = own !== undefined && write.target === local.name ? usesIn(own) : [];
1046
1034
  if (sameStatement.length > 0) {
1047
1035
  continue;
1048
1036
  }
package/src/inventory.ts CHANGED
@@ -1042,9 +1042,7 @@ export function inventory<Brand>(
1042
1042
  }
1043
1043
 
1044
1044
  const byId = new Map(building.map((record) => [positionKey(record.position), record]));
1045
- const symbols = building
1046
- .map(render)
1047
- .sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
1045
+ const symbols = building.map(render).sort((a, b) => byPosition(a.position, b.position));
1048
1046
 
1049
1047
  return {
1050
1048
  symbols,
package/src/matrix.ts CHANGED
@@ -94,14 +94,6 @@ export interface SweepResult {
94
94
  readonly components: readonly Component[];
95
95
  }
96
96
 
97
- /** Two strings ordered bytewise. */
98
- function compare(a: string, b: string): number {
99
- if (a === b) {
100
- return 0;
101
- }
102
- return a < b ? -1 : 1;
103
- }
104
-
105
97
  /**
106
98
  * What makes one reference one reference, whichever configuration made it. A consumer's
107
99
  * position is rendered against its own root, so the consumer is part of what it names.
@@ -163,7 +155,7 @@ export function matrixOf(per: readonly Configured[]): Matrix {
163
155
  references.push(reference);
164
156
  }
165
157
  });
166
- symbols.sort((a, b) => byPosition(a.position, b.position) || compare(a.ref, b.ref));
158
+ symbols.sort((a, b) => byPosition(a.position, b.position));
167
159
 
168
160
  return {
169
161
  configurations: per.map((one) => one.configuration),
@@ -113,9 +113,6 @@ interface Named {
113
113
  function namedBy(
114
114
  held: Inventory,
115
115
  ): (specifier: string, names: readonly string[]) => readonly Named[] {
116
- if (held.modules.size === 0) {
117
- return () => [];
118
- }
119
116
  const refs = new Map<string, string>();
120
117
  const children = new Map<string, InventorySymbol[]>();
121
118
  for (const symbol of held.symbols) {
@@ -151,7 +151,8 @@ function declaringManifest(
151
151
  }
152
152
  }
153
153
  }
154
- if (at === targetRoot || relativePath(targetRoot, at) === undefined || dirnamePath(at) === at) {
154
+ // The target root is not below itself, so the walk stops after reading its manifest.
155
+ if (relativePath(targetRoot, at) === undefined) {
155
156
  return undefined;
156
157
  }
157
158
  }
@@ -45,7 +45,6 @@ const DEPENDENCY_MEMBERS = [
45
45
  ] as const;
46
46
 
47
47
  const SOURCE = /(?<!\.d)\.(?:[mc]?ts|tsx)$/u;
48
- const OUTPUT = /(?:\.d\.[mc]?ts|\.[mc]?js|\.jsx)$/u;
49
48
 
50
49
  function isRecord(value: unknown): value is Record<string, unknown> {
51
50
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -70,7 +69,7 @@ export function packageNameOf(specifier: string): string | undefined {
70
69
  }
71
70
 
72
71
  function inside(dir: string, path: string): boolean {
73
- return path === dir || relativePath(dir, path) !== undefined;
72
+ return relativePath(dir, path) !== undefined;
74
73
  }
75
74
 
76
75
  /** The `exports` entry one subpath selects, with what a pattern's `*` matched. */
@@ -274,12 +273,10 @@ export function workspaceResolver(host: Host, workspace: Workspace): WorkspaceRe
274
273
  return found;
275
274
  }
276
275
  }
277
- if (OUTPUT.test(path)) {
278
- const beside = emittedFrom(path, { sourceDirs: [] });
279
- const found = beside.find((candidate) => host.kindOf(candidate) === "file");
280
- if (found !== undefined) {
281
- return found;
282
- }
276
+ const beside = emittedFrom(path, { sourceDirs: [] });
277
+ const found = beside.find((candidate) => host.kindOf(candidate) === "file");
278
+ if (found !== undefined) {
279
+ return found;
283
280
  }
284
281
  return compiled(member, path) && host.kindOf(path) === "file" ? path : undefined;
285
282
  };
package/src/workspace.ts CHANGED
@@ -266,7 +266,7 @@ function memberDirectories(
266
266
  }
267
267
  return [...included].filter((dir) => {
268
268
  const below = relativePath(root, dir);
269
- const segments = below === undefined || below === "." || below === "" ? [] : below.split("/");
269
+ const segments = below === undefined ? [] : below.split("/");
270
270
  return dir === root || !excluded.some((pattern) => pathMatches(pattern, segments));
271
271
  });
272
272
  }
@@ -274,7 +274,7 @@ function memberDirectories(
274
274
  /** Whether a workspace's patterns name one directory below its root, read without listing any. */
275
275
  function namesDirectory(root: string, patterns: readonly string[], dir: string): boolean {
276
276
  const below = relativePath(root, dir);
277
- if (below === undefined || below === "" || below.startsWith("..")) {
277
+ if (below === undefined || below.startsWith("..")) {
278
278
  return false;
279
279
  }
280
280
  const path = below.split("/");