@goodbones/oxlint 0.1.0-beta.2 → 0.1.0-beta.20

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 (52) hide show
  1. package/build/dts/campaigns/host.d.ts +19 -0
  2. package/build/dts/campaigns/host.d.ts.map +1 -0
  3. package/build/dts/campaigns/live.d.ts +3 -0
  4. package/build/dts/campaigns/live.d.ts.map +1 -0
  5. package/build/dts/campaigns/none.d.ts +6 -0
  6. package/build/dts/campaigns/none.d.ts.map +1 -0
  7. package/build/dts/campaigns/rule.d.ts +6 -0
  8. package/build/dts/campaigns/rule.d.ts.map +1 -0
  9. package/build/dts/config-loader.d.ts +3 -3
  10. package/build/dts/config-loader.d.ts.map +1 -1
  11. package/build/dts/exports-rule.d.ts.map +1 -1
  12. package/build/dts/imports-rule.d.ts.map +1 -1
  13. package/build/dts/members-rule.d.ts.map +1 -1
  14. package/build/dts/oxlint-api.d.ts +4 -33
  15. package/build/dts/oxlint-api.d.ts.map +1 -1
  16. package/build/dts/plugin.d.ts +1 -0
  17. package/build/dts/plugin.d.ts.map +1 -1
  18. package/build/dts/surface-rule.d.ts.map +1 -1
  19. package/build/esm/campaigns/host.js +2 -0
  20. package/build/esm/campaigns/host.js.map +1 -0
  21. package/build/esm/campaigns/live.js +81 -0
  22. package/build/esm/campaigns/live.js.map +1 -0
  23. package/build/esm/campaigns/none.js +46 -0
  24. package/build/esm/campaigns/none.js.map +1 -0
  25. package/build/esm/campaigns/rule.js +163 -0
  26. package/build/esm/campaigns/rule.js.map +1 -0
  27. package/build/esm/config-loader.js +37 -5
  28. package/build/esm/config-loader.js.map +1 -1
  29. package/build/esm/exports-rule.js +15 -71
  30. package/build/esm/exports-rule.js.map +1 -1
  31. package/build/esm/imports-rule.js +14 -26
  32. package/build/esm/imports-rule.js.map +1 -1
  33. package/build/esm/members-rule.js +10 -112
  34. package/build/esm/members-rule.js.map +1 -1
  35. package/build/esm/oxlint-api.js +22 -20
  36. package/build/esm/oxlint-api.js.map +1 -1
  37. package/build/esm/plugin.js +4 -0
  38. package/build/esm/plugin.js.map +1 -1
  39. package/build/esm/surface-rule.js +11 -127
  40. package/build/esm/surface-rule.js.map +1 -1
  41. package/package.json +18 -6
  42. package/src/campaigns/host.ts +45 -0
  43. package/src/campaigns/live.ts +106 -0
  44. package/src/campaigns/none.ts +54 -0
  45. package/src/campaigns/rule.ts +243 -0
  46. package/src/config-loader.ts +42 -5
  47. package/src/exports-rule.ts +19 -106
  48. package/src/imports-rule.ts +16 -35
  49. package/src/members-rule.ts +11 -165
  50. package/src/oxlint-api.ts +31 -47
  51. package/src/plugin.ts +5 -0
  52. package/src/surface-rule.ts +15 -189
@@ -1,193 +1,21 @@
1
1
  import {
2
2
  type CompiledSurfaceRule,
3
- type DeclarationKind,
4
3
  evaluateSurface,
5
- type ExportSite,
6
4
  formatMessage,
7
5
  type LoadedPolicy,
8
6
  surfaceRulesSelecting,
9
7
  } from "@goodbones/core";
8
+ import type { ReadExportSite } from "@goodbones/typescript";
10
9
 
11
10
  import {
11
+ at,
12
+ factsOfProgram,
12
13
  type OxlintRule,
13
- type ReportableNode,
14
+ type Program,
14
15
  type RuleContext,
15
16
  toRepoRelative,
16
17
  } from "./oxlint-api.js";
17
18
 
