@cplieger/deadset-ts 2.2.1 → 2.3.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.
@@ -0,0 +1,157 @@
1
+ /**
2
+ * The target's own source files of one project, looked up the ways a path written
3
+ * in a document or a string names one: as the file itself, or as a file the
4
+ * compiler emits from it.
5
+ */
6
+
7
+ import type { SourceFile } from "@typescript/native/unstable/ast";
8
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
9
+ import type { ProjectView } from "./session.ts";
10
+
11
+ /** The source extensions the compiler emits each output extension from. */
12
+ const EMITTED: readonly { readonly emitted: string; readonly sources: readonly string[] }[] = [
13
+ { emitted: ".d.ts", sources: [".ts", ".tsx"] },
14
+ { emitted: ".d.mts", sources: [".mts"] },
15
+ { emitted: ".d.cts", sources: [".cts"] },
16
+ { emitted: ".js", sources: [".ts", ".tsx"] },
17
+ { emitted: ".jsx", sources: [".tsx"] },
18
+ { emitted: ".mjs", sources: [".mts"] },
19
+ { emitted: ".cjs", sources: [".cts"] },
20
+ ];
21
+
22
+ /** The one character a subpath pattern of a manifest's `exports` holds. */
23
+ const SUBPATH_WILDCARD = "*";
24
+
25
+ /** One path with its extension replaced, where it carries the one named. */
26
+ function reExtended(path: string, emitted: string, source: string): string | undefined {
27
+ return path.endsWith(emitted) ? path.slice(0, path.length - emitted.length) + source : undefined;
28
+ }
29
+
30
+ /**
31
+ * The longest directory every path shares, which is the directory the compiler
32
+ * emits from where the configuration declares no root directory.
33
+ */
34
+ function commonDirectory(paths: readonly string[]): string {
35
+ const first = paths[0];
36
+ if (first === undefined) {
37
+ return "";
38
+ }
39
+ let common = dirnamePath(first).split("/");
40
+ for (const path of paths.slice(1)) {
41
+ const segments = dirnamePath(path).split("/");
42
+ let shared = 0;
43
+ while (
44
+ shared < common.length &&
45
+ shared < segments.length &&
46
+ common[shared] === segments[shared]
47
+ ) {
48
+ shared += 1;
49
+ }
50
+ common = common.slice(0, shared);
51
+ }
52
+ return common.join("/");
53
+ }
54
+
55
+ /**
56
+ * Whether one path is what a subpath pattern stands for: the text before the
57
+ * wildcard, any run of characters including the solidus, then the text after it.
58
+ */
59
+ function matchesSubpath(pattern: string, path: string): boolean {
60
+ const at = pattern.indexOf(SUBPATH_WILDCARD);
61
+ const before = pattern.slice(0, at);
62
+ const after = pattern.slice(at + SUBPATH_WILDCARD.length);
63
+ return (
64
+ path.length >= before.length + after.length && path.startsWith(before) && path.endsWith(after)
65
+ );
66
+ }
67
+
68
+ /** One project's own source files, and the lookups a written path is resolved through. */
69
+ export interface SourceFiles {
70
+ /** Every own file, by its path below the target root, in the order the program holds them. */
71
+ readonly byPath: ReadonlyMap<string, SourceFile>;
72
+ /** Every own file, by the absolute path the program names it by. */
73
+ readonly byName: ReadonlyMap<string, SourceFile>;
74
+ /**
75
+ * The own files one absolute path names: the file at that path, or the source the
76
+ * project would emit a file at that path from. A path holding the wildcard of a
77
+ * subpath pattern names every file it stands for. A path no rule reaches names
78
+ * nothing.
79
+ */
80
+ named(path: string): readonly SourceFile[];
81
+ }
82
+
83
+ /**
84
+ * The lookups over one project's own files.
85
+ *
86
+ * A path that names an emitted file is read back to the source the project would
87
+ * emit it from through the project's own emit mapping and nothing else: the
88
+ * extension is one the compiler emits from a source extension, and a path below the
89
+ * output directory is read below the directory the compiler emits from, which the
90
+ * configuration declares or which is the directory every input file shares.
91
+ */
92
+ export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: string): SourceFiles {
93
+ const byName = new Map<string, SourceFile>();
94
+ const byPath = new Map<string, SourceFile>();
95
+ for (const file of project.ownSourceFiles()) {
96
+ byName.set(file.fileName, file);
97
+ const path = relativePath(targetRoot, file.fileName);
98
+ if (path !== undefined) {
99
+ byPath.set(path, file);
100
+ }
101
+ }
102
+
103
+ const options = project.program.getCompilerOptions();
104
+ const configDir = dirnamePath(project.configFile);
105
+ const at = (option: string | undefined): string | undefined =>
106
+ option === undefined ? undefined : resolvePath(configDir, option);
107
+ const outDir = at(options.outDir);
108
+ const declarationDir = at(options.declarationDir);
109
+ const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
110
+
111
+ /** One path read below the directory the compiler emits from, where it is below the one it emits to. */
112
+ const fromEmit = (path: string, emitted: string): string | undefined => {
113
+ const emitDir = emitted.startsWith(".d.") ? (declarationDir ?? outDir) : outDir;
114
+ if (emitDir === undefined) {
115
+ return undefined;
116
+ }
117
+ const below = relativePath(emitDir, path);
118
+ return below === undefined ? undefined : joinPath(sourceDir, below);
119
+ };
120
+
121
+ /** The source paths one written path may stand for, the path itself first. */
122
+ const candidates = (path: string): readonly string[] => {
123
+ const found = [path];
124
+ for (const { emitted, sources } of EMITTED) {
125
+ const mapped = fromEmit(path, emitted);
126
+ for (const source of sources) {
127
+ for (const from of [path, mapped]) {
128
+ const candidate = from === undefined ? undefined : reExtended(from, emitted, source);
129
+ if (candidate !== undefined) {
130
+ found.push(candidate);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ return found;
136
+ };
137
+
138
+ return {
139
+ byPath,
140
+ byName,
141
+ named: (path) => {
142
+ if (!path.includes(SUBPATH_WILDCARD)) {
143
+ for (const candidate of candidates(path)) {
144
+ const held = byName.get(candidate);
145
+ if (held !== undefined) {
146
+ return [held];
147
+ }
148
+ }
149
+ return [];
150
+ }
151
+ const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
152
+ return [...byName.values()].filter((file) =>
153
+ patterns.some((pattern) => matchesSubpath(pattern, file.fileName)),
154
+ );
155
+ },
156
+ };
157
+ }