@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1

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 (153) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +116 -16
  3. package/dist/src/augmentations.js +21 -8
  4. package/dist/src/automatic-types.js +189 -0
  5. package/dist/src/base-url.js +18 -0
  6. package/dist/src/component-files.js +96 -31
  7. package/dist/src/components.js +7 -6
  8. package/dist/src/config.js +1 -0
  9. package/dist/src/configuration-files.js +54 -15
  10. package/dist/src/configured-declarations.js +24 -2
  11. package/dist/src/conformance-record.js +2 -2
  12. package/dist/src/contextual-uses.js +26 -26
  13. package/dist/src/convention-rows.js +75 -0
  14. package/dist/src/conventions.js +101 -5
  15. package/dist/src/decorator.js +21 -18
  16. package/dist/src/dependencies.js +185 -30
  17. package/dist/src/destructuring.js +77 -2
  18. package/dist/src/discover.js +188 -85
  19. package/dist/src/edges.js +1 -1
  20. package/dist/src/entry-point-gaps.js +7 -10
  21. package/dist/src/entry-points.js +112 -11
  22. package/dist/src/findings/completion.js +116 -21
  23. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/intra-function.js +44 -13
  26. package/dist/src/findings/non-code-artifacts.js +66 -33
  27. package/dist/src/findings/unused-declarations.js +21 -1
  28. package/dist/src/findings/visibility-narrowing.js +22 -3
  29. package/dist/src/generated-file.js +11 -0
  30. package/dist/src/git-ignore.js +86 -0
  31. package/dist/src/graph.js +4 -1
  32. package/dist/src/import-bindings.js +97 -0
  33. package/dist/src/indexed-access.js +130 -28
  34. package/dist/src/interface-satisfaction.js +30 -12
  35. package/dist/src/intra-function-parts.js +313 -50
  36. package/dist/src/inventory.js +63 -8
  37. package/dist/src/json-document.js +5 -5
  38. package/dist/src/kinds.js +13 -13
  39. package/dist/src/matrix.js +18 -9
  40. package/dist/src/mdx-entries.js +197 -0
  41. package/dist/src/module-declarations.js +9 -13
  42. package/dist/src/namespace-objects.js +28 -18
  43. package/dist/src/overrides.js +45 -9
  44. package/dist/src/paths.js +9 -0
  45. package/dist/src/print.js +1 -0
  46. package/dist/src/ref.js +10 -0
  47. package/dist/src/references.js +29 -13
  48. package/dist/src/reflective-lookup.js +8 -6
  49. package/dist/src/report.js +4 -2
  50. package/dist/src/reporters/template.js +22 -13
  51. package/dist/src/resolve.js +2 -0
  52. package/dist/src/roots.js +110 -38
  53. package/dist/src/schema.js +1 -0
  54. package/dist/src/serialization-contract.js +97 -20
  55. package/dist/src/session.js +26 -1
  56. package/dist/src/source-files.js +39 -14
  57. package/dist/src/spelled-names.js +58 -0
  58. package/dist/src/suppress-file.js +12 -4
  59. package/dist/src/suppress-inline.js +21 -3
  60. package/dist/src/suppress.js +7 -5
  61. package/dist/src/sweep.js +27 -10
  62. package/dist/src/symbol-keys.js +103 -2
  63. package/dist/src/template-field.js +9 -6
  64. package/dist/src/test-support.js +121 -17
  65. package/dist/src/type-errors.js +116 -35
  66. package/dist/src/type-parameter-reads.js +172 -0
  67. package/dist/src/value-reach.js +17 -5
  68. package/dist/src/verbs/analyze.js +19 -1
  69. package/dist/src/verbs/explain.js +4 -2
  70. package/dist/src/version-range.js +10 -5
  71. package/dist/src/version.js +1 -1
  72. package/dist/src/workflow-steps.js +119 -22
  73. package/dist/src/workspace-programs.js +7 -3
  74. package/dist/src/workspace-resolution.js +72 -13
  75. package/dist/src/workspace.js +1 -1
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/analysis.ts +175 -16
  79. package/src/augmentations.ts +21 -10
  80. package/src/automatic-types.ts +246 -0
  81. package/src/base-url.ts +24 -0
  82. package/src/component-files.ts +116 -31
  83. package/src/components.ts +7 -6
  84. package/src/config.ts +3 -0
  85. package/src/configuration-files.ts +80 -18
  86. package/src/configured-declarations.ts +33 -4
  87. package/src/conformance-record.ts +2 -2
  88. package/src/contextual-uses.ts +27 -27
  89. package/src/convention-rows.ts +103 -0
  90. package/src/conventions.ts +121 -7
  91. package/src/decorator.ts +28 -18
  92. package/src/dependencies.ts +249 -43
  93. package/src/destructuring.ts +93 -2
  94. package/src/discover.ts +234 -86
  95. package/src/edges.ts +1 -1
  96. package/src/entry-point-gaps.ts +8 -11
  97. package/src/entry-points.ts +133 -11
  98. package/src/finding.ts +10 -0
  99. package/src/findings/completion.ts +147 -24
  100. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  101. package/src/findings/emitters.ts +1 -1
  102. package/src/findings/intra-function.ts +56 -14
  103. package/src/findings/non-code-artifacts.ts +81 -41
  104. package/src/findings/unused-declarations.ts +25 -1
  105. package/src/findings/visibility-narrowing.ts +32 -3
  106. package/src/generated-file.ts +16 -0
  107. package/src/git-ignore.ts +98 -0
  108. package/src/graph.ts +6 -0
  109. package/src/host.ts +5 -9
  110. package/src/import-bindings.ts +128 -0
  111. package/src/indexed-access.ts +152 -30
  112. package/src/interface-satisfaction.ts +33 -13
  113. package/src/intra-function-parts.ts +415 -64
  114. package/src/inventory.ts +83 -7
  115. package/src/json-document.ts +7 -7
  116. package/src/kinds.ts +13 -13
  117. package/src/matrix.ts +32 -14
  118. package/src/mdx-entries.ts +241 -0
  119. package/src/module-declarations.ts +11 -13
  120. package/src/namespace-objects.ts +34 -21
  121. package/src/overrides.ts +56 -9
  122. package/src/paths.ts +10 -0
  123. package/src/print.ts +1 -0
  124. package/src/ref.ts +12 -0
  125. package/src/references.ts +38 -17
  126. package/src/reflective-lookup.ts +9 -7
  127. package/src/report.ts +16 -3
  128. package/src/reporters/template.ts +21 -13
  129. package/src/resolve.ts +3 -0
  130. package/src/roots.ts +163 -45
  131. package/src/schema.ts +1 -0
  132. package/src/serialization-contract.ts +108 -17
  133. package/src/session.ts +29 -1
  134. package/src/source-files.ts +66 -16
  135. package/src/spelled-names.ts +102 -0
  136. package/src/suppress-file.ts +14 -3
  137. package/src/suppress-inline.ts +21 -2
  138. package/src/suppress.ts +9 -5
  139. package/src/sweep.ts +32 -11
  140. package/src/symbol-keys.ts +129 -2
  141. package/src/template-field.ts +9 -6
  142. package/src/test-support.ts +149 -16
  143. package/src/type-errors.ts +132 -41
  144. package/src/type-parameter-reads.ts +227 -0
  145. package/src/value-reach.ts +19 -6
  146. package/src/verbs/analyze.ts +28 -1
  147. package/src/verbs/explain.ts +5 -2
  148. package/src/version-range.ts +10 -5
  149. package/src/version.ts +1 -1
  150. package/src/workflow-steps.ts +127 -22
  151. package/src/workspace-programs.ts +7 -3
  152. package/src/workspace-resolution.ts +86 -14
  153. package/src/workspace.ts +1 -1