18
- type NamedNode = ReportableNode & {
19
- readonly type?: string;
20
- readonly name?: unknown;
21
- readonly value?: unknown;
22
- };
23
-
24
- // A top-level statement, as far as this rule reads one. Every field is
25
- // optional because the visitor takes the whole `Node` union; each is checked
26
- // before use.
27
- type StatementNode = ReportableNode & {
28
- readonly type: string;
29
- readonly id?: NamedNode | null;
30
- readonly declaration?: StatementNode | null;
31
- readonly declarations?: ReadonlyArray<{ readonly id?: PatternNode | null }> | null;
32
- readonly specifiers?: ReadonlyArray<SpecifierNode> | null;
33
- readonly source?: { readonly value?: unknown } | null;
34
- readonly exported?: NamedNode | null;
35
- };
36
-
37
- type PatternNode = ReportableNode & {
38
- readonly type: string;
39
- readonly name?: unknown;
40
- readonly properties?: ReadonlyArray<{
41
- readonly value?: PatternNode | null;
42
- readonly argument?: PatternNode | null;
43
- }> | null;
44
- readonly elements?: ReadonlyArray<PatternNode | null> | null;
45
- readonly left?: PatternNode | null;
46
- readonly argument?: PatternNode | null;
47
- };
48
-
49
- type SpecifierNode = ReportableNode & {
50
- readonly exported?: NamedNode | null;
51
- readonly local?: NamedNode | null;
52
- };
53
-
54
- type ProgramNode = ReportableNode & { readonly body?: unknown };
55
-
56
- const isStatementList = (value: unknown): value is ReadonlyArray<StatementNode> =>
57
- Array.isArray(value);
58
-
59
- const nameOf = (node: NamedNode | null | undefined): string | null => {
60
- if (node === null || node === undefined) return null;
61
- if (typeof node.name === "string") return node.name;
62
- return typeof node.value === "string" ? node.value : null;
63
- };
64
-
65
- // The identifiers a binding pattern introduces.
66
- const patternNames = (pattern: PatternNode | null | undefined): ReadonlyArray<string> => {
67
- if (pattern === null || pattern === undefined) return [];
68
- switch (pattern.type) {
69
- case "Identifier":
70
- return typeof pattern.name === "string" ? [pattern.name] : [];
71
- case "ObjectPattern":
72
- return (pattern.properties ?? []).flatMap((property) =>
73
- patternNames(property.value ?? property.argument),
74
- );
75
- case "ArrayPattern":
76
- return (pattern.elements ?? []).flatMap(patternNames);
77
- case "AssignmentPattern":
78
- return patternNames(pattern.left);
79
- case "RestElement":
80
- return patternNames(pattern.argument);
81
- default:
82
- return [];
83
- }
84
- };
85
-
86
- // The names a declaration statement introduces, with what it declares them as.
87
- const declaredNamesOf = (
88
- statement: StatementNode,
89
- ): ReadonlyArray<readonly [string, DeclarationKind]> => {
90
- const named = (declares: DeclarationKind): ReadonlyArray<readonly [string, DeclarationKind]> => {
91
- const name = nameOf(statement.id);
92
- return name === null ? [] : [[name, declares]];
93
- };
94
- switch (statement.type) {
95
- case "FunctionDeclaration":
96
- return named("function");
97
- case "ClassDeclaration":
98
- return named("class");
99
- case "VariableDeclaration":
100
- return (statement.declarations ?? []).flatMap((declaration) =>
101
- patternNames(declaration.id).map((name) => [name, "variable"] as const),
102
- );
103
- case "TSTypeAliasDeclaration":
104
- return named("type");
105
- case "TSInterfaceDeclaration":
106
- return named("interface");
107
- case "TSEnumDeclaration":
108
- return named("enum");
109
- case "TSModuleDeclaration":
110
- return named("other");
111
- default:
112
- return [];
113
- }
114
- };
115
-
116
- type Found = { readonly site: ExportSite; readonly node: ReportableNode };
117
-
118
- // A file's surface, read off the program body rather than by visiting export
119
- // nodes: an `export` inside a namespace body is that namespace's, not the
120
- // module's, and reading the top level directly is what says so without a
121
- // parent pointer. Mirrors the CLI's `exportSitesOf`.
122
- const surfaceOf = (file: string, body: ReadonlyArray<StatementNode>): ReadonlyArray<Found> => {
123
- const locals = new Map<string, DeclarationKind>();
124
- for (const statement of body) {
125
- const declaration =
126
- statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration"
127
- ? statement.declaration
128
- : statement;
129
- if (declaration === null || declaration === undefined) continue;
130
- for (const [name, declares] of declaredNamesOf(declaration)) locals.set(name, declares);
131
- }
132
-
133
- const found: Array<Found> = [];
134
- const site = (
135
- node: ReportableNode,
136
- name: string,
137
- kind: ExportSite["kind"],
138
- declares: DeclarationKind,
139
- reexport: boolean,
140
- ): void => {
141
- found.push({ node, site: { file, name, kind, declares, reexport } });
142
- };
143
-
144
- for (const statement of body) {
145
- switch (statement.type) {
146
- case "ExportNamedDeclaration": {
147
- const declaration = statement.declaration;
148
- if (declaration !== null && declaration !== undefined) {
149
- for (const [name, declares] of declaredNamesOf(declaration)) {
150
- site(declaration, name, "named", declares, false);
151
- }
152
- break;
153
- }
154
- const reexport = typeof statement.source?.value === "string";
155
- for (const specifier of statement.specifiers ?? []) {
156
- const name = nameOf(specifier.exported);
157
- if (name === null) continue;
158
- const local = nameOf(specifier.local) ?? name;
159
- const declares = reexport ? "other" : (locals.get(local) ?? "other");
160
- site(specifier, name, name === "default" ? "default" : "named", declares, reexport);
161
- }
162
- break;
163
- }
164
- case "ExportDefaultDeclaration": {
165
- const declaration = statement.declaration;
166
- const declares: DeclarationKind =
167
- declaration === null || declaration === undefined
168
- ? "expression"
169
- : declaration.type === "FunctionDeclaration"
170
- ? "function"
171
- : declaration.type === "ClassDeclaration"
172
- ? "class"
173
- : declaration.type === "TSInterfaceDeclaration"
174
- ? "interface"
175
- : declaration.type === "Identifier"
176
- ? (locals.get(nameOf(declaration) ?? "") ?? "expression")
177
- : "expression";
178
- site(statement, "default", "default", declares, false);
179
- break;
180
- }
181
- case "ExportAllDeclaration":
182
- site(statement, nameOf(statement.exported) ?? "*", "namespace", "other", true);
183
- break;
184
- default:
185
- break;
186
- }
187
- }
188
- return found;
189
- };
190
-
191
19
  export const makeSurfaceRule = (policy: LoadedPolicy): OxlintRule => ({
192
20
  meta: {
193
21
  type: "problem" as const,
@@ -210,26 +38,24 @@ export const makeSurfaceRule = (policy: LoadedPolicy): OxlintRule => ({
210
38
  return selected.length > 0;
211
39
  },
212
40
 
213
- Program(node: ProgramNode) {
214
- if (!isStatementList(node.body)) return;
215
- const found = surfaceOf(file, node.body);
216
- const violations = evaluateSurface(
217
- selected,
218
- file,
219
- found.map((one) => one.site),
220
- );
41
+ Program(node: Program) {
42
+ const sites = factsOfProgram(file, node).exportSites;
43
+ const violations = evaluateSurface(selected, file, sites);
221
44
 
222
45
  // A per-site violation lands on the site's own node; a `count` one, on
223
46
  // the program — it is about the file.
224
- const used = new Set<Found>();
47
+ const used = new Set<ReadExportSite>();
225
48
  for (const violation of violations) {
226
49
  if (policy.baseline.isBaselined(violation)) continue;
227
- const at =
50
+ const site =
228
51
  violation.subject === null
229
52
  ? undefined
230
- : found.find((one) => one.site.name === violation.subject && !used.has(one));
231
- if (at !== undefined) used.add(at);
232
- context.report({ node: at?.node ?? node, message: formatMessage(violation) });
53
+ : sites.find((one) => one.name === violation.subject && !used.has(one));
54
+ if (site !== undefined) used.add(site);
55
+ context.report({
56
+ node: site === undefined ? node : at(site.node),
57
+ message: formatMessage(violation),
58
+ });
233
59
  }
234
60
  },
235
61
  };