@goodbones/oxlint 0.1.0-beta.5 → 0.1.0-beta.7
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/build/dts/exports-rule.d.ts.map +1 -1
- package/build/dts/imports-rule.d.ts.map +1 -1
- package/build/dts/members-rule.d.ts.map +1 -1
- package/build/dts/oxlint-api.d.ts +4 -33
- package/build/dts/oxlint-api.d.ts.map +1 -1
- package/build/dts/surface-rule.d.ts.map +1 -1
- package/build/esm/exports-rule.js +15 -71
- package/build/esm/exports-rule.js.map +1 -1
- package/build/esm/imports-rule.js +14 -26
- package/build/esm/imports-rule.js.map +1 -1
- package/build/esm/members-rule.js +10 -112
- package/build/esm/members-rule.js.map +1 -1
- package/build/esm/oxlint-api.js +22 -20
- package/build/esm/oxlint-api.js.map +1 -1
- package/build/esm/surface-rule.js +11 -127
- package/build/esm/surface-rule.js.map +1 -1
- package/package.json +3 -3
- package/src/exports-rule.ts +19 -106
- package/src/imports-rule.ts +16 -35
- package/src/members-rule.ts +11 -165
- package/src/oxlint-api.ts +31 -47
- package/src/surface-rule.ts +15 -189
|
@@ -1,122 +1,5 @@
|
|
|
1
1
|
import { evaluateSurface, formatMessage, surfaceRulesSelecting, } from "@goodbones/core";
|
|
2
|
-
import { toRepoRelative, } from "./oxlint-api.js";
|
|
3
|
-
const isStatementList = (value) => Array.isArray(value);
|
|
4
|
-
const nameOf = (node) => {
|
|
5
|
-
if (node === null || node === undefined)
|
|
6
|
-
return null;
|
|
7
|
-
if (typeof node.name === "string")
|
|
8
|
-
return node.name;
|
|
9
|
-
return typeof node.value === "string" ? node.value : null;
|
|
10
|
-
};
|
|
11
|
-
// The identifiers a binding pattern introduces.
|
|
12
|
-
const patternNames = (pattern) => {
|
|
13
|
-
if (pattern === null || pattern === undefined)
|
|
14
|
-
return [];
|
|
15
|
-
switch (pattern.type) {
|
|
16
|
-
case "Identifier":
|
|
17
|
-
return typeof pattern.name === "string" ? [pattern.name] : [];
|
|
18
|
-
case "ObjectPattern":
|
|
19
|
-
return (pattern.properties ?? []).flatMap((property) => patternNames(property.value ?? property.argument));
|
|
20
|
-
case "ArrayPattern":
|
|
21
|
-
return (pattern.elements ?? []).flatMap(patternNames);
|
|
22
|
-
case "AssignmentPattern":
|
|
23
|
-
return patternNames(pattern.left);
|
|
24
|
-
case "RestElement":
|
|
25
|
-
return patternNames(pattern.argument);
|
|
26
|
-
default:
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
// The names a declaration statement introduces, with what it declares them as.
|
|
31
|
-
const declaredNamesOf = (statement) => {
|
|
32
|
-
const named = (declares) => {
|
|
33
|
-
const name = nameOf(statement.id);
|
|
34
|
-
return name === null ? [] : [[name, declares]];
|
|
35
|
-
};
|
|
36
|
-
switch (statement.type) {
|
|
37
|
-
case "FunctionDeclaration":
|
|
38
|
-
return named("function");
|
|
39
|
-
case "ClassDeclaration":
|
|
40
|
-
return named("class");
|
|
41
|
-
case "VariableDeclaration":
|
|
42
|
-
return (statement.declarations ?? []).flatMap((declaration) => patternNames(declaration.id).map((name) => [name, "variable"]));
|
|
43
|
-
case "TSTypeAliasDeclaration":
|
|
44
|
-
return named("type");
|
|
45
|
-
case "TSInterfaceDeclaration":
|
|
46
|
-
return named("interface");
|
|
47
|
-
case "TSEnumDeclaration":
|
|
48
|
-
return named("enum");
|
|
49
|
-
case "TSModuleDeclaration":
|
|
50
|
-
return named("other");
|
|
51
|
-
default:
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
// A file's surface, read off the program body rather than by visiting export
|
|
56
|
-
// nodes: an `export` inside a namespace body is that namespace's, not the
|
|
57
|
-
// module's, and reading the top level directly is what says so without a
|
|
58
|
-
// parent pointer. Mirrors the CLI's `exportSitesOf`.
|
|
59
|
-
const surfaceOf = (file, body) => {
|
|
60
|
-
const locals = new Map();
|
|
61
|
-
for (const statement of body) {
|
|
62
|
-
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration"
|
|
63
|
-
? statement.declaration
|
|
64
|
-
: statement;
|
|
65
|
-
if (declaration === null || declaration === undefined)
|
|
66
|
-
continue;
|
|
67
|
-
for (const [name, declares] of declaredNamesOf(declaration))
|
|
68
|
-
locals.set(name, declares);
|
|
69
|
-
}
|
|
70
|
-
const found = [];
|
|
71
|
-
const site = (node, name, kind, declares, reexport) => {
|
|
72
|
-
found.push({ node, site: { file, name, kind, declares, reexport } });
|
|
73
|
-
};
|
|
74
|
-
for (const statement of body) {
|
|
75
|
-
switch (statement.type) {
|
|
76
|
-
case "ExportNamedDeclaration": {
|
|
77
|
-
const declaration = statement.declaration;
|
|
78
|
-
if (declaration !== null && declaration !== undefined) {
|
|
79
|
-
for (const [name, declares] of declaredNamesOf(declaration)) {
|
|
80
|
-
site(declaration, name, "named", declares, false);
|
|
81
|
-
}
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
const reexport = typeof statement.source?.value === "string";
|
|
85
|
-
for (const specifier of statement.specifiers ?? []) {
|
|
86
|
-
const name = nameOf(specifier.exported);
|
|
87
|
-
if (name === null)
|
|
88
|
-
continue;
|
|
89
|
-
const local = nameOf(specifier.local) ?? name;
|
|
90
|
-
const declares = reexport ? "other" : (locals.get(local) ?? "other");
|
|
91
|
-
site(specifier, name, name === "default" ? "default" : "named", declares, reexport);
|
|
92
|
-
}
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
case "ExportDefaultDeclaration": {
|
|
96
|
-
const declaration = statement.declaration;
|
|
97
|
-
const declares = declaration === null || declaration === undefined
|
|
98
|
-
? "expression"
|
|
99
|
-
: declaration.type === "FunctionDeclaration"
|
|
100
|
-
? "function"
|
|
101
|
-
: declaration.type === "ClassDeclaration"
|
|
102
|
-
? "class"
|
|
103
|
-
: declaration.type === "TSInterfaceDeclaration"
|
|
104
|
-
? "interface"
|
|
105
|
-
: declaration.type === "Identifier"
|
|
106
|
-
? (locals.get(nameOf(declaration) ?? "") ?? "expression")
|
|
107
|
-
: "expression";
|
|
108
|
-
site(statement, "default", "default", declares, false);
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
case "ExportAllDeclaration":
|
|
112
|
-
site(statement, nameOf(statement.exported) ?? "*", "namespace", "other", true);
|
|
113
|
-
break;
|
|
114
|
-
default:
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return found;
|
|
119
|
-
};
|
|
2
|
+
import { at, factsOfProgram, toRepoRelative, } from "./oxlint-api.js";
|
|
120
3
|
export const makeSurfaceRule = (policy) => ({
|
|
121
4
|
meta: {
|
|
122
5
|
type: "problem",
|
|
@@ -137,22 +20,23 @@ export const makeSurfaceRule = (policy) => ({
|
|
|
137
20
|
return selected.length > 0;
|
|
138
21
|
},
|
|
139
22
|
Program(node) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const found = surfaceOf(file, node.body);
|
|
143
|
-
const violations = evaluateSurface(selected, file, found.map((one) => one.site));
|
|
23
|
+
const sites = factsOfProgram(file, node).exportSites;
|
|
24
|
+
const violations = evaluateSurface(selected, file, sites);
|
|
144
25
|
// A per-site violation lands on the site's own node; a `count` one, on
|
|
145
26
|
// the program — it is about the file.
|
|
146
27
|
const used = new Set();
|
|
147
28
|
for (const violation of violations) {
|
|
148
29
|
if (policy.baseline.isBaselined(violation))
|
|
149
30
|
continue;
|
|
150
|
-
const
|
|
31
|
+
const site = violation.subject === null
|
|
151
32
|
? undefined
|
|
152
|
-
:
|
|
153
|
-
if (
|
|
154
|
-
used.add(
|
|
155
|
-
context.report({
|
|
33
|
+
: sites.find((one) => one.name === violation.subject && !used.has(one));
|
|
34
|
+
if (site !== undefined)
|
|
35
|
+
used.add(site);
|
|
36
|
+
context.report({
|
|
37
|
+
node: site === undefined ? node : at(site.node),
|
|
38
|
+
message: formatMessage(violation),
|
|
39
|
+
});
|
|
156
40
|
}
|
|
157
41
|
},
|
|
158
42
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface-rule.js","sourceRoot":"","sources":["../../src/surface-rule.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"surface-rule.js","sourceRoot":"","sources":["../../src/surface-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,aAAa,EAEb,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,EAAE,EACF,cAAc,EAId,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAoB,EAAc,EAAE,CAAC,CAAC;IACpE,IAAI,EAAE;QACJ,IAAI,EAAE,SAAkB;QACxB,IAAI,EAAE;YACJ,WAAW,EACT,iHAAiH;SACpH;QACD,MAAM,EAAE,EAAE;KACX;IAED,UAAU,CAAC,OAAoB;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,QAAQ,GAAuC,EAAE,CAAC;QAEtD,OAAO;YACL,MAAM;gBACJ,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACxC,QAAQ,GAAG,qBAAqB,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;gBAC5D,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7B,CAAC;YAED,OAAO,CAAC,IAAa;gBACnB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,WAAW,CAAC;gBACrD,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;gBAE1D,uEAAuE;gBACvE,sCAAsC;gBACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;gBACvC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC;wBAAE,SAAS;oBACrD,MAAM,IAAI,GACR,SAAS,CAAC,OAAO,KAAK,IAAI;wBACxB,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC5E,IAAI,IAAI,KAAK,SAAS;wBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACvC,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;wBAC/C,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC;qBAClC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodbones/oxlint",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "The oxlint plugin for @goodbones/core: the imports, exports, members, structure and surface families as five oxlint rules, evaluated in the editor and in CI from the same manifest the CLI reads.",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"effect": "4.0.0-beta.94",
|
|
47
|
-
"@goodbones/
|
|
48
|
-
"@goodbones/
|
|
47
|
+
"@goodbones/core": "0.1.0-beta.7",
|
|
48
|
+
"@goodbones/typescript": "0.1.0-beta.7"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"oxlint": ">=1.77.0"
|
package/src/exports-rule.ts
CHANGED
|
@@ -1,97 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type Binding,
|
|
3
2
|
evaluateSelectedBindings,
|
|
4
3
|
exportRulesSelecting,
|
|
5
4
|
formatMessage,
|
|
6
5
|
type LoadedPolicy,
|
|
7
6
|
type SelectedExportRule,
|
|
8
7
|
} from "@goodbones/core";
|
|
8
|
+
import type { ReadBinding, ReadEdge } from "@goodbones/typescript";
|
|
9
9
|
import * as Result from "effect/Result";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
at,
|
|
13
|
+
factsOfProgram,
|
|
13
14
|
type Fixer,
|
|
14
|
-
type ImportEqualsNode,
|
|
15
|
-
importEqualsSpecifierOf,
|
|
16
|
-
type ImportExpressionNode,
|
|
17
|
-
importExpressionSpecifierOf,
|
|
18
15
|
type OxlintRule,
|
|
19
|
-
type
|
|
20
|
-
requireSpecifierOf,
|
|
16
|
+
type Program,
|
|
21
17
|
type RuleContext,
|
|
22
18
|
toRepoRelative,
|
|
23
19
|
} from "./oxlint-api.js";
|
|
24
20
|
|
|
25
|
-
type NamedNode = ReportableNode & { readonly name?: unknown; readonly value?: unknown };
|
|
26
|
-
|
|
27
|
-
type SpecifierNode = ReportableNode & {
|
|
28
|
-
readonly type: string;
|
|
29
|
-
readonly imported?: NamedNode | null;
|
|
30
|
-
readonly local?: NamedNode | null;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
type DeclarationNode = ReportableNode & {
|
|
34
|
-
readonly source?: { readonly value?: unknown } | null;
|
|
35
|
-
readonly specifiers?: ReadonlyArray<SpecifierNode> | null;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const nameOf = (node: NamedNode | null | undefined): string | null => {
|
|
39
|
-
if (node === null || node === undefined) return null;
|
|
40
|
-
if (typeof node.name === "string") return node.name;
|
|
41
|
-
// `import { "a-b" as ab }` — a string-literal export name.
|
|
42
|
-
return typeof node.value === "string" ? node.value : null;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
type Bound = Binding & { readonly node: ReportableNode; readonly local: string };
|
|
46
|
-
|
|
47
|
-
// The whole module, as one binding. `export * from "m"`, `export * as ns from
|
|
48
|
-
// "m"`, `import x = require("m")`, `import("m")` and `require("m")` all carry
|
|
49
|
-
// every export of `m` at once, exactly as `import * as ns` does — and are the
|
|
50
|
-
// same way around a rule about a name. A side-effect import carries nothing.
|
|
51
|
-
const wholeModule = (node: ReportableNode, local = ""): Bound => ({
|
|
52
|
-
symbol: "*",
|
|
53
|
-
kind: "namespace",
|
|
54
|
-
node,
|
|
55
|
-
local,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
type ExportAllNode = DeclarationNode & { readonly exported?: NamedNode | null };
|
|
59
|
-
|
|
60
|
-
const boundOf = (specifier: SpecifierNode): Bound | null => {
|
|
61
|
-
const local = nameOf(specifier.local) ?? "";
|
|
62
|
-
switch (specifier.type) {
|
|
63
|
-
case "ImportSpecifier": {
|
|
64
|
-
const symbol = nameOf(specifier.imported);
|
|
65
|
-
return symbol === null ? null : { symbol, kind: "named", node: specifier, local };
|
|
66
|
-
}
|
|
67
|
-
case "ImportDefaultSpecifier":
|
|
68
|
-
return { symbol: "default", kind: "default", node: specifier, local };
|
|
69
|
-
case "ImportNamespaceSpecifier":
|
|
70
|
-
return { symbol: "*", kind: "namespace", node: specifier, local };
|
|
71
|
-
// `export { a } from "…"` — `local` is the name in the source module.
|
|
72
|
-
case "ExportSpecifier": {
|
|
73
|
-
const symbol = nameOf(specifier.local);
|
|
74
|
-
return symbol === null ? null : { symbol, kind: "named", node: specifier, local: symbol };
|
|
75
|
-
}
|
|
76
|
-
default:
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const specifierOf = (node: DeclarationNode): string | null => {
|
|
82
|
-
const value = node.source?.value;
|
|
83
|
-
return typeof value === "string" ? value : null;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const boundSpecifiers = (node: DeclarationNode): ReadonlyArray<Bound> =>
|
|
87
|
-
(node.specifiers ?? []).map(boundOf).filter((one): one is Bound => one !== null);
|
|
88
|
-
|
|
89
21
|
// `import { A, B as C } from "pkg"` becomes `import * as A from "pkg/A"` and
|
|
90
22
|
// `import * as C from "pkg/B"`. Only whole-declaration rewrites are offered: a
|
|
91
23
|
// declaration mixing restricted named imports with a default or namespace one
|
|
92
24
|
// would need comma surgery inside the braces, and a fix that is subtly wrong is
|
|
93
25
|
// worse than a diagnostic the author resolves by hand.
|
|
94
|
-
const subpathNamespaceImport = (specifier: string, bound: ReadonlyArray<
|
|
26
|
+
const subpathNamespaceImport = (specifier: string, bound: ReadonlyArray<ReadBinding>): string =>
|
|
95
27
|
bound
|
|
96
28
|
.map((binding) => `import * as ${binding.local} from "${specifier}/${binding.symbol}";`)
|
|
97
29
|
.join("\n");
|
|
@@ -111,20 +43,16 @@ export const makeExportsRule = (policy: LoadedPolicy): OxlintRule => ({
|
|
|
111
43
|
let importer = "";
|
|
112
44
|
let selected: ReadonlyArray<SelectedExportRule> = [];
|
|
113
45
|
|
|
114
|
-
// `
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
const check = (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
bound: ReadonlyArray<Bound>,
|
|
121
|
-
fixable: boolean,
|
|
122
|
-
): void => {
|
|
123
|
-
if (specifierValue === null || bound.length === 0) return;
|
|
46
|
+
// Only an `import` declaration can be rewritten into subpath namespace
|
|
47
|
+
// imports. `export *` and the whole-module forms are reported and left
|
|
48
|
+
// for the author.
|
|
49
|
+
const check = (edge: ReadEdge): void => {
|
|
50
|
+
const { bindings: bound, node, specifier } = edge;
|
|
51
|
+
if (bound.length === 0) return;
|
|
124
52
|
|
|
125
53
|
const outcome = evaluateSelectedBindings(selected, policy.resolver, {
|
|
126
54
|
importer,
|
|
127
|
-
specifier
|
|
55
|
+
specifier,
|
|
128
56
|
bindings: bound,
|
|
129
57
|
});
|
|
130
58
|
|
|
@@ -136,30 +64,30 @@ export const makeExportsRule = (policy: LoadedPolicy): OxlintRule => ({
|
|
|
136
64
|
|
|
137
65
|
for (const { bindings, rule, violation } of outcome.success) {
|
|
138
66
|
if (policy.baseline.isBaselined(violation)) continue;
|
|
139
|
-
const offending = bound.filter((one
|
|
67
|
+
const offending = bound.filter((one) =>
|
|
140
68
|
bindings.some((binding) => binding.symbol === one.symbol && binding.kind === one.kind),
|
|
141
69
|
);
|
|
142
70
|
// The rewrite is `import * as X from "pkg/<name>"`, which only means
|
|
143
71
|
// something for a named binding — a namespace one has no name to put
|
|
144
72
|
// in the subpath.
|
|
145
73
|
const rewritable =
|
|
146
|
-
|
|
74
|
+
edge.form === "import" &&
|
|
147
75
|
rule.fix === "subpath-namespace-import" &&
|
|
148
76
|
offending.length === bound.length &&
|
|
149
77
|
offending.every((one) => one.kind === "named");
|
|
150
78
|
|
|
151
79
|
if (rewritable) {
|
|
152
80
|
context.report({
|
|
153
|
-
node,
|
|
81
|
+
node: at(node),
|
|
154
82
|
message: formatMessage(violation),
|
|
155
83
|
fix: (fixer: Fixer) =>
|
|
156
|
-
fixer.replaceText(node, subpathNamespaceImport(
|
|
84
|
+
fixer.replaceText(at(node), subpathNamespaceImport(specifier, offending)),
|
|
157
85
|
});
|
|
158
86
|
continue;
|
|
159
87
|
}
|
|
160
88
|
|
|
161
89
|
context.report({
|
|
162
|
-
node: offending[0]?.node ?? node,
|
|
90
|
+
node: at(offending[0]?.node ?? node),
|
|
163
91
|
message: formatMessage(violation),
|
|
164
92
|
});
|
|
165
93
|
}
|
|
@@ -172,23 +100,8 @@ export const makeExportsRule = (policy: LoadedPolicy): OxlintRule => ({
|
|
|
172
100
|
selected = exportRulesSelecting(policy.exportRules, importer);
|
|
173
101
|
return selected.length > 0;
|
|
174
102
|
},
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
},
|
|
178
|
-
ExportNamedDeclaration(node: DeclarationNode) {
|
|
179
|
-
check(node, specifierOf(node), boundSpecifiers(node), false);
|
|
180
|
-
},
|
|
181
|
-
ExportAllDeclaration(node: ExportAllNode) {
|
|
182
|
-
check(node, specifierOf(node), [wholeModule(node, nameOf(node.exported) ?? "")], false);
|
|
183
|
-
},
|
|
184
|
-
ImportExpression(node: ImportExpressionNode) {
|
|
185
|
-
check(node, importExpressionSpecifierOf(node), [wholeModule(node)], false);
|
|
186
|
-
},
|
|
187
|
-
CallExpression(node: CallNode) {
|
|
188
|
-
check(node, requireSpecifierOf(node), [wholeModule(node)], false);
|
|
189
|
-
},
|
|
190
|
-
TSImportEqualsDeclaration(node: ImportEqualsNode) {
|
|
191
|
-
check(node, importEqualsSpecifierOf(node), [wholeModule(node)], false);
|
|
103
|
+
Program(node: Program) {
|
|
104
|
+
for (const edge of factsOfProgram(importer, node).edges) check(edge);
|
|
192
105
|
},
|
|
193
106
|
};
|
|
194
107
|
},
|
package/src/imports-rule.ts
CHANGED
|
@@ -5,20 +5,15 @@ import {
|
|
|
5
5
|
rulesSelecting,
|
|
6
6
|
type SelectedRule,
|
|
7
7
|
} from "@goodbones/core";
|
|
8
|
+
import type { ReadEdge } from "@goodbones/typescript";
|
|
8
9
|
import * as Result from "effect/Result";
|
|
9
10
|
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
importEqualsSpecifierOf,
|
|
14
|
-
type ImportExpressionNode,
|
|
15
|
-
importExpressionSpecifierOf,
|
|
12
|
+
at,
|
|
13
|
+
factsOfProgram,
|
|
16
14
|
type OxlintRule,
|
|
17
|
-
type
|
|
18
|
-
requireSpecifierOf,
|
|
15
|
+
type Program,
|
|
19
16
|
type RuleContext,
|
|
20
|
-
type SourceNode,
|
|
21
|
-
specifierOf,
|
|
22
17
|
toRepoRelative,
|
|
23
18
|
} from "./oxlint-api.js";
|
|
24
19
|
|
|
@@ -44,32 +39,29 @@ export const makeImportsRule = (policy: LoadedPolicy): OxlintRule => ({
|
|
|
44
39
|
let importer = "";
|
|
45
40
|
let selected: ReadonlyArray<SelectedRule> = [];
|
|
46
41
|
|
|
47
|
-
const check = (
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
const check = (edge: ReadEdge): void => {
|
|
43
|
+
const { specifier } = edge;
|
|
50
44
|
const outcome = evaluateSelectedEdge(selected, policy.resolver, { importer, specifier });
|
|
51
45
|
|
|
52
46
|
if (Result.isFailure(outcome)) {
|
|
53
47
|
if (policy.config.resolve.unresolved === "off") return;
|
|
54
48
|
if (policy.ignoreUnresolved.some((pattern) => pattern.test(specifier))) return;
|
|
55
|
-
context.report({
|
|
49
|
+
context.report({
|
|
50
|
+
node: at(edge.node),
|
|
51
|
+
message: unresolvedMessage(specifier, outcome.failure.detail),
|
|
52
|
+
});
|
|
56
53
|
return;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
for (const violation of outcome.success) {
|
|
60
57
|
if (policy.baseline.isBaselined(violation)) continue;
|
|
61
|
-
context.report({ node, message: formatMessage(violation) });
|
|
58
|
+
context.report({ node: at(edge.node), message: formatMessage(violation) });
|
|
62
59
|
}
|
|
63
60
|
};
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Every form that names a module is an edge. The CLI adapter reads the same
|
|
70
|
-
// five out of TypeScript's tree, and the parity suite holds the two to it: a
|
|
71
|
-
// `require` the plugin skipped would be a rule that enforces nothing under
|
|
72
|
-
// `oxlint` while failing under `architecture check`.
|
|
62
|
+
// Every form that names a module is an edge, and the pack's reader is
|
|
63
|
+
// what says which forms those are — the same reader the CLI reads a file
|
|
64
|
+
// through, so a form one host sees the other sees too.
|
|
73
65
|
return {
|
|
74
66
|
before() {
|
|
75
67
|
importer = toRepoRelative(policy.repoRoot, context.filename);
|
|
@@ -77,19 +69,8 @@ export const makeImportsRule = (policy: LoadedPolicy): OxlintRule => ({
|
|
|
77
69
|
selected = rulesSelecting(policy.importRules, importer);
|
|
78
70
|
return selected.length > 0;
|
|
79
71
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
ExportAllDeclaration: checkSource,
|
|
83
|
-
// `import("m")` with a literal argument. A computed one is not a fact a
|
|
84
|
-
// static policy can speak about, in either adapter.
|
|
85
|
-
ImportExpression(node: ImportExpressionNode) {
|
|
86
|
-
check(node, importExpressionSpecifierOf(node));
|
|
87
|
-
},
|
|
88
|
-
CallExpression(node: CallNode) {
|
|
89
|
-
check(node, requireSpecifierOf(node));
|
|
90
|
-
},
|
|
91
|
-
TSImportEqualsDeclaration(node: ImportEqualsNode) {
|
|
92
|
-
check(node, importEqualsSpecifierOf(node));
|
|
72
|
+
Program(node: Program) {
|
|
73
|
+
for (const edge of factsOfProgram(importer, node).edges) check(edge);
|
|
93
74
|
},
|
|
94
75
|
};
|
|
95
76
|
},
|