@@ -1,18 +1,33 @@
1
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.
2
+ * The source files a CI workflow's `run:` steps and the repository's git hooks name by their
3
+ * path from the target root. A workflow or hook file is read as text, line by line, with no
4
+ * YAML parsed.
4
5
  */
5
6
  import { scriptTokens } from "./manifest.js";
6
- import { joinPath, resolvePath } from "./paths.js";
7
+ import { joinPath, relativePath, resolvePath } from "./paths.js";
7
8
  const WORKFLOWS = ".github/workflows";
8
9
  /** A line holding a run command: indentation and one sequence marker before `run:`. */
9
10
  const RUN_LINE = /^(\s*(?:- )?)run:(.*)$/u;
11
+ /** The directories whose every file is a git hook script, run as the shell runs it. */
12
+ const HOOK_DIRS = [".husky", ".githooks"];
13
+ /** The hook manager files whose commands are written under a key, with that key. */
14
+ const HOOK_MANAGERS = [
15
+ [".pre-commit-config.yaml", /^(\s*(?:- )?)entry:(.*)$/u],
16
+ ["lefthook.yml", RUN_LINE],
17
+ ["lefthook.yaml", RUN_LINE],
18
+ [".lefthook.yml", RUN_LINE],
19
+ ];
20
+ /** The names GNU make reads a makefile under, in the order it tries them. */
21
+ const MAKEFILES = ["GNUmakefile", "makefile", "Makefile"];
22
+ /** The operators that end one shell command of a recipe line. */
23
+ const COMMAND_SEPARATORS = /&&|\|\||;|\|/u;
10
24
  const BLOCK_SCALAR = /^[|>][+-]?$/u;
