@goodbones/typescript 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/build/dts/extractor.d.ts +4 -0
- package/build/dts/extractor.d.ts.map +1 -0
- package/build/dts/index.d.ts +7 -0
- package/build/dts/index.d.ts.map +1 -0
- package/build/dts/npm-package.d.ts +2 -0
- package/build/dts/npm-package.d.ts.map +1 -0
- package/build/dts/options.d.ts +12 -0
- package/build/dts/options.d.ts.map +1 -0
- package/build/dts/resolver.d.ts +5 -0
- package/build/dts/resolver.d.ts.map +1 -0
- package/build/esm/extractor.js +263 -0
- package/build/esm/extractor.js.map +1 -0
- package/build/esm/index.js +21 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/npm-package.js +16 -0
- package/build/esm/npm-package.js.map +1 -0
- package/build/esm/options.js +26 -0
- package/build/esm/options.js.map +1 -0
- package/build/esm/resolver.js +98 -0
- package/build/esm/resolver.js.map +1 -0
- package/package.json +60 -0
- package/src/extractor.ts +282 -0
- package/src/index.ts +24 -0
- package/src/npm-package.ts +15 -0
- package/src/options.ts +37 -0
- package/src/resolver.ts +140 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Data Quail
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EAEb,WAAW,EACZ,MAAM,iBAAiB,CAAC;AA4FzB,eAAO,MAAM,WAAW,SAAU,MAAM,QAAQ,MAAM,KAAG,WAoFxD,CAAC;AAkGF,eAAO,MAAM,qBAAqB,QAAO,aAA2C,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Language } from "@goodbones/core";
|
|
2
|
+
export { factsOfText, makeFactExtractorLive } from "./extractor.js";
|
|
3
|
+
export { npmPackageOf } from "./npm-package.js";
|
|
4
|
+
export { decodeTypescriptScopeOptions, type TypescriptScopeOptions } from "./options.js";
|
|
5
|
+
export { makeModuleResolverLive, TYPESCRIPT } from "./resolver.js";
|
|
6
|
+
export declare const typescriptLanguage: () => Language;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAKhD,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAAE,KAAK,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAMnE,eAAO,MAAM,kBAAkB,QAAO,QASpC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-package.d.ts","sourceRoot":"","sources":["../../src/npm-package.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,qBAAsB,MAAM,KAAG,MAAM,GAAG,SAIhE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ResolveScope, ScopeInvalid } from "@goodbones/core";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
export declare const TypescriptScopeOptions: Schema.Struct<{
|
|
5
|
+
readonly tsconfig: Schema.String;
|
|
6
|
+
readonly extensions: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
7
|
+
readonly conditionNames: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
8
|
+
readonly mainFields: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
9
|
+
}>;
|
|
10
|
+
export type TypescriptScopeOptions = typeof TypescriptScopeOptions.Type;
|
|
11
|
+
export declare const decodeTypescriptScopeOptions: (scope: ResolveScope) => Result.Result<TypescriptScopeOptions, ScopeInvalid>;
|
|
12
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAKxC,eAAO,MAAM,sBAAsB;;;;;EASjC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,OAAO,sBAAsB,CAAC,IAAI,CAAC;AAOxE,eAAO,MAAM,4BAA4B,UAChC,YAAY,KAClB,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE,YAAY,CASlD,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ModuleResolver, type ResolveConfig, ScopeInvalid } from "@goodbones/core";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
export declare const TYPESCRIPT = "typescript";
|
|
4
|
+
export declare const makeModuleResolverLive: (repoRoot: string, config: ResolveConfig) => Result.Result<ModuleResolver, ScopeInvalid>;
|
|
5
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EAGlB,YAAY,EACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAmBxC,eAAO,MAAM,UAAU,eAAe,CAAC;AA+CvC,eAAO,MAAM,sBAAsB,aACvB,MAAM,UACR,aAAa,KACpB,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,YAAY,CA4D5C,CAAC"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
// The facts, read out of TypeScript's syntax tree. The plugin reads the same
|
|
3
|
+
// facts out of oxlint's; `src/adapters/parity.test.ts` holds the two to one
|
|
4
|
+
// answer, so a form added here without the matching visitor there fails a test
|
|
5
|
+
// rather than a user.
|
|
6
|
+
const scriptKindOf = (file) => file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
7
|
+
const nameOf = (node) => {
|
|
8
|
+
if (node === undefined)
|
|
9
|
+
return null;
|
|
10
|
+
if (ts.isIdentifier(node))
|
|
11
|
+
return node.text;
|
|
12
|
+
if (ts.isStringLiteral(node))
|
|
13
|
+
return node.text;
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
const bindingsOfImportClause = (clause) => {
|
|
17
|
+
if (clause === undefined)
|
|
18
|
+
return [];
|
|
19
|
+
const found = [];
|
|
20
|
+
if (clause.name !== undefined)
|
|
21
|
+
found.push({ symbol: "default", kind: "default" });
|
|
22
|
+
const bindings = clause.namedBindings;
|
|
23
|
+
if (bindings !== undefined) {
|
|
24
|
+
if (ts.isNamespaceImport(bindings))
|
|
25
|
+
found.push({ symbol: "*", kind: "namespace" });
|
|
26
|
+
else {
|
|
27
|
+
for (const element of bindings.elements) {
|
|
28
|
+
const symbol = nameOf(element.propertyName ?? element.name);
|
|
29
|
+
if (symbol !== null)
|
|
30
|
+
found.push({ symbol, kind: "named" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return found;
|
|
35
|
+
};
|
|
36
|
+
// The whole module, as one binding. `export * from "m"`, `export * as ns from
|
|
37
|
+
// "m"`, `import x = require("m")`, `import("m")` and `require("m")` all carry
|
|
38
|
+
// every export of `m` at once, exactly as `import * as ns` does — and are the
|
|
39
|
+
// same way around a rule about a name. A side-effect import carries nothing.
|
|
40
|
+
const WHOLE_MODULE = [{ symbol: "*", kind: "namespace" }];
|
|
41
|
+
// `export { a } from "m"` — `propertyName` is the name in the source module when
|
|
42
|
+
// the export is renamed, so it is the one the policy is about.
|
|
43
|
+
const bindingsOfExportClause = (clause) => {
|
|
44
|
+
if (clause === undefined || ts.isNamespaceExport(clause))
|
|
45
|
+
return WHOLE_MODULE;
|
|
46
|
+
const found = [];
|
|
47
|
+
for (const element of clause.elements) {
|
|
48
|
+
const symbol = nameOf(element.propertyName ?? element.name);
|
|
49
|
+
if (symbol !== null)
|
|
50
|
+
found.push({ symbol, kind: "named" });
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
};
|
|
54
|
+
// The type literals written in a type, through intersections, unions and
|
|
55
|
+
// parentheses: `type Port = Base & ({ a(): void } | { b(): void })` declares `a`
|
|
56
|
+
// and `b`. A reference is not followed — `Base`'s members are declared where
|
|
57
|
+
// `Base` is, and are reported there under its own name.
|
|
58
|
+
const literalsOf = (node) => {
|
|
59
|
+
if (ts.isTypeLiteralNode(node))
|
|
60
|
+
return [node];
|
|
61
|
+
if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
|
|
62
|
+
return node.types.flatMap(literalsOf);
|
|
63
|
+
}
|
|
64
|
+
if (ts.isParenthesizedTypeNode(node))
|
|
65
|
+
return literalsOf(node.type);
|
|
66
|
+
return [];
|
|
67
|
+
};
|
|
68
|
+
// The member shapes that carry a name a vocabulary rule can speak about: a
|
|
69
|
+
// property or method signature in a type, a property, method or accessor in a
|
|
70
|
+
// class. Mirrors the plugin's `DECLARED_MEMBER_TYPES`.
|
|
71
|
+
const isNamedMember = (member) => ts.isPropertySignature(member) ||
|
|
72
|
+
ts.isMethodSignature(member) ||
|
|
73
|
+
ts.isPropertyDeclaration(member) ||
|
|
74
|
+
ts.isMethodDeclaration(member) ||
|
|
75
|
+
ts.isGetAccessorDeclaration(member) ||
|
|
76
|
+
ts.isSetAccessorDeclaration(member);
|
|
77
|
+
const calleeNameOf = (expression) => {
|
|
78
|
+
if (ts.isIdentifier(expression))
|
|
79
|
+
return expression.text;
|
|
80
|
+
if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.name)) {
|
|
81
|
+
return expression.name.text;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
// The parse alone, for a source that need not be on disk — the CLI reads
|
|
86
|
+
// every file through it, a source probe is checked through it at load, and the
|
|
87
|
+
// parity suite feeds both adapters the same snippet through it.
|
|
88
|
+
export const factsOfText = (file, text) => {
|
|
89
|
+
const parsed = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, scriptKindOf(file));
|
|
90
|
+
const specifiers = [];
|
|
91
|
+
const bindings = new Map();
|
|
92
|
+
const memberSites = [];
|
|
93
|
+
const record = (specifier, found) => {
|
|
94
|
+
if (!bindings.has(specifier)) {
|
|
95
|
+
specifiers.push(specifier);
|
|
96
|
+
bindings.set(specifier, []);
|
|
97
|
+
}
|
|
98
|
+
const existing = bindings.get(specifier);
|
|
99
|
+
if (existing !== undefined)
|
|
100
|
+
for (const binding of found)
|
|
101
|
+
existing.push(binding);
|
|
102
|
+
};
|
|
103
|
+
// The members written in a declaration, under that declaration's name and
|
|
104
|
+
// kind. A computed key is not a name a vocabulary rule can speak about;
|
|
105
|
+
// neither is a private `#name`, an index, call or construct signature, or a
|
|
106
|
+
// constructor.
|
|
107
|
+
const declared = (declaration, declares, members) => {
|
|
108
|
+
for (const member of members) {
|
|
109
|
+
if (!isNamedMember(member))
|
|
110
|
+
continue;
|
|
111
|
+
if (ts.isComputedPropertyName(member.name) || ts.isPrivateIdentifier(member.name))
|
|
112
|
+
continue;
|
|
113
|
+
const name = nameOf(member.name);
|
|
114
|
+
if (name !== null) {
|
|
115
|
+
memberSites.push({ file, subject: "members", name, in: declaration, declares });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const visit = (node) => {
|
|
120
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
121
|
+
// A side-effect import carries no bindings but is still an edge — the
|
|
122
|
+
// `import "server-only"` form a regex cannot see.
|
|
123
|
+
record(node.moduleSpecifier.text, bindingsOfImportClause(node.importClause));
|
|
124
|
+
}
|
|
125
|
+
else if (ts.isExportDeclaration(node) &&
|
|
126
|
+
node.moduleSpecifier !== undefined &&
|
|
127
|
+
ts.isStringLiteral(node.moduleSpecifier)) {
|
|
128
|
+
record(node.moduleSpecifier.text, bindingsOfExportClause(node.exportClause));
|
|
129
|
+
}
|
|
130
|
+
else if (ts.isImportEqualsDeclaration(node) &&
|
|
131
|
+
ts.isExternalModuleReference(node.moduleReference) &&
|
|
132
|
+
ts.isStringLiteral(node.moduleReference.expression)) {
|
|
133
|
+
record(node.moduleReference.expression.text, WHOLE_MODULE);
|
|
134
|
+
}
|
|
135
|
+
else if (ts.isCallExpression(node)) {
|
|
136
|
+
const [first] = node.arguments;
|
|
137
|
+
const isModuleCall = node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
138
|
+
(ts.isIdentifier(node.expression) && node.expression.text === "require");
|
|
139
|
+
if (isModuleCall && first !== undefined && ts.isStringLiteral(first)) {
|
|
140
|
+
record(first.text, WHOLE_MODULE);
|
|
141
|
+
}
|
|
142
|
+
const callee = calleeNameOf(node.expression);
|
|
143
|
+
if (callee !== null)
|
|
144
|
+
memberSites.push({ file, subject: "calls", name: callee });
|
|
145
|
+
}
|
|
146
|
+
else if (ts.isTypeAliasDeclaration(node)) {
|
|
147
|
+
declared(node.name.text, "type", literalsOf(node.type).flatMap((literal) => literal.members));
|
|
148
|
+
}
|
|
149
|
+
else if (ts.isInterfaceDeclaration(node)) {
|
|
150
|
+
declared(node.name.text, "interface", node.members);
|
|
151
|
+
}
|
|
152
|
+
else if (ts.isClassDeclaration(node) && node.name !== undefined) {
|
|
153
|
+
// A class body is a declaration with members like any other. An anonymous
|
|
154
|
+
// class (`export default class {}`) has no name for `in` and is not read;
|
|
155
|
+
// neither is a class expression, which is a value.
|
|
156
|
+
declared(node.name.text, "class", node.members);
|
|
157
|
+
}
|
|
158
|
+
ts.forEachChild(node, visit);
|
|
159
|
+
};
|
|
160
|
+
visit(parsed);
|
|
161
|
+
return { specifiers, bindings, memberSites, exportSites: exportSitesOf(file, parsed) };
|
|
162
|
+
};
|
|
163
|
+
const hasModifier = (node, kind) => ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((one) => one.kind === kind);
|
|
164
|
+
// The names a declaration statement introduces, with what it declares them as.
|
|
165
|
+
// A destructuring pattern introduces names too, but not ones a surface rule
|
|
166
|
+
// judges by declaration kind; they read as `variable` like the rest.
|
|
167
|
+
const declaredNamesOf = (statement) => {
|
|
168
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
169
|
+
return statement.name === undefined ? [] : [[statement.name.text, "function"]];
|
|
170
|
+
}
|
|
171
|
+
if (ts.isClassDeclaration(statement)) {
|
|
172
|
+
return statement.name === undefined ? [] : [[statement.name.text, "class"]];
|
|
173
|
+
}
|
|
174
|
+
if (ts.isVariableStatement(statement)) {
|
|
175
|
+
const names = [];
|
|
176
|
+
const collect = (binding) => {
|
|
177
|
+
if (ts.isIdentifier(binding))
|
|
178
|
+
names.push([binding.text, "variable"]);
|
|
179
|
+
else {
|
|
180
|
+
for (const element of binding.elements) {
|
|
181
|
+
if (ts.isBindingElement(element))
|
|
182
|
+
collect(element.name);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
for (const declaration of statement.declarationList.declarations)
|
|
187
|
+
collect(declaration.name);
|
|
188
|
+
return names;
|
|
189
|
+
}
|
|
190
|
+
if (ts.isTypeAliasDeclaration(statement))
|
|
191
|
+
return [[statement.name.text, "type"]];
|
|
192
|
+
if (ts.isInterfaceDeclaration(statement))
|
|
193
|
+
return [[statement.name.text, "interface"]];
|
|
194
|
+
if (ts.isEnumDeclaration(statement))
|
|
195
|
+
return [[statement.name.text, "enum"]];
|
|
196
|
+
if (ts.isModuleDeclaration(statement) && ts.isIdentifier(statement.name)) {
|
|
197
|
+
return [[statement.name.text, "other"]];
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
};
|
|
201
|
+
// A file's surface: what its top-level statements export, in source order. An
|
|
202
|
+
// `export` inside a namespace body is that namespace's, not the module's.
|
|
203
|
+
const exportSitesOf = (file, parsed) => {
|
|
204
|
+
const locals = new Map();
|
|
205
|
+
for (const statement of parsed.statements) {
|
|
206
|
+
for (const [name, declares] of declaredNamesOf(statement))
|
|
207
|
+
locals.set(name, declares);
|
|
208
|
+
}
|
|
209
|
+
const sites = [];
|
|
210
|
+
const site = (name, kind, declares, reexport) => {
|
|
211
|
+
sites.push({ file, name, kind, declares, reexport });
|
|
212
|
+
};
|
|
213
|
+
for (const statement of parsed.statements) {
|
|
214
|
+
if (ts.isExportDeclaration(statement)) {
|
|
215
|
+
const reexport = statement.moduleSpecifier !== undefined;
|
|
216
|
+
const clause = statement.exportClause;
|
|
217
|
+
if (clause === undefined)
|
|
218
|
+
site("*", "namespace", "other", true);
|
|
219
|
+
else if (ts.isNamespaceExport(clause))
|
|
220
|
+
site(clause.name.text, "namespace", "other", true);
|
|
221
|
+
else {
|
|
222
|
+
for (const element of clause.elements) {
|
|
223
|
+
const name = nameOf(element.name);
|
|
224
|
+
if (name === null)
|
|
225
|
+
continue;
|
|
226
|
+
const local = nameOf(element.propertyName ?? element.name) ?? name;
|
|
227
|
+
const declares = reexport ? "other" : (locals.get(local) ?? "other");
|
|
228
|
+
site(name, name === "default" ? "default" : "named", declares, reexport);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if (ts.isExportAssignment(statement)) {
|
|
233
|
+
// `export = x` is a CommonJS surface, not a module's.
|
|
234
|
+
if (statement.isExportEquals === true)
|
|
235
|
+
continue;
|
|
236
|
+
const declares = ts.isIdentifier(statement.expression)
|
|
237
|
+
? (locals.get(statement.expression.text) ?? "expression")
|
|
238
|
+
: "expression";
|
|
239
|
+
site("default", "default", declares, false);
|
|
240
|
+
}
|
|
241
|
+
else if (hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
|
|
242
|
+
const isDefault = hasModifier(statement, ts.SyntaxKind.DefaultKeyword);
|
|
243
|
+
const declared = declaredNamesOf(statement);
|
|
244
|
+
if (isDefault) {
|
|
245
|
+
// `export default function () {}` has no name to look up.
|
|
246
|
+
const declares = declared[0]?.[1] ??
|
|
247
|
+
(ts.isFunctionDeclaration(statement)
|
|
248
|
+
? "function"
|
|
249
|
+
: ts.isClassDeclaration(statement)
|
|
250
|
+
? "class"
|
|
251
|
+
: "other");
|
|
252
|
+
site("default", "default", declares, false);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
for (const [name, declares] of declared)
|
|
256
|
+
site(name, "named", declares, false);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return sites;
|
|
261
|
+
};
|
|
262
|
+
export const makeFactExtractorLive = () => ({ factsOf: factsOfText });
|
|
263
|
+
//# sourceMappingURL=extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../../src/extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { makeFactExtractorLive } from "./extractor.js";
|
|
2
|
+
import { makeModuleResolverLive, TYPESCRIPT } from "./resolver.js";
|
|
3
|
+
export { factsOfText, makeFactExtractorLive } from "./extractor.js";
|
|
4
|
+
export { npmPackageOf } from "./npm-package.js";
|
|
5
|
+
export { decodeTypescriptScopeOptions } from "./options.js";
|
|
6
|
+
export { makeModuleResolverLive, TYPESCRIPT } from "./resolver.js";
|
|
7
|
+
// TypeScript, as one language pack: the parser-backed extractor and the
|
|
8
|
+
// `unrs-resolver`-backed resolver, behind the core's `Language` port. A host
|
|
9
|
+
// constructs this once, at its composition root, and hands it to `loadPolicy`;
|
|
10
|
+
// a second language is a second package shaped like this one.
|
|
11
|
+
export const typescriptLanguage = () => ({
|
|
12
|
+
id: TYPESCRIPT,
|
|
13
|
+
extensions: [".ts", ".tsx", ".mts", ".cts"],
|
|
14
|
+
// A declaration file states types, not code; no linter visits one and no
|
|
15
|
+
// policy is written about one.
|
|
16
|
+
ignoredFiles: [/\.d\.[cm]?ts$/],
|
|
17
|
+
extractor: makeFactExtractorLive(),
|
|
18
|
+
fixes: ["subpath-namespace-import"],
|
|
19
|
+
makeResolver: (repoRoot, scope) => makeModuleResolverLive(repoRoot, { scopes: [scope] }),
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAA+B,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnE,wEAAwE;AACxE,6EAA6E;AAC7E,+EAA+E;AAC/E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAa,EAAE,CAAC,CAAC;IACjD,EAAE,EAAE,UAAU;IACd,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;IAC3C,yEAAyE;IACzE,+BAA+B;IAC/B,YAAY,EAAE,CAAC,eAAe,CAAC;IAC/B,SAAS,EAAE,qBAAqB,EAAE;IAClC,KAAK,EAAE,CAAC,0BAA0B,CAAC;IACnC,YAAY,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,sBAAsB,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;CACzF,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The package a `node_modules/` path belongs to, as an `imports.external` entry
|
|
2
|
+
// names it: `effect`, or `@scope/name`. The LAST `node_modules/` segment is the
|
|
3
|
+
// one that matters — under pnpm the store path is
|
|
4
|
+
// `node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/…`, and the first
|
|
5
|
+
// segment after the first `node_modules/` would be `.pnpm`.
|
|
6
|
+
//
|
|
7
|
+
// This is npm's layout, and it belongs to the TypeScript side of the package:
|
|
8
|
+
// the policy only ever sees the name that comes out.
|
|
9
|
+
const LAST_NODE_MODULES = /(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/g;
|
|
10
|
+
export const npmPackageOf = (repoRelativePath) => {
|
|
11
|
+
let found;
|
|
12
|
+
for (const match of repoRelativePath.matchAll(LAST_NODE_MODULES))
|
|
13
|
+
found = match[1];
|
|
14
|
+
return found;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=npm-package.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-package.js","sourceRoot":"","sources":["../../src/npm-package.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,kDAAkD;AAClD,8EAA8E;AAC9E,4DAA4D;AAC5D,EAAE;AACF,8EAA8E;AAC9E,qDAAqD;AACrD,MAAM,iBAAiB,GAAG,6CAA6C,CAAC;AAExE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,gBAAwB,EAAsB,EAAE;IAC3E,IAAI,KAAyB,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,gBAAgB,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACnF,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ScopeInvalid } from "@goodbones/core";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
// What a `resolve.scopes` entry for TypeScript may say. The manifest carries
|
|
5
|
+
// these opaquely; this is where they mean something. Every one of them is an
|
|
6
|
+
// `unrs-resolver` option, and the defaults are the resolver's own.
|
|
7
|
+
export const TypescriptScopeOptions = Schema.Struct({
|
|
8
|
+
// The tsconfig whose `paths` and `baseUrl` resolve this scope's imports,
|
|
9
|
+
// repo-relative. Its `paths` targets should be extensionless: a mapped
|
|
10
|
+
// target is a template, and `x.js` against a `.ts`-suffixed one looks for
|
|
11
|
+
// `x.js.ts`.
|
|
12
|
+
tsconfig: Schema.String,
|
|
13
|
+
extensions: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
14
|
+
conditionNames: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
15
|
+
mainFields: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
16
|
+
});
|
|
17
|
+
// Unknown keys are refused: a misspelled `conditionNames` would otherwise be
|
|
18
|
+
// a resolver quietly built on defaults, and every rule about that scope would
|
|
19
|
+
// be evaluated against the wrong files.
|
|
20
|
+
const decode = Schema.decodeUnknownResult(TypescriptScopeOptions, { onExcessProperty: "error" });
|
|
21
|
+
export const decodeTypescriptScopeOptions = (scope) => Result.mapError(decode(scope.options ?? {}), (issue) => new ScopeInvalid({
|
|
22
|
+
files: scope.files,
|
|
23
|
+
language: scope.language,
|
|
24
|
+
detail: `options do not decode as TypeScript resolver options — ${String(issue)}`,
|
|
25
|
+
}));
|
|
26
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,6EAA6E;AAC7E,6EAA6E;AAC7E,mEAAmE;AACnE,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC;IAClD,yEAAyE;IACzE,uEAAuE;IACvE,0EAA0E;IAC1E,aAAa;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM;IACvB,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3D,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/D,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CAC5D,CAAC,CAAC;AAIH,6EAA6E;AAC7E,8EAA8E;AAC9E,wCAAwC;AACxC,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC;AAEjG,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,KAAmB,EACkC,EAAE,CACvD,MAAM,CAAC,QAAQ,CACb,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,EAC3B,CAAC,KAAK,EAAE,EAAE,CACR,IAAI,YAAY,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,KAAK;IAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;IACxB,MAAM,EAAE,0DAA0D,MAAM,CAAC,KAAK,CAAC,EAAE;CAClF,CAAC,CACL,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { ImportUnresolved, ScopeInvalid, } from "@goodbones/core";
|
|
3
|
+
import * as Result from "effect/Result";
|
|
4
|
+
import { ResolverFactory } from "unrs-resolver";
|
|
5
|
+
import { npmPackageOf } from "./npm-package.js";
|
|
6
|
+
import { decodeTypescriptScopeOptions } from "./options.js";
|
|
7
|
+
// `.js` in a NodeNext import specifier points at a `.ts` on disk. Without this,
|
|
8
|
+
// every relative import in the repo resolves to nothing and every path rule about
|
|
9
|
+
// it goes silently vacuous — the failure this package exists to make impossible.
|
|
10
|
+
const EXTENSION_ALIAS = {
|
|
11
|
+
".js": [".ts", ".tsx", ".js"],
|
|
12
|
+
".mjs": [".mts", ".mjs"],
|
|
13
|
+
".cjs": [".cts", ".cjs"],
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
16
|
+
const DEFAULT_CONDITION_NAMES = ["import", "require", "node", "default"];
|
|
17
|
+
const DEFAULT_MAIN_FIELDS = ["main", "types"];
|
|
18
|
+
export const TYPESCRIPT = "typescript";
|
|
19
|
+
const toPosix = (value) => value.replaceAll(path.sep, "/");
|
|
20
|
+
// A file under `node_modules/` is a third-party package, and the policy speaks
|
|
21
|
+
// about it by the package's name rather than by where pnpm or npm put it.
|
|
22
|
+
const toResolvedTarget = (repoRoot, absolutePath) => {
|
|
23
|
+
const relative = toPosix(path.relative(repoRoot, absolutePath));
|
|
24
|
+
const pkg = npmPackageOf(relative);
|
|
25
|
+
return pkg === undefined
|
|
26
|
+
? { path: relative, kind: "local" }
|
|
27
|
+
: { path: relative, kind: "external", package: pkg };
|
|
28
|
+
};
|
|
29
|
+
const factoryOf = (repoRoot, options) => new ResolverFactory({
|
|
30
|
+
tsconfig: { configFile: path.resolve(repoRoot, options.tsconfig) },
|
|
31
|
+
extensions: [...(options.extensions ?? DEFAULT_EXTENSIONS)],
|
|
32
|
+
extensionAlias: EXTENSION_ALIAS,
|
|
33
|
+
conditionNames: [...(options.conditionNames ?? DEFAULT_CONDITION_NAMES)],
|
|
34
|
+
mainFields: [...(options.mainFields ?? DEFAULT_MAIN_FIELDS)],
|
|
35
|
+
builtinModules: true,
|
|
36
|
+
});
|
|
37
|
+
// Every scope here is TypeScript's: this resolver is the one the TypeScript pack
|
|
38
|
+
// hands out, and a scope naming another language is one it cannot serve.
|
|
39
|
+
const scopeOf = (repoRoot, scope) => {
|
|
40
|
+
if (scope.language !== TYPESCRIPT) {
|
|
41
|
+
return Result.fail(new ScopeInvalid({
|
|
42
|
+
files: scope.files,
|
|
43
|
+
language: scope.language,
|
|
44
|
+
detail: `the TypeScript resolver cannot serve a "${scope.language}" scope`,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
return Result.map(decodeTypescriptScopeOptions(scope), (options) => ({
|
|
48
|
+
matches: new RegExp(scope.files),
|
|
49
|
+
factory: factoryOf(repoRoot, options),
|
|
50
|
+
}));
|
|
51
|
+
};
|
|
52
|
+
export const makeModuleResolverLive = (repoRoot, config) => {
|
|
53
|
+
const scopes = [];
|
|
54
|
+
for (const entry of config.scopes) {
|
|
55
|
+
const scope = scopeOf(repoRoot, entry);
|
|
56
|
+
if (Result.isFailure(scope))
|
|
57
|
+
return Result.fail(scope.failure);
|
|
58
|
+
scopes.push(scope.success);
|
|
59
|
+
}
|
|
60
|
+
const cache = new Map();
|
|
61
|
+
const resolveUncached = (scope, fromDirectory, fromFile, specifier) => {
|
|
62
|
+
const resolved = scope.factory.sync(fromDirectory, specifier);
|
|
63
|
+
const builtin = resolved.builtin;
|
|
64
|
+
if (builtin !== undefined) {
|
|
65
|
+
return Result.succeed({ path: builtin.resolved, kind: "builtin" });
|
|
66
|
+
}
|
|
67
|
+
const resolvedPath = resolved.path;
|
|
68
|
+
if (resolvedPath === undefined) {
|
|
69
|
+
return Result.fail(new ImportUnresolved({
|
|
70
|
+
fromFile,
|
|
71
|
+
specifier,
|
|
72
|
+
detail: resolved.error ?? "unknown resolution failure",
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
return Result.succeed(toResolvedTarget(repoRoot, resolvedPath));
|
|
76
|
+
};
|
|
77
|
+
return Result.succeed({
|
|
78
|
+
resolve: (fromFile, specifier) => {
|
|
79
|
+
const scope = scopes.find((candidate) => candidate.matches.test(fromFile));
|
|
80
|
+
if (scope === undefined) {
|
|
81
|
+
return Result.fail(new ImportUnresolved({
|
|
82
|
+
fromFile,
|
|
83
|
+
specifier,
|
|
84
|
+
detail: "no resolve scope in the architecture config matches this file",
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
const fromDirectory = path.resolve(repoRoot, path.dirname(fromFile));
|
|
88
|
+
const key = `${scope.matches.source} ${fromDirectory} ${specifier}`;
|
|
89
|
+
const cached = cache.get(key);
|
|
90
|
+
if (cached !== undefined)
|
|
91
|
+
return cached;
|
|
92
|
+
const outcome = resolveUncached(scope, fromDirectory, fromFile, specifier);
|
|
93
|
+
cache.set(key, outcome);
|
|
94
|
+
return outcome;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EACL,gBAAgB,EAKhB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAA+B,MAAM,cAAc,CAAC;AAEzF,gFAAgF;AAChF,kFAAkF;AAClF,iFAAiF;AACjF,MAAM,eAAe,GAAG;IACtB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC;IAC7B,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACnF,MAAM,uBAAuB,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AACzE,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE9C,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC;AAEvC,MAAM,OAAO,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAE3E,+EAA+E;AAC/E,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAE,YAAoB,EAAkB,EAAE;IAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACnC,OAAO,GAAG,KAAK,SAAS;QACtB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;QACnC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AACzD,CAAC,CAAC;AAOF,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAE,OAA+B,EAAmB,EAAE,CACvF,IAAI,eAAe,CAAC;IAClB,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE;IAClE,UAAU,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,kBAAkB,CAAC,CAAC;IAC3D,cAAc,EAAE,eAAe;IAC/B,cAAc,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,uBAAuB,CAAC,CAAC;IACxE,UAAU,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC;IAC5D,cAAc,EAAE,IAAI;CACrB,CAAC,CAAC;AAEL,iFAAiF;AACjF,yEAAyE;AACzE,MAAM,OAAO,GAAG,CAAC,QAAgB,EAAE,KAAmB,EAAsC,EAAE;IAC5F,IAAI,KAAK,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAClC,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,YAAY,CAAC;YACf,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,MAAM,EAAE,2CAA2C,KAAK,CAAC,QAAQ,SAAS;SAC3E,CAAC,CACH,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACnE,OAAO,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;QAChC,OAAO,EAAE,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC;KACtC,CAAC,CAAC,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,QAAgB,EAChB,MAAqB,EACwB,EAAE;IAC/C,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2D,CAAC;IAEjF,MAAM,eAAe,GAAG,CACtB,KAAY,EACZ,aAAqB,EACrB,QAAgB,EAChB,SAAiB,EACgC,EAAE;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAE9D,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QACjC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC;QACnC,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,gBAAgB,CAAC;gBACnB,QAAQ;gBACR,SAAS;gBACT,MAAM,EAAE,QAAQ,CAAC,KAAK,IAAI,4BAA4B;aACvD,CAAC,CACH,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,OAAO,CAAC;QACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE;YAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC3E,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,gBAAgB,CAAC;oBACnB,QAAQ;oBACR,SAAS;oBACT,MAAM,EAAE,+DAA+D;iBACxE,CAAC,CACH,CAAC;YACJ,CAAC;YAED,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;YACrE,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,IAAI,aAAa,IAAI,SAAS,EAAE,CAAC;YACpE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YAExC,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC3E,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACxB,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@goodbones/typescript",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "The TypeScript language pack for @goodbones/core: facts read through the TypeScript parser, specifiers resolved through unrs-resolver, behind the core's Language port.",
|
|
7
|
+
"author": "zacharyweidenbach",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"architecture",
|
|
10
|
+
"lint",
|
|
11
|
+
"import-boundaries",
|
|
12
|
+
"hexagonal",
|
|
13
|
+
"monorepo"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/dataquail/goodbones.git",
|
|
21
|
+
"directory": "packages/typescript"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://dataquail.github.io/goodbones",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/dataquail/goodbones/issues"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./build/dts/index.d.ts",
|
|
30
|
+
"default": "./build/esm/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"types": "./build/dts/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"build/dts",
|
|
37
|
+
"build/esm",
|
|
38
|
+
"src",
|
|
39
|
+
"!src/**/*.test.ts"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"effect": "4.0.0-beta.94",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"unrs-resolver": "^1.12.2",
|
|
45
|
+
"@goodbones/core": "0.1.0-beta.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^25.9.4"
|
|
49
|
+
},
|
|
50
|
+
"nx": {
|
|
51
|
+
"includedScripts": []
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -b tsconfig.build.json",
|
|
55
|
+
"check": "tsc -b tsconfig.json",
|
|
56
|
+
"clean": "rm -rf build .tsbuildinfo",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"coverage": "vitest run --coverage"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/extractor.ts
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Binding,
|
|
3
|
+
DeclarationKind,
|
|
4
|
+
ExportSite,
|
|
5
|
+
FactExtractor,
|
|
6
|
+
MemberSite,
|
|
7
|
+
SourceFacts,
|
|
8
|
+
} from "@goodbones/core";
|
|
9
|
+
import ts from "typescript";
|
|
10
|
+
|
|
11
|
+
// The facts, read out of TypeScript's syntax tree. The plugin reads the same
|
|
12
|
+
// facts out of oxlint's; `src/adapters/parity.test.ts` holds the two to one
|
|
13
|
+
// answer, so a form added here without the matching visitor there fails a test
|
|
14
|
+
// rather than a user.
|
|
15
|
+
|
|
16
|
+
const scriptKindOf = (file: string): ts.ScriptKind =>
|
|
17
|
+
file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
18
|
+
|
|
19
|
+
const nameOf = (node: ts.PropertyName | ts.ModuleExportName | undefined): string | null => {
|
|
20
|
+
if (node === undefined) return null;
|
|
21
|
+
if (ts.isIdentifier(node)) return node.text;
|
|
22
|
+
if (ts.isStringLiteral(node)) return node.text;
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const bindingsOfImportClause = (clause: ts.ImportClause | undefined): ReadonlyArray<Binding> => {
|
|
27
|
+
if (clause === undefined) return [];
|
|
28
|
+
const found: Array<Binding> = [];
|
|
29
|
+
if (clause.name !== undefined) found.push({ symbol: "default", kind: "default" });
|
|
30
|
+
const bindings = clause.namedBindings;
|
|
31
|
+
if (bindings !== undefined) {
|
|
32
|
+
if (ts.isNamespaceImport(bindings)) found.push({ symbol: "*", kind: "namespace" });
|
|
33
|
+
else {
|
|
34
|
+
for (const element of bindings.elements) {
|
|
35
|
+
const symbol = nameOf(element.propertyName ?? element.name);
|
|
36
|
+
if (symbol !== null) found.push({ symbol, kind: "named" });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// The whole module, as one binding. `export * from "m"`, `export * as ns from
|
|
44
|
+
// "m"`, `import x = require("m")`, `import("m")` and `require("m")` all carry
|
|
45
|
+
// every export of `m` at once, exactly as `import * as ns` does — and are the
|
|
46
|
+
// same way around a rule about a name. A side-effect import carries nothing.
|
|
47
|
+
const WHOLE_MODULE: ReadonlyArray<Binding> = [{ symbol: "*", kind: "namespace" }];
|
|
48
|
+
|
|
49
|
+
// `export { a } from "m"` — `propertyName` is the name in the source module when
|
|
50
|
+
// the export is renamed, so it is the one the policy is about.
|
|
51
|
+
const bindingsOfExportClause = (
|
|
52
|
+
clause: ts.NamedExportBindings | undefined,
|
|
53
|
+
): ReadonlyArray<Binding> => {
|
|
54
|
+
if (clause === undefined || ts.isNamespaceExport(clause)) return WHOLE_MODULE;
|
|
55
|
+
const found: Array<Binding> = [];
|
|
56
|
+
for (const element of clause.elements) {
|
|
57
|
+
const symbol = nameOf(element.propertyName ?? element.name);
|
|
58
|
+
if (symbol !== null) found.push({ symbol, kind: "named" });
|
|
59
|
+
}
|
|
60
|
+
return found;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// The type literals written in a type, through intersections, unions and
|
|
64
|
+
// parentheses: `type Port = Base & ({ a(): void } | { b(): void })` declares `a`
|
|
65
|
+
// and `b`. A reference is not followed — `Base`'s members are declared where
|
|
66
|
+
// `Base` is, and are reported there under its own name.
|
|
67
|
+
const literalsOf = (node: ts.TypeNode): ReadonlyArray<ts.TypeLiteralNode> => {
|
|
68
|
+
if (ts.isTypeLiteralNode(node)) return [node];
|
|
69
|
+
if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
|
|
70
|
+
return node.types.flatMap(literalsOf);
|
|
71
|
+
}
|
|
72
|
+
if (ts.isParenthesizedTypeNode(node)) return literalsOf(node.type);
|
|
73
|
+
return [];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// The member shapes that carry a name a vocabulary rule can speak about: a
|
|
77
|
+
// property or method signature in a type, a property, method or accessor in a
|
|
78
|
+
// class. Mirrors the plugin's `DECLARED_MEMBER_TYPES`.
|
|
79
|
+
const isNamedMember = (
|
|
80
|
+
member: ts.TypeElement | ts.ClassElement,
|
|
81
|
+
): member is (ts.TypeElement | ts.ClassElement) & { readonly name: ts.PropertyName } =>
|
|
82
|
+
ts.isPropertySignature(member) ||
|
|
83
|
+
ts.isMethodSignature(member) ||
|
|
84
|
+
ts.isPropertyDeclaration(member) ||
|
|
85
|
+
ts.isMethodDeclaration(member) ||
|
|
86
|
+
ts.isGetAccessorDeclaration(member) ||
|
|
87
|
+
ts.isSetAccessorDeclaration(member);
|
|
88
|
+
|
|
89
|
+
const calleeNameOf = (expression: ts.LeftHandSideExpression): string | null => {
|
|
90
|
+
if (ts.isIdentifier(expression)) return expression.text;
|
|
91
|
+
if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.name)) {
|
|
92
|
+
return expression.name.text;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// The parse alone, for a source that need not be on disk — the CLI reads
|
|
98
|
+
// every file through it, a source probe is checked through it at load, and the
|
|
99
|
+
// parity suite feeds both adapters the same snippet through it.
|
|
100
|
+
export const factsOfText = (file: string, text: string): SourceFacts => {
|
|
101
|
+
const parsed = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, scriptKindOf(file));
|
|
102
|
+
|
|
103
|
+
const specifiers: Array<string> = [];
|
|
104
|
+
const bindings = new Map<string, Array<Binding>>();
|
|
105
|
+
const memberSites: Array<MemberSite> = [];
|
|
106
|
+
|
|
107
|
+
const record = (specifier: string, found: ReadonlyArray<Binding>): void => {
|
|
108
|
+
if (!bindings.has(specifier)) {
|
|
109
|
+
specifiers.push(specifier);
|
|
110
|
+
bindings.set(specifier, []);
|
|
111
|
+
}
|
|
112
|
+
const existing = bindings.get(specifier);
|
|
113
|
+
if (existing !== undefined) for (const binding of found) existing.push(binding);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// The members written in a declaration, under that declaration's name and
|
|
117
|
+
// kind. A computed key is not a name a vocabulary rule can speak about;
|
|
118
|
+
// neither is a private `#name`, an index, call or construct signature, or a
|
|
119
|
+
// constructor.
|
|
120
|
+
const declared = (
|
|
121
|
+
declaration: string,
|
|
122
|
+
declares: DeclarationKind,
|
|
123
|
+
members: ReadonlyArray<ts.TypeElement | ts.ClassElement>,
|
|
124
|
+
): void => {
|
|
125
|
+
for (const member of members) {
|
|
126
|
+
if (!isNamedMember(member)) continue;
|
|
127
|
+
if (ts.isComputedPropertyName(member.name) || ts.isPrivateIdentifier(member.name)) continue;
|
|
128
|
+
const name = nameOf(member.name);
|
|
129
|
+
if (name !== null) {
|
|
130
|
+
memberSites.push({ file, subject: "members", name, in: declaration, declares });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const visit = (node: ts.Node): void => {
|
|
136
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
137
|
+
// A side-effect import carries no bindings but is still an edge — the
|
|
138
|
+
// `import "server-only"` form a regex cannot see.
|
|
139
|
+
record(node.moduleSpecifier.text, bindingsOfImportClause(node.importClause));
|
|
140
|
+
} else if (
|
|
141
|
+
ts.isExportDeclaration(node) &&
|
|
142
|
+
node.moduleSpecifier !== undefined &&
|
|
143
|
+
ts.isStringLiteral(node.moduleSpecifier)
|
|
144
|
+
) {
|
|
145
|
+
record(node.moduleSpecifier.text, bindingsOfExportClause(node.exportClause));
|
|
146
|
+
} else if (
|
|
147
|
+
ts.isImportEqualsDeclaration(node) &&
|
|
148
|
+
ts.isExternalModuleReference(node.moduleReference) &&
|
|
149
|
+
ts.isStringLiteral(node.moduleReference.expression)
|
|
150
|
+
) {
|
|
151
|
+
record(node.moduleReference.expression.text, WHOLE_MODULE);
|
|
152
|
+
} else if (ts.isCallExpression(node)) {
|
|
153
|
+
const [first] = node.arguments;
|
|
154
|
+
const isModuleCall =
|
|
155
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
156
|
+
(ts.isIdentifier(node.expression) && node.expression.text === "require");
|
|
157
|
+
if (isModuleCall && first !== undefined && ts.isStringLiteral(first)) {
|
|
158
|
+
record(first.text, WHOLE_MODULE);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const callee = calleeNameOf(node.expression);
|
|
162
|
+
if (callee !== null) memberSites.push({ file, subject: "calls", name: callee });
|
|
163
|
+
} else if (ts.isTypeAliasDeclaration(node)) {
|
|
164
|
+
declared(
|
|
165
|
+
node.name.text,
|
|
166
|
+
"type",
|
|
167
|
+
literalsOf(node.type).flatMap((literal) => literal.members),
|
|
168
|
+
);
|
|
169
|
+
} else if (ts.isInterfaceDeclaration(node)) {
|
|
170
|
+
declared(node.name.text, "interface", node.members);
|
|
171
|
+
} else if (ts.isClassDeclaration(node) && node.name !== undefined) {
|
|
172
|
+
// A class body is a declaration with members like any other. An anonymous
|
|
173
|
+
// class (`export default class {}`) has no name for `in` and is not read;
|
|
174
|
+
// neither is a class expression, which is a value.
|
|
175
|
+
declared(node.name.text, "class", node.members);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ts.forEachChild(node, visit);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
visit(parsed);
|
|
182
|
+
|
|
183
|
+
return { specifiers, bindings, memberSites, exportSites: exportSitesOf(file, parsed) };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const hasModifier = (node: ts.Node, kind: ts.SyntaxKind): boolean =>
|
|
187
|
+
ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((one) => one.kind === kind);
|
|
188
|
+
|
|
189
|
+
// The names a declaration statement introduces, with what it declares them as.
|
|
190
|
+
// A destructuring pattern introduces names too, but not ones a surface rule
|
|
191
|
+
// judges by declaration kind; they read as `variable` like the rest.
|
|
192
|
+
const declaredNamesOf = (statement: ts.Node): ReadonlyArray<readonly [string, DeclarationKind]> => {
|
|
193
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
194
|
+
return statement.name === undefined ? [] : [[statement.name.text, "function"]];
|
|
195
|
+
}
|
|
196
|
+
if (ts.isClassDeclaration(statement)) {
|
|
197
|
+
return statement.name === undefined ? [] : [[statement.name.text, "class"]];
|
|
198
|
+
}
|
|
199
|
+
if (ts.isVariableStatement(statement)) {
|
|
200
|
+
const names: Array<readonly [string, DeclarationKind]> = [];
|
|
201
|
+
const collect = (binding: ts.BindingName): void => {
|
|
202
|
+
if (ts.isIdentifier(binding)) names.push([binding.text, "variable"]);
|
|
203
|
+
else {
|
|
204
|
+
for (const element of binding.elements) {
|
|
205
|
+
if (ts.isBindingElement(element)) collect(element.name);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
for (const declaration of statement.declarationList.declarations) collect(declaration.name);
|
|
210
|
+
return names;
|
|
211
|
+
}
|
|
212
|
+
if (ts.isTypeAliasDeclaration(statement)) return [[statement.name.text, "type"]];
|
|
213
|
+
if (ts.isInterfaceDeclaration(statement)) return [[statement.name.text, "interface"]];
|
|
214
|
+
if (ts.isEnumDeclaration(statement)) return [[statement.name.text, "enum"]];
|
|
215
|
+
if (ts.isModuleDeclaration(statement) && ts.isIdentifier(statement.name)) {
|
|
216
|
+
return [[statement.name.text, "other"]];
|
|
217
|
+
}
|
|
218
|
+
return [];
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// A file's surface: what its top-level statements export, in source order. An
|
|
222
|
+
// `export` inside a namespace body is that namespace's, not the module's.
|
|
223
|
+
const exportSitesOf = (file: string, parsed: ts.SourceFile): ReadonlyArray<ExportSite> => {
|
|
224
|
+
const locals = new Map<string, DeclarationKind>();
|
|
225
|
+
for (const statement of parsed.statements) {
|
|
226
|
+
for (const [name, declares] of declaredNamesOf(statement)) locals.set(name, declares);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const sites: Array<ExportSite> = [];
|
|
230
|
+
const site = (
|
|
231
|
+
name: string,
|
|
232
|
+
kind: ExportSite["kind"],
|
|
233
|
+
declares: DeclarationKind,
|
|
234
|
+
reexport: boolean,
|
|
235
|
+
): void => {
|
|
236
|
+
sites.push({ file, name, kind, declares, reexport });
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
for (const statement of parsed.statements) {
|
|
240
|
+
if (ts.isExportDeclaration(statement)) {
|
|
241
|
+
const reexport = statement.moduleSpecifier !== undefined;
|
|
242
|
+
const clause = statement.exportClause;
|
|
243
|
+
if (clause === undefined) site("*", "namespace", "other", true);
|
|
244
|
+
else if (ts.isNamespaceExport(clause)) site(clause.name.text, "namespace", "other", true);
|
|
245
|
+
else {
|
|
246
|
+
for (const element of clause.elements) {
|
|
247
|
+
const name = nameOf(element.name);
|
|
248
|
+
if (name === null) continue;
|
|
249
|
+
const local = nameOf(element.propertyName ?? element.name) ?? name;
|
|
250
|
+
const declares = reexport ? "other" : (locals.get(local) ?? "other");
|
|
251
|
+
site(name, name === "default" ? "default" : "named", declares, reexport);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} else if (ts.isExportAssignment(statement)) {
|
|
255
|
+
// `export = x` is a CommonJS surface, not a module's.
|
|
256
|
+
if (statement.isExportEquals === true) continue;
|
|
257
|
+
const declares = ts.isIdentifier(statement.expression)
|
|
258
|
+
? (locals.get(statement.expression.text) ?? "expression")
|
|
259
|
+
: "expression";
|
|
260
|
+
site("default", "default", declares, false);
|
|
261
|
+
} else if (hasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
|
|
262
|
+
const isDefault = hasModifier(statement, ts.SyntaxKind.DefaultKeyword);
|
|
263
|
+
const declared = declaredNamesOf(statement);
|
|
264
|
+
if (isDefault) {
|
|
265
|
+
// `export default function () {}` has no name to look up.
|
|
266
|
+
const declares =
|
|
267
|
+
declared[0]?.[1] ??
|
|
268
|
+
(ts.isFunctionDeclaration(statement)
|
|
269
|
+
? "function"
|
|
270
|
+
: ts.isClassDeclaration(statement)
|
|
271
|
+
? "class"
|
|
272
|
+
: "other");
|
|
273
|
+
site("default", "default", declares, false);
|
|
274
|
+
} else {
|
|
275
|
+
for (const [name, declares] of declared) site(name, "named", declares, false);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return sites;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const makeFactExtractorLive = (): FactExtractor => ({ factsOf: factsOfText });
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Language } from "@goodbones/core";
|
|
2
|
+
|
|
3
|
+
import { makeFactExtractorLive } from "./extractor.js";
|
|
4
|
+
import { makeModuleResolverLive, TYPESCRIPT } from "./resolver.js";
|
|
5
|
+
|
|
6
|
+
export { factsOfText, makeFactExtractorLive } from "./extractor.js";
|
|
7
|
+
export { npmPackageOf } from "./npm-package.js";
|
|
8
|
+
export { decodeTypescriptScopeOptions, type TypescriptScopeOptions } from "./options.js";
|
|
9
|
+
export { makeModuleResolverLive, TYPESCRIPT } from "./resolver.js";
|
|
10
|
+
|
|
11
|
+
// TypeScript, as one language pack: the parser-backed extractor and the
|
|
12
|
+
// `unrs-resolver`-backed resolver, behind the core's `Language` port. A host
|
|
13
|
+
// constructs this once, at its composition root, and hands it to `loadPolicy`;
|
|
14
|
+
// a second language is a second package shaped like this one.
|
|
15
|
+
export const typescriptLanguage = (): Language => ({
|
|
16
|
+
id: TYPESCRIPT,
|
|
17
|
+
extensions: [".ts", ".tsx", ".mts", ".cts"],
|
|
18
|
+
// A declaration file states types, not code; no linter visits one and no
|
|
19
|
+
// policy is written about one.
|
|
20
|
+
ignoredFiles: [/\.d\.[cm]?ts$/],
|
|
21
|
+
extractor: makeFactExtractorLive(),
|
|
22
|
+
fixes: ["subpath-namespace-import"],
|
|
23
|
+
makeResolver: (repoRoot, scope) => makeModuleResolverLive(repoRoot, { scopes: [scope] }),
|
|
24
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The package a `node_modules/` path belongs to, as an `imports.external` entry
|
|
2
|
+
// names it: `effect`, or `@scope/name`. The LAST `node_modules/` segment is the
|
|
3
|
+
// one that matters — under pnpm the store path is
|
|
4
|
+
// `node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/…`, and the first
|
|
5
|
+
// segment after the first `node_modules/` would be `.pnpm`.
|
|
6
|
+
//
|
|
7
|
+
// This is npm's layout, and it belongs to the TypeScript side of the package:
|
|
8
|
+
// the policy only ever sees the name that comes out.
|
|
9
|
+
const LAST_NODE_MODULES = /(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/g;
|
|
10
|
+
|
|
11
|
+
export const npmPackageOf = (repoRelativePath: string): string | undefined => {
|
|
12
|
+
let found: string | undefined;
|
|
13
|
+
for (const match of repoRelativePath.matchAll(LAST_NODE_MODULES)) found = match[1];
|
|
14
|
+
return found;
|
|
15
|
+
};
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ResolveScope, ScopeInvalid } from "@goodbones/core";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
|
|
5
|
+
// What a `resolve.scopes` entry for TypeScript may say. The manifest carries
|
|
6
|
+
// these opaquely; this is where they mean something. Every one of them is an
|
|
7
|
+
// `unrs-resolver` option, and the defaults are the resolver's own.
|
|
8
|
+
export const TypescriptScopeOptions = Schema.Struct({
|
|
9
|
+
// The tsconfig whose `paths` and `baseUrl` resolve this scope's imports,
|
|
10
|
+
// repo-relative. Its `paths` targets should be extensionless: a mapped
|
|
11
|
+
// target is a template, and `x.js` against a `.ts`-suffixed one looks for
|
|
12
|
+
// `x.js.ts`.
|
|
13
|
+
tsconfig: Schema.String,
|
|
14
|
+
extensions: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
15
|
+
conditionNames: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
16
|
+
mainFields: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type TypescriptScopeOptions = typeof TypescriptScopeOptions.Type;
|
|
20
|
+
|
|
21
|
+
// Unknown keys are refused: a misspelled `conditionNames` would otherwise be
|
|
22
|
+
// a resolver quietly built on defaults, and every rule about that scope would
|
|
23
|
+
// be evaluated against the wrong files.
|
|
24
|
+
const decode = Schema.decodeUnknownResult(TypescriptScopeOptions, { onExcessProperty: "error" });
|
|
25
|
+
|
|
26
|
+
export const decodeTypescriptScopeOptions = (
|
|
27
|
+
scope: ResolveScope,
|
|
28
|
+
): Result.Result<TypescriptScopeOptions, ScopeInvalid> =>
|
|
29
|
+
Result.mapError(
|
|
30
|
+
decode(scope.options ?? {}),
|
|
31
|
+
(issue) =>
|
|
32
|
+
new ScopeInvalid({
|
|
33
|
+
files: scope.files,
|
|
34
|
+
language: scope.language,
|
|
35
|
+
detail: `options do not decode as TypeScript resolver options — ${String(issue)}`,
|
|
36
|
+
}),
|
|
37
|
+
);
|
package/src/resolver.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ImportUnresolved,
|
|
5
|
+
type ModuleResolver,
|
|
6
|
+
type ResolveConfig,
|
|
7
|
+
type ResolvedTarget,
|
|
8
|
+
type ResolveScope,
|
|
9
|
+
ScopeInvalid,
|
|
10
|
+
} from "@goodbones/core";
|
|
11
|
+
import * as Result from "effect/Result";
|
|
12
|
+
import { ResolverFactory } from "unrs-resolver";
|
|
13
|
+
|
|
14
|
+
import { npmPackageOf } from "./npm-package.js";
|
|
15
|
+
import { decodeTypescriptScopeOptions, type TypescriptScopeOptions } from "./options.js";
|
|
16
|
+
|
|
17
|
+
// `.js` in a NodeNext import specifier points at a `.ts` on disk. Without this,
|
|
18
|
+
// every relative import in the repo resolves to nothing and every path rule about
|
|
19
|
+
// it goes silently vacuous — the failure this package exists to make impossible.
|
|
20
|
+
const EXTENSION_ALIAS = {
|
|
21
|
+
".js": [".ts", ".tsx", ".js"],
|
|
22
|
+
".mjs": [".mts", ".mjs"],
|
|
23
|
+
".cjs": [".cts", ".cjs"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
27
|
+
const DEFAULT_CONDITION_NAMES = ["import", "require", "node", "default"];
|
|
28
|
+
const DEFAULT_MAIN_FIELDS = ["main", "types"];
|
|
29
|
+
|
|
30
|
+
export const TYPESCRIPT = "typescript";
|
|
31
|
+
|
|
32
|
+
const toPosix = (value: string): string => value.replaceAll(path.sep, "/");
|
|
33
|
+
|
|
34
|
+
// A file under `node_modules/` is a third-party package, and the policy speaks
|
|
35
|
+
// about it by the package's name rather than by where pnpm or npm put it.
|
|
36
|
+
const toResolvedTarget = (repoRoot: string, absolutePath: string): ResolvedTarget => {
|
|
37
|
+
const relative = toPosix(path.relative(repoRoot, absolutePath));
|
|
38
|
+
const pkg = npmPackageOf(relative);
|
|
39
|
+
return pkg === undefined
|
|
40
|
+
? { path: relative, kind: "local" }
|
|
41
|
+
: { path: relative, kind: "external", package: pkg };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type Scope = {
|
|
45
|
+
readonly matches: RegExp;
|
|
46
|
+
readonly factory: ResolverFactory;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const factoryOf = (repoRoot: string, options: TypescriptScopeOptions): ResolverFactory =>
|
|
50
|
+
new ResolverFactory({
|
|
51
|
+
tsconfig: { configFile: path.resolve(repoRoot, options.tsconfig) },
|
|
52
|
+
extensions: [...(options.extensions ?? DEFAULT_EXTENSIONS)],
|
|
53
|
+
extensionAlias: EXTENSION_ALIAS,
|
|
54
|
+
conditionNames: [...(options.conditionNames ?? DEFAULT_CONDITION_NAMES)],
|
|
55
|
+
mainFields: [...(options.mainFields ?? DEFAULT_MAIN_FIELDS)],
|
|
56
|
+
builtinModules: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Every scope here is TypeScript's: this resolver is the one the TypeScript pack
|
|
60
|
+
// hands out, and a scope naming another language is one it cannot serve.
|
|
61
|
+
const scopeOf = (repoRoot: string, scope: ResolveScope): Result.Result<Scope, ScopeInvalid> => {
|
|
62
|
+
if (scope.language !== TYPESCRIPT) {
|
|
63
|
+
return Result.fail(
|
|
64
|
+
new ScopeInvalid({
|
|
65
|
+
files: scope.files,
|
|
66
|
+
language: scope.language,
|
|
67
|
+
detail: `the TypeScript resolver cannot serve a "${scope.language}" scope`,
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return Result.map(decodeTypescriptScopeOptions(scope), (options) => ({
|
|
72
|
+
matches: new RegExp(scope.files),
|
|
73
|
+
factory: factoryOf(repoRoot, options),
|
|
74
|
+
}));
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const makeModuleResolverLive = (
|
|
78
|
+
repoRoot: string,
|
|
79
|
+
config: ResolveConfig,
|
|
80
|
+
): Result.Result<ModuleResolver, ScopeInvalid> => {
|
|
81
|
+
const scopes: Array<Scope> = [];
|
|
82
|
+
for (const entry of config.scopes) {
|
|
83
|
+
const scope = scopeOf(repoRoot, entry);
|
|
84
|
+
if (Result.isFailure(scope)) return Result.fail(scope.failure);
|
|
85
|
+
scopes.push(scope.success);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const cache = new Map<string, Result.Result<ResolvedTarget, ImportUnresolved>>();
|
|
89
|
+
|
|
90
|
+
const resolveUncached = (
|
|
91
|
+
scope: Scope,
|
|
92
|
+
fromDirectory: string,
|
|
93
|
+
fromFile: string,
|
|
94
|
+
specifier: string,
|
|
95
|
+
): Result.Result<ResolvedTarget, ImportUnresolved> => {
|
|
96
|
+
const resolved = scope.factory.sync(fromDirectory, specifier);
|
|
97
|
+
|
|
98
|
+
const builtin = resolved.builtin;
|
|
99
|
+
if (builtin !== undefined) {
|
|
100
|
+
return Result.succeed({ path: builtin.resolved, kind: "builtin" });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const resolvedPath = resolved.path;
|
|
104
|
+
if (resolvedPath === undefined) {
|
|
105
|
+
return Result.fail(
|
|
106
|
+
new ImportUnresolved({
|
|
107
|
+
fromFile,
|
|
108
|
+
specifier,
|
|
109
|
+
detail: resolved.error ?? "unknown resolution failure",
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Result.succeed(toResolvedTarget(repoRoot, resolvedPath));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return Result.succeed({
|
|
118
|
+
resolve: (fromFile, specifier) => {
|
|
119
|
+
const scope = scopes.find((candidate) => candidate.matches.test(fromFile));
|
|
120
|
+
if (scope === undefined) {
|
|
121
|
+
return Result.fail(
|
|
122
|
+
new ImportUnresolved({
|
|
123
|
+
fromFile,
|
|
124
|
+
specifier,
|
|
125
|
+
detail: "no resolve scope in the architecture config matches this file",
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const fromDirectory = path.resolve(repoRoot, path.dirname(fromFile));
|
|
131
|
+
const key = `${scope.matches.source} ${fromDirectory} ${specifier}`;
|
|
132
|
+
const cached = cache.get(key);
|
|
133
|
+
if (cached !== undefined) return cached;
|
|
134
|
+
|
|
135
|
+
const outcome = resolveUncached(scope, fromDirectory, fromFile, specifier);
|
|
136
|
+
cache.set(key, outcome);
|
|
137
|
+
return outcome;
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
};
|