11
25
  const QUOTED = /^(["'])(.*)\1$/su;
12
26
  /** A token holding one of these is computed or a pattern, so it names no file. */
13
27
  const COMPUTED = /[${*?[]/u;
14
28
  const INDENT = /^\s*/u;
15
- function workflowFiles(host, dir) {
29
+ /** The files directly in one directory whose name `accepts`, in name order. */
30
+ function filesIn(host, dir, accepts) {
16
31
  let entries;
17
32
  try {
18
33
  entries = host.readDirectory(dir);
@@ -21,16 +36,24 @@ function workflowFiles(host, dir) {
21
36
  return [];
22
37
  }
23
38
  return entries
24
- .filter((entry) => !entry.directory && /\.ya?ml$/u.test(entry.name))
39
+ .filter((entry) => !entry.directory && accepts(entry.name))
25
40
  .map((entry) => joinPath(dir, entry.name))
26
41
  .sort();
27
42
  }
28
- /** Every command line one workflow file's `run:` steps hold. */
29
- export function runCommands(text) {
43
+ function readText(host, path) {
44
+ try {
45
+ return host.readFile(path);
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
51
+ /** Every command line one workflow file's `run:` steps, or the steps under `key`, hold. */
52
+ export function runCommands(text, key = RUN_LINE) {
30
53
  const lines = text.split(/\r?\n/u);
31
54
  const found = [];
32
55
  for (let at = 0; at < lines.length; at += 1) {
33
- const match = RUN_LINE.exec(lines[at] ?? "");
56
+ const match = key.exec(lines[at] ?? "");
34
57
  if (match === null) {
35
58
  continue;
36
59
  }
@@ -50,28 +73,92 @@ export function runCommands(text) {
50
73
  }
51
74
  return found;
52
75
  }
53
- /** Every token the `run:` steps of the workflow files in the target root's `.github/workflows` hold. */
54
- export function workflowTokens(host, targetRoot) {
55
- const dir = joinPath(resolvePath(host.workingDirectory(), targetRoot), WORKFLOWS);
76
+ /**
77
+ * Every token the recipe lines of the target root's makefile hold, each with the directory
78
+ * below the root it is read against: a recipe line is one that starts with a tab, joined
79
+ * with the lines a trailing `\` continues it onto, and make runs each in a shell of its
80
+ * own, so a `cd` moves the directory of the later commands of its line alone.
81
+ */
82
+ function recipeTokens(host, root) {
83
+ const path = MAKEFILES.map((name) => joinPath(root, name)).find((candidate) => host.kindOf(candidate) === "file");
84
+ const text = path === undefined ? undefined : readText(host, path);
85
+ if (text === undefined) {
86
+ return [];
87
+ }
56
88
  const found = [];
57
- for (const path of workflowFiles(host, dir)) {
58
- let text;
59
- try {
60
- text = host.readFile(path);
61
- }
62
- catch {
89
+ for (const line of text.replace(/\\\r?\n/gu, " ").split(/\r?\n/u)) {
90
+ if (!line.startsWith("\t")) {
63
91
  continue;
64
92
  }
65
- for (const command of runCommands(text)) {
66
- found.push(...scriptTokens(command));
93
+ let dir = root;
94
+ for (const command of withoutComment(line.slice(1).replace(/^[@+-]+/u, "")).split(COMMAND_SEPARATORS)) {
95
+ const tokens = scriptTokens(command);
96
+ const [first, target, ...rest] = tokens;
97
+ if (first === "cd" && target !== undefined && rest.length === 0 && !COMPUTED.test(target)) {
98
+ dir = resolvePath(dir, target);
99
+ continue;
100
+ }
101
+ for (const token of tokens) {
102
+ found.push({ token, dir: relativePath(root, dir) ?? "" });
103
+ }
67
104
  }
68
105
  }
69
106
  return found;
70
107
  }
108
+ /** One shell line up to its comment: a `#` that starts a word outside quotes. */
109
+ function withoutComment(line) {
110
+ let quote = "";
111
+ for (let at = 0; at < line.length; at += 1) {
112
+ const char = line[at];
113
+ if (quote !== "") {
114
+ if (char === quote) {
115
+ quote = "";
116
+ }
117
+ else if (char === "\\" && quote === '"') {
118
+ at += 1;
119
+ }
120
+ }
121
+ else if (char === "'" || char === '"') {
122
+ quote = char;
123
+ }
124
+ else if (char === "\\") {
125
+ at += 1;
126
+ }
127
+ else if (char === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
128
+ return line.slice(0, at);
129
+ }
130
+ }
131
+ return line;
132
+ }
133
+ /**
134
+ * Every token the target root's workflow steps, git hooks and makefile hold: the `run:`
135
+ * steps of the workflow files in `.github/workflows`, every line of each file in `.husky`
136
+ * and `.githooks`, the `entry:` commands of `.pre-commit-config.yaml`, the `run:` commands
137
+ * of a lefthook file and the makefile's recipe lines.
138
+ */
139
+ export function workflowTokens(host, targetRoot) {
140
+ const root = resolvePath(host.workingDirectory(), targetRoot);
141
+ const commands = [];
142
+ for (const path of filesIn(host, joinPath(root, WORKFLOWS), (name) => /\.ya?ml$/u.test(name))) {
143
+ commands.push(...runCommands(readText(host, path) ?? ""));
144
+ }
145
+ for (const dir of HOOK_DIRS) {
146
+ for (const path of filesIn(host, joinPath(root, dir), () => true)) {
147
+ commands.push(...(readText(host, path) ?? "").split(/\r?\n/u).map(withoutComment));
148
+ }
149
+ }
150
+ for (const [name, key] of HOOK_MANAGERS) {
151
+ commands.push(...runCommands(readText(host, joinPath(root, name)) ?? "", key));
152
+ }
153
+ return [
154
+ ...commands.flatMap((command) => scriptTokens(command)),
155
+ ...recipeTokens(host, root).map(({ token }) => token),
156
+ ];
157
+ }
71
158
  /**
72
- * The own files the `run:` steps of every workflow file in the target root's
73
- * `.github/workflows` name: a token equal to a file's path below the target root, with or
74
- * without a leading `./`, whatever directory the step runs in.
159
+ * The own files the target root's workflow steps and git hooks name, as
160
+ * {@link workflowTokens} reads them: a token equal to a file's path below the target root,
161
+ * with or without a leading `./`, whatever directory the step runs in.
75
162
  */
76
163
  export function workflowEntries(host, targetRoot, files) {
77
164
  const found = [];
@@ -84,5 +171,15 @@ export function workflowEntries(host, targetRoot, files) {
84
171
  found.push({ file, source: token });
85
172
  }
86
173
  }
174
+ const root = resolvePath(host.workingDirectory(), targetRoot);
175
+ for (const { token, dir } of recipeTokens(host, root)) {
176
+ const below = COMPUTED.test(token) || dir === ""
177
+ ? undefined
178
+ : relativePath(root, resolvePath(joinPath(root, dir), token));
179
+ const file = below === undefined ? undefined : files.byPath.get(below);
180
+ if (file !== undefined) {
181
+ found.push({ file, source: token });
182
+ }
183
+ }
87
184
  return found;
88
185
  }
@@ -7,18 +7,22 @@
7
7
  */
8
8
  import { ModuleKind, ModuleResolutionKind } from "@typescript/native/sync";
9
9
  import { isStringLiteralLikeNode } from "@typescript/native/ast";
10
+ import { baseUrlPaths } from "./base-url.js";
10
11
  import { dirnamePath, resolvePath } from "./paths.js";
11
12
  /** The most questions one round trip carries. */
12
13
  const BATCH = 2048;
13
14
  /**
14
15
  * The options a configuration's program is built with: its own, less the ones that
15
- * lay out an emitted program's inputs and outputs. The analysis emits nothing, and a
16
+ * lay out an emitted program's inputs and outputs, and with `paths` read against the
17
+ * effective `baseUrl` as {@link baseUrlPaths} states. The analysis emits nothing, and a
16
18
  * member's source is outside the importer's root directory by construction, which
17
19
  * the compiler refuses under such a layout.
18
20
  */
19
21
  function analysisOptions(parsed) {
20
22
  const { composite, rootDir: _rootDir, outDir: _outDir, declarationDir: _declarationDir, tsBuildInfoFile: _tsBuildInfoFile, ...kept } = parsed.options;
21
- return composite === true ? { ...kept, declaration: true } : kept;
23
+ const paths = baseUrlPaths(parsed.options);
24
+ const options = paths === undefined ? kept : { ...kept, paths };
25
+ return composite === true ? { ...options, declaration: true } : options;
22
26
  }
23
27
  /** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
24
28
  function conditionsOf(parsed, mode) {
@@ -73,7 +77,7 @@ function configurationOf(engine, host, reading) {
73
77
  const raw = options["pathsBasePath"];
74
78
  const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
75
79
  const paths = {};
76
- for (const [key, targets] of Object.entries(options.paths ?? {})) {
80
+ for (const [key, targets] of Object.entries(baseUrlPaths(options) ?? options.paths ?? {})) {
77
81
  paths[key] = targets.map((target) => resolvePath(base, target));
78
82
  }
79
83
  for (const entry of reading.entries) {
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Which file an import of a workspace package's name is read from. The package is the
3
- * one `node_modules` links the importer to or, with no link, the one the importer's
4
- * manifest depends on. A source file the compiler reaches stands; any other file is read
5
- * back to the source it is compiled from or to a manifest target that is source, a file
6
- * the package's configurations neither compile nor write is read as installed, and an
7
- * import nothing answers for is unresolved.
2
+ * Which file an import of a workspace package's name is read from: the package
3
+ * `node_modules` links the importer to or, with no link, the one its manifest depends on.
4
+ * A source file the compiler reaches stands; any other file reads back to its source or to
5
+ * a manifest target that is source, an ignored file to the source its path mirrors, a file
6
+ * the package neither compiles nor writes is read as installed, and the rest is unresolved.
8
7
  */
9
8
  import { emittedFrom } from "./emit-map.js";
9
+ import { gitIgnored } from "./git-ignore.js";
10
10
  import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
11
11
  import { satisfies } from "./version-range.js";
12
12
  /** The manifest members that name an entry point for the subpath `.` without `exports`. */
@@ -19,6 +19,7 @@ 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;
22
23
  function isRecord(value) {
23
24
  return typeof value === "object" && value !== null && !Array.isArray(value);
24
25
  }
@@ -42,9 +43,17 @@ function inside(dir, path) {
42
43
  }
43
44
  /** The `exports` entry one subpath selects, with what a pattern's `*` matched. */
44
45
  function exportsEntry(exportsField, subpath) {
45
- const map = isRecord(exportsField) && Object.keys(exportsField).some((key) => key.startsWith("."))
46
- ? exportsField
47
- : { ".": exportsField };
46
+ const keys = isRecord(exportsField) ? Object.keys(exportsField) : [];
47
+ const dotted = keys.filter((key) => key.startsWith(".")).length;
48
+ // An object mixing subpath keys with others is invalid to Node; the compiler still
49
+ // answers `.` from its `"."` key and no other subpath:
50
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L673-L695
51
+ if (isRecord(exportsField) && dotted > 0 && dotted < keys.length) {
52
+ return subpath === "." && Object.hasOwn(exportsField, ".")
53
+ ? { value: exportsField["."] }
54
+ : undefined;
55
+ }
56
+ const map = isRecord(exportsField) && dotted > 0 ? exportsField : { ".": exportsField };
48
57
  if (Object.hasOwn(map, subpath)) {
49
58
  return { value: map[subpath] };
50
59
  }
@@ -154,6 +163,27 @@ export function workspaceResolver(host, workspace) {
154
163
  links.set(key, found);
155
164
  return found;
156
165
  };
166
+ const root = workspace.members[0]?.dir;
167
+ const ignored = root === undefined ? () => false : gitIgnored(host, root);
168
+ const manifests = new Map();
169
+ /** The nearest manifest at or above one directory, up to the workspace's root. */
170
+ const nearestManifest = (dir) => {
171
+ if (manifests.has(dir)) {
172
+ return manifests.get(dir);
173
+ }
174
+ let found;
175
+ try {
176
+ const value = JSON.parse(host.readFile(joinPath(dir, "package.json")));
177
+ found = isRecord(value) ? { dir, manifest: value } : undefined;
178
+ }
179
+ catch {
180
+ const up = dirnamePath(dir);
181
+ found =
182
+ up === dir || root === undefined || !inside(root, up) ? undefined : nearestManifest(up);
183
+ }
184
+ manifests.set(dir, found);
185
+ return found;
186
+ };
157
187
  const linkOf = (candidates, fromDir) => {
158
188
  const name = candidates[0]?.name ?? "";
159
189
  const linked = linkedFrom(name, fromDir);
@@ -165,13 +195,15 @@ export function workspaceResolver(host, workspace) {
165
195
  if (scope?.name === name) {
166
196
  return { kind: "member", member: scope };
167
197
  }
168
- const manifest = scope?.manifest;
198
+ // A package below the root that no workspace lists depends on a member as a member does.
199
+ const owner = scope ?? nearestManifest(fromDir);
200
+ const manifest = owner?.manifest;
169
201
  const declared = DEPENDENCY_MEMBERS.flatMap((field) => {
170
202
  const held = manifest?.[field];
171
203
  const specifier = isRecord(held) ? held[name] : undefined;
172
204
  return typeof specifier === "string" ? [specifier] : [];
173
205
  });
174
- const matched = candidates.filter((member) => declared.some((specifier) => dependsOn(specifier, member, scope?.dir ?? fromDir)));
206
+ const matched = candidates.filter((member) => declared.some((specifier) => dependsOn(specifier, member, owner?.dir ?? fromDir)));
175
207
  const [one, two] = matched;
176
208
  if (one === undefined) {
177
209
  return { kind: "none" };
@@ -182,6 +214,21 @@ export function workspaceResolver(host, workspace) {
182
214
  };
183
215
  /** The files a member's configurations compile, which is what makes a file there source. */
184
216
  const compiled = (member, path) => member.configurations.some((configuration) => configuration.fileNames.has(path));
217
+ /**
218
+ * The member's source an ignored output path mirrors: the path below one of its
219
+ * directories, the deepest first, read below the member's own directory as an output
220
+ * directory's tree mirrors its root directory's, which a build leaves in an ignored
221
+ * directory however its configuration writes it.
222
+ */
223
+ const mirrored = (member, path) => {
224
+ for (let dir = dirnamePath(path); inside(member.dir, dir); dir = dirnamePath(dir)) {
225
+ const found = emittedFrom(path, { outDir: dir, sourceDirs: [member.dir] }).find((candidate) => SOURCE.test(candidate) && !ignored(candidate) && host.kindOf(candidate) === "file");
226
+ if (found !== undefined) {
227
+ return found;
228
+ }
229
+ }
230
+ return undefined;
231
+ };
185
232
  /**
186
233
  * The member's source one of its paths is, or is compiled from: a TypeScript source
187
234
  * file, the source an output path maps back to, or a file one of the member's
@@ -203,6 +250,9 @@ export function workspaceResolver(host, workspace) {
203
250
  if (found !== undefined) {
204
251
  return found;
205
252
  }
253
+ if (ignored(path)) {
254
+ return mirrored(member, path);
255
+ }
206
256
  return compiled(member, path) && host.kindOf(path) === "file" ? path : undefined;
207
257
  };
208
258
  /** The targets a member's manifest names for one subpath, the selected one first. */
@@ -261,9 +311,11 @@ export function workspaceResolver(host, workspace) {
261
311
  }
262
312
  const { member } = linked;
263
313
  // A member no compiler configuration builds is read as the package manager
264
- // installed it, wherever the compiler's resolution found it.
314
+ // installed it, wherever the compiler's resolution found it, unless what it found is
315
+ // a file the tree ignores, which no install wrote.
265
316
  if (answered !== undefined &&
266
- (member.configurations.length === 0 || !inside(member.dir, answered.resolvedFileName))) {
317
+ ((member.configurations.length === 0 && !ignored(answered.resolvedFileName)) ||
318
+ !inside(member.dir, answered.resolvedFileName))) {
267
319
  return { kind: "default" };
268
320
  }
269
321
  if (answered !== undefined) {
@@ -286,6 +338,13 @@ export function workspaceResolver(host, workspace) {
286
338
  if (answered !== undefined) {
287
339
  return { kind: "default" };
288
340
  }
341
+ // A stylesheet or another asset is no module with a source, so the import of one
342
+ // keeps the compiler's resolution.
343
+ if (targets.some((target) => !SOURCE.test(target) &&
344
+ !OUTPUT.test(target) &&
345
+ host.kindOf(joinPath(member.dir, target)) === "file")) {
346
+ return { kind: "default" };
347
+ }
289
348
  const named = targets.length === 0 ? "no target" : targets.join(", ");
290
349
  const built = targets.some((target) => host.kindOf(joinPath(member.dir, target)) === "file");
291
350
  return {
@@ -205,7 +205,7 @@ function memberDirectories(host, root, patterns) {
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.startsWith("..")) {
208
+ if (below === undefined) {
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.3",
3
+ "version": "8.0.0-dev.1",
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.3",
3
+ "version": "8.0.0-dev.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Deterministic dead-code analysis for TypeScript, class and type members included",
@@ -43,7 +43,7 @@
43
43
  "@types/node": "24.19.2",
44
44
  "eslint": "10.12.0",
45
45
  "fast-check": "4.10.2",
46
- "prettier": "3.9.9",
46
+ "prettier": "3.9.10",
47
47
  "typescript": "npm:@typescript/typescript6@6.0.2",
48
48
  "typescript-eslint": "8.71.1",
49
49
  "vitest": "5.0.4"