oxlint-plugin-arch 0.2.1 → 0.2.2

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 (43) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/dist/index.js +1984 -0
  4. package/package.json +7 -8
  5. package/src/index.ts +0 -57
  6. package/src/rules/export-file-prefix.ts +0 -70
  7. package/src/rules/export-name-pattern.ts +0 -71
  8. package/src/rules/filename-export-name.ts +0 -86
  9. package/src/rules/filename-match.ts +0 -47
  10. package/src/rules/folder-prefix.ts +0 -73
  11. package/src/rules/no-extra-exports.ts +0 -60
  12. package/src/rules/no-extra-factory-keys.ts +0 -58
  13. package/src/rules/no-file-level-helpers.ts +0 -58
  14. package/src/rules/no-imported-type-alias.ts +0 -83
  15. package/src/rules/no-inline-schema-elements.ts +0 -70
  16. package/src/rules/no-local-schema-construction.ts +0 -162
  17. package/src/rules/no-rederive-schema.ts +0 -66
  18. package/src/rules/no-restricted-files.ts +0 -40
  19. package/src/rules/no-restricted-token.ts +0 -71
  20. package/src/rules/no-runtime-in-types.ts +0 -72
  21. package/src/rules/no-single-use-scalar-schema.ts +0 -164
  22. package/src/rules/no-top-level-functions.ts +0 -77
  23. package/src/rules/no-trivial-functions.ts +0 -56
  24. package/src/rules/no-unescaped-like.ts +0 -67
  25. package/src/rules/only-export-components.ts +0 -78
  26. package/src/rules/require-file-factory.ts +0 -67
  27. package/src/rules/require-object-params.ts +0 -32
  28. package/src/rules/require-orpc-output.ts +0 -64
  29. package/src/rules/require-paired-call.ts +0 -71
  30. package/src/rules/route-surface.ts +0 -62
  31. package/src/utils/helpers/ast.ts +0 -101
  32. package/src/utils/helpers/declarations.ts +0 -203
  33. package/src/utils/helpers/exports.ts +0 -158
  34. package/src/utils/helpers/factories.ts +0 -67
  35. package/src/utils/helpers/index.ts +0 -11
  36. package/src/utils/helpers/naming.ts +0 -89
  37. package/src/utils/helpers/options.ts +0 -5
  38. package/src/utils/helpers/orpc.ts +0 -31
  39. package/src/utils/helpers/react-components.ts +0 -146
  40. package/src/utils/helpers/routes.ts +0 -31
  41. package/src/utils/helpers/sanitizers.ts +0 -33
  42. package/src/utils/helpers/schemas.ts +0 -82
  43. package/src/utils/index.ts +0 -1
package/dist/index.js ADDED
@@ -0,0 +1,1984 @@
1
+ // src/index.ts
2
+ import { eslintCompatPlugin } from "@oxlint/plugins";
3
+
4
+ // src/rules/export-file-prefix.ts
5
+ import { defineRule } from "@oxlint/plugins";
6
+
7
+ // src/utils/helpers/ast.ts
8
+ function astIsNode(value) {
9
+ return value !== null && typeof value === "object" && typeof Reflect.get(value, "type") === "string";
10
+ }
11
+ function astVisit(node, ancestors, callback) {
12
+ if (Array.isArray(node)) {
13
+ for (const child of node) {
14
+ astVisit(child, ancestors, callback);
15
+ }
16
+ return;
17
+ }
18
+ if (!astIsNode(node)) {
19
+ return;
20
+ }
21
+ callback(node, ancestors);
22
+ for (const [key, value] of Object.entries(node)) {
23
+ if (!["parent", "type", "start", "end", "range", "loc"].includes(key)) {
24
+ astVisit(value, [...ancestors, node], callback);
25
+ }
26
+ }
27
+ }
28
+ function astContainsJsx(node) {
29
+ let found = false;
30
+ astVisit(node, [], (candidate) => {
31
+ if (candidate.type === "JSXElement" || candidate.type === "JSXFragment") {
32
+ found = true;
33
+ }
34
+ });
35
+ return found;
36
+ }
37
+ function astNearestFunction(ancestors) {
38
+ return [...ancestors].reverse().find((node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
39
+ }
40
+ function astDirectReturnExpressions(node) {
41
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
42
+ return [node.body];
43
+ }
44
+ const expressions = [];
45
+ astVisit(node.body, [node], (candidate, ancestors) => {
46
+ if (candidate.type === "ReturnStatement" && candidate.argument && astNearestFunction(ancestors) === node) {
47
+ expressions.push(candidate.argument);
48
+ }
49
+ });
50
+ return expressions;
51
+ }
52
+ function astStaticMemberName(node) {
53
+ return !node.computed && node.property.type === "Identifier" ? node.property.name : undefined;
54
+ }
55
+ function astCallName(node) {
56
+ if (node.callee.type === "Identifier") {
57
+ return node.callee.name;
58
+ }
59
+ return node.callee.type === "MemberExpression" ? astStaticMemberName(node.callee) : undefined;
60
+ }
61
+ function astImportedCallAliases(program) {
62
+ const aliases = new Map;
63
+ for (const statement of program.body) {
64
+ if (statement.type !== "ImportDeclaration") {
65
+ continue;
66
+ }
67
+ for (const specifier of statement.specifiers) {
68
+ if (specifier.type !== "ImportSpecifier") {
69
+ continue;
70
+ }
71
+ const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
72
+ if (imported !== specifier.local.name) {
73
+ aliases.set(specifier.local.name, imported);
74
+ }
75
+ }
76
+ }
77
+ return aliases;
78
+ }
79
+ function astResolvedCallName(node, aliases) {
80
+ const name = astCallName(node);
81
+ return name ? aliases.get(name) ?? name : undefined;
82
+ }
83
+ // src/utils/helpers/declarations.ts
84
+ function declarationsTopLevelUnexportedFunctions(program) {
85
+ return program.body.flatMap((statement) => {
86
+ if (statement.type === "FunctionDeclaration") {
87
+ return [{ name: statement.id?.name ?? "<anonymous>", node: statement }];
88
+ }
89
+ if (statement.type !== "VariableDeclaration") {
90
+ return [];
91
+ }
92
+ return statement.declarations.flatMap((item) => item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression" ? [{ name: item.id.type === "Identifier" ? item.id.name : "<anonymous>", node: item.init }] : []);
93
+ });
94
+ }
95
+ function declarationsFileLevelFunctionCandidates(program) {
96
+ const result = [];
97
+ for (const statement of program.body) {
98
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
99
+ if (declaration?.type === "FunctionDeclaration") {
100
+ result.push({ name: declaration.id?.name ?? "<anonymous>", node: declaration });
101
+ continue;
102
+ }
103
+ if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") {
104
+ continue;
105
+ }
106
+ for (const item of declaration.declarations) {
107
+ if (item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") {
108
+ result.push({
109
+ name: item.id.type === "Identifier" ? item.id.name : "<anonymous>",
110
+ node: declaration,
111
+ initializer: item.init
112
+ });
113
+ }
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ function declarationsIsObjectParam(node) {
119
+ if (node.type === "ObjectPattern") {
120
+ return true;
121
+ }
122
+ if (node.type === "TSParameterProperty") {
123
+ return declarationsIsObjectParam(node.parameter);
124
+ }
125
+ const annotation = node.typeAnnotation?.typeAnnotation;
126
+ return !annotation || annotation.type === "TSTypeReference" || annotation.type === "TSTypeLiteral" || annotation.type === "TSIntersectionType";
127
+ }
128
+ function declarationsFunctionName(node) {
129
+ if (node.type === "VariableDeclarator") {
130
+ return node.id.type === "Identifier" ? node.id.name : "<anonymous>";
131
+ }
132
+ return node.id?.name ?? "<anonymous>";
133
+ }
134
+ function declarationsIsRuntime(node) {
135
+ return node.type === "VariableDeclaration" || node.type === "FunctionDeclaration" || node.type === "ClassDeclaration" || node.type === "TSEnumDeclaration";
136
+ }
137
+ function declarationsRuntimeName(node) {
138
+ if (node.type === "VariableDeclaration") {
139
+ return node.declarations.map((item) => item.id.type === "Identifier" ? item.id.name : "<anonymous>").join(", ");
140
+ }
141
+ return node.id?.name ?? "<anonymous>";
142
+ }
143
+ function declarationsRuntimeKind(node) {
144
+ if (node.type === "ClassDeclaration") {
145
+ return "class";
146
+ }
147
+ if (node.type === "TSEnumDeclaration") {
148
+ return "enum";
149
+ }
150
+ if (node.type === "FunctionDeclaration") {
151
+ return "function";
152
+ }
153
+ return "variable";
154
+ }
155
+ function declarationsUnwrapExpression(node) {
156
+ if (node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") {
157
+ return declarationsUnwrapExpression(node.expression);
158
+ }
159
+ if (node.type === "ChainExpression") {
160
+ return declarationsUnwrapExpression(node.expression);
161
+ }
162
+ return node.type === "AwaitExpression" && node.argument ? declarationsUnwrapExpression(node.argument) : node;
163
+ }
164
+ function declarationsIsTrivialExpression(node) {
165
+ const value = declarationsUnwrapExpression(node);
166
+ if (value.type === "Identifier" || value.type === "ThisExpression" || value.type === "MetaProperty" || value.type.endsWith("Literal")) {
167
+ return true;
168
+ }
169
+ if (value.type === "TemplateLiteral") {
170
+ return value.expressions.length === 0;
171
+ }
172
+ if (value.type === "MemberExpression" || value.type === "CallExpression" || value.type === "NewExpression" || value.type === "TaggedTemplateExpression") {
173
+ return true;
174
+ }
175
+ return value.type === "LogicalExpression" && declarationsIsTrivialExpression(value.left) && declarationsIsTrivialExpression(value.right);
176
+ }
177
+ function declarationsIsTrivialFunction(node) {
178
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
179
+ return declarationsIsTrivialExpression(node.body);
180
+ }
181
+ if (!node.body || node.body.type !== "BlockStatement") {
182
+ return false;
183
+ }
184
+ const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
185
+ if (statements.length === 0) {
186
+ return true;
187
+ }
188
+ if (statements.length > 1) {
189
+ return false;
190
+ }
191
+ const statement = statements[0];
192
+ if (statement.type === "ReturnStatement") {
193
+ return !statement.argument || declarationsIsTrivialExpression(statement.argument);
194
+ }
195
+ return statement.type === "ExpressionStatement" && declarationsIsTrivialExpression(statement.expression);
196
+ }
197
+ function declarationsCollectNamed(root) {
198
+ const result = [];
199
+ astVisit(root, [], (node, ancestors) => {
200
+ if ((node.type === "FunctionDeclaration" || node.type === "ClassDeclaration" || node.type === "TSTypeAliasDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSEnumDeclaration") && node.id?.name) {
201
+ result.push({ name: node.id.name, node });
202
+ return;
203
+ }
204
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && !ancestors.some((ancestor) => ancestor.type === "ForStatement" || ancestor.type === "ForInStatement" || ancestor.type === "ForOfStatement")) {
205
+ result.push({ name: node.id.name, node });
206
+ }
207
+ });
208
+ return result;
209
+ }
210
+ function declarationsCollectFunctions(root) {
211
+ return declarationsCollectNamed(root).flatMap((item) => {
212
+ if (item.node.type === "FunctionDeclaration") {
213
+ return [{ name: item.name, node: item.node }];
214
+ }
215
+ if (item.node.type === "VariableDeclarator" && (item.node.init?.type === "FunctionExpression" || item.node.init?.type === "ArrowFunctionExpression")) {
216
+ return [{ name: item.name, node: item.node.init }];
217
+ }
218
+ return [];
219
+ });
220
+ }
221
+ // src/utils/helpers/exports.ts
222
+ function moduleExportName(node) {
223
+ return node.type === "Identifier" ? node.name : node.value;
224
+ }
225
+ function declarationBindings(declaration) {
226
+ if (!declaration || declaration.type === "TSModuleDeclaration") {
227
+ return [];
228
+ }
229
+ if (declaration.type === "VariableDeclaration") {
230
+ return declaration.declarations.map((item) => ({
231
+ exportedName: item.id.type === "Identifier" ? item.id.name : "<anonymous>",
232
+ localName: item.id.type === "Identifier" ? item.id.name : undefined,
233
+ node: declaration,
234
+ declaration,
235
+ initializer: item.init,
236
+ typeOnly: false,
237
+ default: false,
238
+ reExport: false
239
+ }));
240
+ }
241
+ const name = declaration.id?.name ?? "<anonymous>";
242
+ return [{
243
+ exportedName: name,
244
+ localName: declaration.id?.name,
245
+ node: declaration,
246
+ declaration,
247
+ typeOnly: declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration" || declaration.type === "TSDeclareFunction",
248
+ default: false,
249
+ reExport: false
250
+ }];
251
+ }
252
+ function localBindings(program) {
253
+ const result = new Map;
254
+ for (const statement of program.body) {
255
+ if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") {
256
+ continue;
257
+ }
258
+ if (statement.type.endsWith("Declaration")) {
259
+ for (const binding of declarationBindings(statement)) {
260
+ if (binding.localName) {
261
+ result.set(binding.localName, binding);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ return result;
267
+ }
268
+ function exportsCollect(program) {
269
+ const locals = localBindings(program);
270
+ return program.body.flatMap((statement) => {
271
+ if (statement.type === "ExportAllDeclaration") {
272
+ return [{
273
+ exportedName: "*",
274
+ node: statement,
275
+ typeOnly: statement.exportKind === "type",
276
+ default: false,
277
+ reExport: true
278
+ }];
279
+ }
280
+ if (statement.type === "ExportDefaultDeclaration") {
281
+ if (statement.declaration.type === "Identifier") {
282
+ const local = locals.get(statement.declaration.name);
283
+ return [{
284
+ ...local ?? {
285
+ exportedName: "default",
286
+ node: statement,
287
+ typeOnly: false,
288
+ default: true,
289
+ reExport: false
290
+ },
291
+ exportedName: "default",
292
+ localName: local?.localName ?? statement.declaration.name,
293
+ default: true
294
+ }];
295
+ }
296
+ if (statement.declaration.type.endsWith("Declaration")) {
297
+ const bindings = declarationBindings(statement.declaration);
298
+ return bindings.length > 0 ? bindings.map((binding) => ({ ...binding, exportedName: "default", default: true })) : [{ exportedName: "default", node: statement, typeOnly: false, default: true, reExport: false }];
299
+ }
300
+ return [{ exportedName: "default", node: statement, typeOnly: false, default: true, reExport: false }];
301
+ }
302
+ if (statement.type !== "ExportNamedDeclaration") {
303
+ return [];
304
+ }
305
+ return [
306
+ ...declarationBindings(statement.declaration),
307
+ ...statement.specifiers.map((specifier) => {
308
+ const localName = moduleExportName(specifier.local);
309
+ const local = locals.get(localName);
310
+ return {
311
+ ...local ?? {
312
+ exportedName: moduleExportName(specifier.exported),
313
+ node: specifier,
314
+ typeOnly: false,
315
+ default: false,
316
+ reExport: Boolean(statement.source)
317
+ },
318
+ exportedName: moduleExportName(specifier.exported),
319
+ localName,
320
+ node: local?.node ?? specifier,
321
+ typeOnly: specifier.exportKind === "type" || statement.exportKind === "type" || Boolean(local?.typeOnly),
322
+ default: moduleExportName(specifier.exported) === "default",
323
+ reExport: Boolean(statement.source)
324
+ };
325
+ })
326
+ ];
327
+ });
328
+ }
329
+ function exportsCollectFunctions(program) {
330
+ return exportsCollect(program).flatMap((binding) => {
331
+ const name = binding.localName ?? binding.exportedName;
332
+ if (binding.declaration?.type === "FunctionDeclaration") {
333
+ return [{ name, node: binding.declaration }];
334
+ }
335
+ return binding.initializer?.type === "FunctionExpression" || binding.initializer?.type === "ArrowFunctionExpression" ? [{ name, node: binding.initializer }] : [];
336
+ });
337
+ }
338
+ // src/utils/helpers/naming.ts
339
+ function namingPosixPath(filePath) {
340
+ return filePath.replaceAll("\\", "/");
341
+ }
342
+ function namingFileBasename(filePath) {
343
+ const normalized = namingPosixPath(filePath);
344
+ const slash = normalized.lastIndexOf("/");
345
+ return slash === -1 ? normalized : normalized.slice(slash + 1);
346
+ }
347
+ function namingDirSegments(filePath) {
348
+ const parts = namingPosixPath(filePath).split("/").filter(Boolean);
349
+ return parts.slice(0, -1);
350
+ }
351
+ function namingSegmentsAfter(filePath, after) {
352
+ const dirs = namingDirSegments(filePath);
353
+ const marker = after.split("/").filter(Boolean);
354
+ if (marker.length === 0) {
355
+ return;
356
+ }
357
+ for (let index = dirs.length - marker.length;index >= 0; index -= 1) {
358
+ if (marker.every((part, offset) => dirs[index + offset] === part)) {
359
+ return dirs.slice(index + marker.length);
360
+ }
361
+ }
362
+ }
363
+ function namingFolderPrefixes(folders, separator, singularize) {
364
+ const joined = folders.join(separator);
365
+ const last = folders.at(-1) ?? "";
366
+ const prefixes = [joined];
367
+ if (singularize === "trailing-s" && last.endsWith("s") && last.length > 1) {
368
+ prefixes.push([...folders.slice(0, -1), last.slice(0, -1)].join(separator));
369
+ }
370
+ return prefixes.filter(Boolean);
371
+ }
372
+ function namingPascalCase(value) {
373
+ return value.split(/[-_\s.]+/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
374
+ }
375
+ function namingCamelCase(value) {
376
+ const next = namingPascalCase(value);
377
+ return `${next.charAt(0).toLowerCase()}${next.slice(1)}`;
378
+ }
379
+ function namingMatchTemplate(basename, template, placeholderPattern = "[a-z0-9-]+") {
380
+ let pattern = "";
381
+ let index = 0;
382
+ while (index < template.length) {
383
+ const char = template[index];
384
+ if (char === "{") {
385
+ const end = template.indexOf("}", index);
386
+ if (end === -1) {
387
+ return null;
388
+ }
389
+ const key = template.slice(index + 1, end);
390
+ pattern += `(?<${key}>${placeholderPattern})`;
391
+ index = end + 1;
392
+ continue;
393
+ }
394
+ pattern += /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
395
+ index += 1;
396
+ }
397
+ return new RegExp(`^${pattern}$`).exec(basename)?.groups ?? null;
398
+ }
399
+
400
+ // src/utils/helpers/factories.ts
401
+ function factoriesExpandTemplate(template, groups) {
402
+ return template.replaceAll(/\{([^}]+)\}/g, (placeholder, key) => {
403
+ const group = groups[key.toLowerCase()];
404
+ if (!group) {
405
+ return placeholder;
406
+ }
407
+ return /^[A-Z]/.test(key) ? namingPascalCase(group) : group;
408
+ });
409
+ }
410
+ function factoriesCollectTopLevel(program, pattern) {
411
+ return program.body.flatMap((statement) => {
412
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
413
+ if (declaration?.type === "FunctionDeclaration") {
414
+ const name = declaration.id?.name ?? "<anonymous>";
415
+ return pattern.test(name) ? [{ name, node: declaration }] : [];
416
+ }
417
+ if (declaration?.type !== "VariableDeclaration") {
418
+ return [];
419
+ }
420
+ return declaration.declarations.flatMap((item) => item.id.type === "Identifier" && pattern.test(item.id.name) && (item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") ? [{ name: item.id.name, node: item.init }] : []);
421
+ });
422
+ }
423
+ function factoriesDirectlyReturnedObjects(factory) {
424
+ if (factory.node.type === "ArrowFunctionExpression" && factory.node.body.type === "ObjectExpression") {
425
+ return [factory.node.body];
426
+ }
427
+ const result = [];
428
+ astVisit(factory.node.body, [factory.node], (node, ancestors) => {
429
+ if (node.type === "ReturnStatement" && node.argument?.type === "ObjectExpression" && astNearestFunction(ancestors) === factory.node) {
430
+ result.push(node.argument);
431
+ }
432
+ });
433
+ return result;
434
+ }
435
+ function factoriesObjectPropertyName(property) {
436
+ if (property.type === "SpreadElement" || property.computed) {
437
+ return;
438
+ }
439
+ if (property.key.type === "Identifier") {
440
+ return property.key.name;
441
+ }
442
+ return property.key.type === "Literal" && typeof property.key.value === "string" ? property.key.value : undefined;
443
+ }
444
+ // src/utils/helpers/options.ts
445
+ function optionsFirst(context, fallback) {
446
+ return context.options[0] ?? fallback;
447
+ }
448
+ // src/utils/helpers/orpc.ts
449
+ function orpcFindChainCall(expression, method) {
450
+ if (expression.type === "CallExpression") {
451
+ if (expression.callee.type === "MemberExpression" && astStaticMemberName(expression.callee) === method) {
452
+ return expression;
453
+ }
454
+ if (expression.callee.type === "MemberExpression") {
455
+ return orpcFindChainCall(expression.callee.object, method);
456
+ }
457
+ }
458
+ if (expression.type === "MemberExpression") {
459
+ return orpcFindChainCall(expression.object, method);
460
+ }
461
+ if (expression.type === "ChainExpression") {
462
+ return orpcFindChainCall(expression.expression, method);
463
+ }
464
+ }
465
+ function orpcIsNamedOrComposedArgument(argument, composers) {
466
+ if (argument?.type === "Identifier") {
467
+ return true;
468
+ }
469
+ return argument?.type === "CallExpression" && argument.callee.type === "Identifier" && composers.has(argument.callee.name) && argument.arguments[0]?.type === "Identifier";
470
+ }
471
+ // src/utils/helpers/react-components.ts
472
+ function reactComponentsExportCandidates(program) {
473
+ return exportsCollect(program).map((binding) => ({
474
+ ...binding,
475
+ name: binding.localName ?? binding.exportedName
476
+ }));
477
+ }
478
+ function importedPropsWithChildren(program) {
479
+ return new Set(program.body.flatMap((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react" ? statement.specifiers.flatMap((specifier) => specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "PropsWithChildren" ? [specifier.local.name] : []) : []));
480
+ }
481
+ function containsNamedTypeReference(node, names) {
482
+ let found = false;
483
+ astVisit(node, [], (candidate) => {
484
+ if (candidate.type === "TSTypeReference" && candidate.typeName.type === "Identifier" && names.has(candidate.typeName.name)) {
485
+ found = true;
486
+ }
487
+ });
488
+ return found;
489
+ }
490
+ function returnsReactChildren(program, node, expressions) {
491
+ const returnedNames = new Set(expressions.flatMap((expression) => expression.type === "Identifier" ? [expression.name] : []));
492
+ const propsWithChildren = importedPropsWithChildren(program);
493
+ return node.params.some((param) => {
494
+ if (param.type !== "ObjectPattern") {
495
+ return false;
496
+ }
497
+ const returnsChild = param.properties.some((property) => property.type === "Property" && property.value.type === "Identifier" && returnedNames.has(property.value.name));
498
+ return returnsChild && containsNamedTypeReference(param.typeAnnotation, propsWithChildren);
499
+ });
500
+ }
501
+ function reactComponentsFunctionReturnsOutput(program, node) {
502
+ const expressions = astDirectReturnExpressions(node);
503
+ return expressions.some((expression) => astContainsJsx(expression) || expression.type === "Literal" && expression.value === null) || returnsReactChildren(program, node, expressions);
504
+ }
505
+ function functionReturnsLocalComponent(program, node) {
506
+ const componentNames = new Set;
507
+ astVisit(node.body, [node], (candidate, ancestors) => {
508
+ if (candidate.type === "FunctionDeclaration" && candidate !== node && candidate.id?.name && /^[A-Z]/.test(candidate.id.name) && astNearestFunction(ancestors) === node && reactComponentsFunctionReturnsOutput(program, candidate)) {
509
+ componentNames.add(candidate.id.name);
510
+ }
511
+ });
512
+ return astDirectReturnExpressions(node).some((expression) => expression.type === "Identifier" && componentNames.has(expression.name));
513
+ }
514
+ function componentFactoryNames(program) {
515
+ return new Set(program.body.flatMap((statement) => statement.type === "FunctionDeclaration" && statement.id?.name && functionReturnsLocalComponent(program, statement) ? [statement.id.name] : []));
516
+ }
517
+ function isComponentInitializer(program, initializer) {
518
+ if (!initializer) {
519
+ return false;
520
+ }
521
+ if (initializer.type === "ArrowFunctionExpression" || initializer.type === "FunctionExpression") {
522
+ return reactComponentsFunctionReturnsOutput(program, initializer);
523
+ }
524
+ if (initializer.type !== "CallExpression") {
525
+ return false;
526
+ }
527
+ const name = astCallName(initializer);
528
+ if (name === "forwardRef" || name === "memo") {
529
+ return initializer.arguments.some((argument) => argument.type !== "SpreadElement" && isComponentInitializer(program, argument));
530
+ }
531
+ if (name === "lazy") {
532
+ return initializer.arguments.some((argument) => argument.type === "ArrowFunctionExpression" || argument.type === "FunctionExpression");
533
+ }
534
+ return Boolean(name?.startsWith("create") && componentFactoryNames(program).has(name));
535
+ }
536
+ function reactComponentsIsLike(program, candidate) {
537
+ if (!/^[A-Z]/.test(candidate.name)) {
538
+ return false;
539
+ }
540
+ const declaration = candidate.declaration ?? candidate.node;
541
+ if (declaration.type === "FunctionDeclaration") {
542
+ return reactComponentsFunctionReturnsOutput(program, declaration);
543
+ }
544
+ return declaration.type === "VariableDeclaration" && isComponentInitializer(program, candidate.initializer);
545
+ }
546
+ function reactComponentsPascalFromBasename(basename) {
547
+ return basename.replace(/\.(tsx?|jsx?)$/, "").split(/[-.]/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
548
+ }
549
+ // src/utils/helpers/routes.ts
550
+ function routesInspectFunction(context, item, bannedHooks, banIntrinsicJsx) {
551
+ astVisit(item.node.body, [item.node], (node) => {
552
+ if (banIntrinsicJsx && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && /^[a-z]/.test(node.name.name)) {
553
+ context.report({
554
+ node,
555
+ messageId: "hostJsx",
556
+ data: { name: item.name }
557
+ });
558
+ }
559
+ if (node.type === "CallExpression") {
560
+ const name = astCallName(node);
561
+ if (name && bannedHooks.has(name)) {
562
+ context.report({
563
+ node,
564
+ messageId: "bannedHook",
565
+ data: { hook: name, name: item.name }
566
+ });
567
+ }
568
+ }
569
+ });
570
+ }
571
+ // src/utils/helpers/sanitizers.ts
572
+ function sanitizersCallsConfigured(node, names) {
573
+ let found = false;
574
+ astVisit(node, [], (candidate) => {
575
+ if (candidate.type === "CallExpression" && candidate.callee.type === "Identifier" && names.has(candidate.callee.name)) {
576
+ found = true;
577
+ }
578
+ });
579
+ return found;
580
+ }
581
+ function sanitizersConfiguredBindings(program, names) {
582
+ const result = new Set;
583
+ for (const statement of program.body) {
584
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
585
+ if (declaration?.type !== "VariableDeclaration") {
586
+ continue;
587
+ }
588
+ for (const item of declaration.declarations) {
589
+ if (item.id.type === "Identifier" && sanitizersCallsConfigured(item.init, names)) {
590
+ result.add(item.id.name);
591
+ }
592
+ }
593
+ }
594
+ return result;
595
+ }
596
+ // src/utils/helpers/schemas.ts
597
+ function schemasIsRuntimeSpecifier(specifier) {
598
+ return specifier.type !== "ImportSpecifier" || specifier.importKind !== "type";
599
+ }
600
+ function schemasMemberRootName(node) {
601
+ if (node.object.type === "Identifier") {
602
+ return node.object.name;
603
+ }
604
+ return node.object.type === "MemberExpression" ? schemasMemberRootName(node.object) : undefined;
605
+ }
606
+ function schemasIsPackage(source, packages) {
607
+ return packages.has(source) || [...packages].some((pkg) => source.startsWith(`${pkg}/`));
608
+ }
609
+ function schemasIsCall(node, namespaces) {
610
+ return node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && namespaces.has(node.callee.object.name);
611
+ }
612
+ function schemasRuntimeImportLocals(node) {
613
+ return node.importKind === "type" ? [] : node.specifiers.flatMap((specifier) => schemasIsRuntimeSpecifier(specifier) ? [specifier.local.name] : []);
614
+ }
615
+ function schemasIsRuntimeImport(node, packages) {
616
+ return schemasIsPackage(node.source.value, packages) && (node.specifiers.length === 0 ? node.importKind !== "type" : schemasRuntimeImportLocals(node).length > 0);
617
+ }
618
+ function schemasIsRuntimeReexport(node, packages) {
619
+ if (!node.source || !schemasIsPackage(node.source.value, packages) || node.exportKind === "type") {
620
+ return false;
621
+ }
622
+ return node.type === "ExportAllDeclaration" || node.specifiers.length === 0 || node.specifiers.some((specifier) => specifier.exportKind !== "type");
623
+ }
624
+ function schemasIsConstruction(node, constructors) {
625
+ if (node.callee.type === "Identifier") {
626
+ return constructors.has(node.callee.name);
627
+ }
628
+ if (node.callee.type !== "MemberExpression") {
629
+ return false;
630
+ }
631
+ const root = schemasMemberRootName(node.callee);
632
+ return Boolean(root && constructors.has(root));
633
+ }
634
+ function schemasElements(node) {
635
+ return node.arguments.flatMap((argument) => argument.type === "ArrayExpression" ? argument.elements.filter((element) => element !== null) : [argument]);
636
+ }
637
+ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMethods) {
638
+ if (node.type === "Identifier") {
639
+ return true;
640
+ }
641
+ if (!allowScalars || node.type !== "CallExpression" || !schemasIsCall(node, namespaces)) {
642
+ return false;
643
+ }
644
+ const name = node.callee.type === "MemberExpression" ? astStaticMemberName(node.callee) : undefined;
645
+ return Boolean(name && !structuralMethods.has(name));
646
+ }
647
+ function schemasTypeOperator(node, namespaces, operators) {
648
+ return node.typeName.type === "TSQualifiedName" && node.typeName.left.type === "Identifier" && namespaces.has(node.typeName.left.name) && operators.has(node.typeName.right.name) ? node.typeName.right.name : undefined;
649
+ }
650
+ // src/rules/export-file-prefix.ts
651
+ var exportFilePrefix = defineRule({
652
+ meta: {
653
+ type: "problem",
654
+ schema: [{
655
+ type: "object",
656
+ additionalProperties: false,
657
+ properties: {
658
+ stem: { type: "string", enum: ["before-first-dot", "full-basename"] },
659
+ normalize: { type: "string", enum: ["remove-separators", "none"] },
660
+ allDeclarations: { type: "boolean" },
661
+ allowPattern: { type: "string" }
662
+ }
663
+ }],
664
+ messages: {
665
+ prefix: "'{{name}}' must start with file prefix '{{prefix}}'."
666
+ }
667
+ },
668
+ createOnce(context) {
669
+ return {
670
+ Program(program) {
671
+ const options2 = optionsFirst(context, {
672
+ stem: "before-first-dot",
673
+ normalize: "remove-separators"
674
+ });
675
+ const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
676
+ const stem = options2.stem === "full-basename" ? basename : basename.split(".")[0] ?? "";
677
+ const prefix = options2.normalize === "none" ? stem : stem.replaceAll(/[-_]/g, "").toLowerCase();
678
+ const allowed = options2.allowPattern ? new RegExp(options2.allowPattern) : null;
679
+ const names = options2.allDeclarations ? declarationsCollectNamed(program) : exportsCollect(program).map((binding) => ({
680
+ name: binding.localName ?? binding.exportedName,
681
+ node: binding.node
682
+ }));
683
+ const seen = new Set;
684
+ for (const item of names) {
685
+ const key = `${item.name}:${item.node.start}:${item.node.end}`;
686
+ const comparableName = options2.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
687
+ if (seen.has(key) || allowed?.test(item.name) || comparableName.startsWith(prefix)) {
688
+ continue;
689
+ }
690
+ seen.add(key);
691
+ context.report({
692
+ node: item.node,
693
+ messageId: "prefix",
694
+ data: { name: item.name, prefix }
695
+ });
696
+ }
697
+ }
698
+ };
699
+ }
700
+ });
701
+
702
+ // src/rules/export-name-pattern.ts
703
+ import { defineRule as defineRule2 } from "@oxlint/plugins";
704
+ var exportNamePattern = defineRule2({
705
+ meta: {
706
+ type: "problem",
707
+ schema: [{
708
+ type: "object",
709
+ additionalProperties: false,
710
+ properties: {
711
+ pattern: { type: "string" },
712
+ flags: { type: "string" },
713
+ ignoreTypeExports: { type: "boolean" },
714
+ allDeclarations: { type: "boolean" },
715
+ allowPattern: { type: "string" }
716
+ },
717
+ required: ["pattern"]
718
+ }],
719
+ messages: {
720
+ pattern: "'{{name}}' must match {{pattern}}."
721
+ }
722
+ },
723
+ createOnce(context) {
724
+ return {
725
+ Program(program) {
726
+ const {
727
+ pattern,
728
+ flags,
729
+ ignoreTypeExports = false,
730
+ allDeclarations = false,
731
+ allowPattern
732
+ } = optionsFirst(context);
733
+ const allowed = allowPattern ? new RegExp(allowPattern) : null;
734
+ const names = allDeclarations ? declarationsCollectNamed(program) : exportsCollect(program).flatMap((binding) => ignoreTypeExports && binding.typeOnly ? [] : [{ name: binding.localName ?? binding.exportedName, node: binding.node }]);
735
+ const seen = new Set;
736
+ for (const item of names) {
737
+ const key = `${item.name}:${item.node.start}:${item.node.end}`;
738
+ if (seen.has(key) || allowed?.test(item.name) || new RegExp(pattern, flags).test(item.name)) {
739
+ continue;
740
+ }
741
+ seen.add(key);
742
+ context.report({
743
+ node: item.node,
744
+ messageId: "pattern",
745
+ data: { name: item.name, pattern }
746
+ });
747
+ }
748
+ }
749
+ };
750
+ }
751
+ });
752
+
753
+ // src/rules/filename-export-name.ts
754
+ import { defineRule as defineRule3 } from "@oxlint/plugins";
755
+ var filenameExportName = defineRule3({
756
+ meta: {
757
+ type: "problem",
758
+ schema: [{
759
+ type: "object",
760
+ additionalProperties: false,
761
+ properties: {
762
+ file: { type: "string" },
763
+ export: { type: "string" },
764
+ mode: { type: "string", enum: ["all", "some"] },
765
+ placeholderPattern: { type: "string" },
766
+ allDeclarations: { type: "boolean" }
767
+ },
768
+ required: ["file", "export"]
769
+ }],
770
+ messages: {
771
+ mismatch: "Function '{{actual}}' must match '{{expected}}'."
772
+ }
773
+ },
774
+ createOnce(context) {
775
+ return {
776
+ Program(program) {
777
+ const {
778
+ file,
779
+ export: exportTemplate,
780
+ mode = "all",
781
+ placeholderPattern,
782
+ allDeclarations = false
783
+ } = optionsFirst(context);
784
+ const groups = namingMatchTemplate(namingFileBasename(context.filename), file, placeholderPattern);
785
+ if (!groups) {
786
+ return;
787
+ }
788
+ const expected = factoriesExpandTemplate(exportTemplate, groups);
789
+ const functions = allDeclarations ? declarationsCollectFunctions(program) : exportsCollectFunctions(program);
790
+ if (mode === "some") {
791
+ if (!functions.some((item) => item.name === expected)) {
792
+ const first = functions.at(0);
793
+ context.report({
794
+ node: first?.node ?? program,
795
+ messageId: "mismatch",
796
+ data: { actual: first?.name ?? "<none>", expected }
797
+ });
798
+ }
799
+ return;
800
+ }
801
+ for (const item of functions) {
802
+ if (item.name !== expected) {
803
+ context.report({
804
+ node: item.node,
805
+ messageId: "mismatch",
806
+ data: { actual: item.name, expected }
807
+ });
808
+ }
809
+ }
810
+ }
811
+ };
812
+ }
813
+ });
814
+
815
+ // src/rules/filename-match.ts
816
+ import { defineRule as defineRule4 } from "@oxlint/plugins";
817
+ var filenameMatch = defineRule4({
818
+ meta: {
819
+ type: "problem",
820
+ schema: [{
821
+ type: "object",
822
+ additionalProperties: false,
823
+ properties: {
824
+ pattern: { type: "string" },
825
+ message: { type: "string" },
826
+ flags: { type: "string" }
827
+ },
828
+ required: ["pattern", "message"]
829
+ }],
830
+ messages: {
831
+ mismatch: "{{message}}"
832
+ }
833
+ },
834
+ createOnce(context) {
835
+ return {
836
+ Program(program) {
837
+ const { pattern, message, flags } = optionsFirst(context);
838
+ if (!new RegExp(pattern, flags).test(namingFileBasename(context.filename))) {
839
+ context.report({
840
+ node: program,
841
+ messageId: "mismatch",
842
+ data: { message }
843
+ });
844
+ }
845
+ }
846
+ };
847
+ }
848
+ });
849
+
850
+ // src/rules/folder-prefix.ts
851
+ import { defineRule as defineRule5 } from "@oxlint/plugins";
852
+ var folderPrefix = defineRule5({
853
+ meta: {
854
+ type: "problem",
855
+ schema: [{
856
+ type: "object",
857
+ additionalProperties: false,
858
+ properties: {
859
+ singularize: { type: "string", enum: ["none", "trailing-s"] },
860
+ separators: { type: "array", items: { type: "string" } },
861
+ after: { type: "string" }
862
+ }
863
+ }],
864
+ messages: {
865
+ prefix: "File '{{file}}' must start with folder name '{{folder}}' or an allowed singular form."
866
+ }
867
+ },
868
+ createOnce(context) {
869
+ return {
870
+ Program(program) {
871
+ const {
872
+ singularize = "trailing-s",
873
+ separators = ["-"],
874
+ after
875
+ } = optionsFirst(context, {});
876
+ const filename = namingPosixPath(context.filename);
877
+ const folders = after ? namingSegmentsAfter(filename, after) : [namingDirSegments(filename).at(-1) ?? ""];
878
+ if (!folders) {
879
+ return;
880
+ }
881
+ const resolved = folders.length > 0 ? folders : [namingDirSegments(filename).at(-1) ?? ""];
882
+ const separator = separators[0] ?? "-";
883
+ const prefixes = namingFolderPrefixes(resolved, separator, singularize);
884
+ const file = namingFileBasename(filename);
885
+ const stem = file.replace(/\.(tsx?|jsx?)$/, "");
886
+ const valid = prefixes.some((prefix) => stem === prefix || separators.some((item) => stem.startsWith(`${prefix}${item}`)));
887
+ if (!valid) {
888
+ context.report({
889
+ node: program,
890
+ messageId: "prefix",
891
+ data: { file, folder: prefixes[0] ?? "" }
892
+ });
893
+ }
894
+ }
895
+ };
896
+ }
897
+ });
898
+
899
+ // src/rules/no-extra-exports.ts
900
+ import { defineRule as defineRule6 } from "@oxlint/plugins";
901
+ var noExtraExports = defineRule6({
902
+ meta: {
903
+ type: "problem",
904
+ schema: [{
905
+ type: "object",
906
+ additionalProperties: false,
907
+ properties: {
908
+ names: { type: "array", items: { type: "string" } },
909
+ domainStem: { type: "string", enum: ["before-first-dot", "full-basename"] },
910
+ allowTypeExports: { type: "boolean" }
911
+ },
912
+ required: ["names"]
913
+ }],
914
+ messages: {
915
+ extraExport: "Export '{{name}}' is not allowed; expected one of: {{allowed}}."
916
+ }
917
+ },
918
+ createOnce(context) {
919
+ return {
920
+ Program(program) {
921
+ const options2 = optionsFirst(context);
922
+ const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
923
+ const domain = options2.domainStem === "full-basename" ? basename : basename.split(".")[0] ?? "";
924
+ const allowed = options2.names.map((template) => template.replaceAll("{Domain}", namingPascalCase(domain)).replaceAll("{domain}", namingCamelCase(domain)));
925
+ for (const binding of exportsCollect(program)) {
926
+ if (options2.allowTypeExports && binding.typeOnly) {
927
+ continue;
928
+ }
929
+ const name = binding.localName ?? binding.exportedName;
930
+ if (!allowed.includes(name)) {
931
+ context.report({
932
+ node: binding.node,
933
+ messageId: "extraExport",
934
+ data: { name, allowed: allowed.join(", ") }
935
+ });
936
+ }
937
+ }
938
+ }
939
+ };
940
+ }
941
+ });
942
+
943
+ // src/rules/no-extra-factory-keys.ts
944
+ import { defineRule as defineRule7 } from "@oxlint/plugins";
945
+ var noExtraFactoryKeys = defineRule7({
946
+ meta: {
947
+ type: "problem",
948
+ schema: [{
949
+ type: "object",
950
+ additionalProperties: false,
951
+ properties: {
952
+ keys: { type: "array", items: { type: "string" } },
953
+ factoryPattern: { type: "string" }
954
+ },
955
+ required: ["keys"]
956
+ }],
957
+ messages: {
958
+ extraKey: "Factory '{{factory}}' must not return key '{{key}}'; allowed keys: {{allowed}}."
959
+ }
960
+ },
961
+ createOnce(context) {
962
+ return {
963
+ Program(program) {
964
+ const { keys, factoryPattern = "^make[A-Z]" } = optionsFirst(context);
965
+ for (const factory of factoriesCollectTopLevel(program, new RegExp(factoryPattern))) {
966
+ for (const object of factoriesDirectlyReturnedObjects(factory)) {
967
+ for (const property of object.properties) {
968
+ const key = factoriesObjectPropertyName(property);
969
+ if (!key || !keys.includes(key)) {
970
+ context.report({
971
+ node: property,
972
+ messageId: "extraKey",
973
+ data: { factory: factory.name, key: key ?? "<computed or spread>", allowed: keys.join(", ") }
974
+ });
975
+ }
976
+ }
977
+ }
978
+ }
979
+ }
980
+ };
981
+ }
982
+ });
983
+
984
+ // src/rules/no-file-level-helpers.ts
985
+ import { defineRule as defineRule8 } from "@oxlint/plugins";
986
+ var noFileLevelHelpers = defineRule8({
987
+ meta: {
988
+ type: "problem",
989
+ schema: [{
990
+ type: "object",
991
+ additionalProperties: false,
992
+ properties: {
993
+ allowPattern: { type: "string" },
994
+ detectComponents: { type: "boolean" },
995
+ hookPattern: { type: "string" }
996
+ }
997
+ }],
998
+ messages: {
999
+ helper: "File-level helper {{name}} must move inside its owning component or hook."
1000
+ }
1001
+ },
1002
+ createOnce(context) {
1003
+ return {
1004
+ Program(program) {
1005
+ const {
1006
+ allowPattern,
1007
+ detectComponents = true,
1008
+ hookPattern = "^use[A-Z]"
1009
+ } = optionsFirst(context, {});
1010
+ const allowed = allowPattern ? new RegExp(allowPattern) : null;
1011
+ const hooks = new RegExp(hookPattern);
1012
+ for (const candidate of declarationsFileLevelFunctionCandidates(program)) {
1013
+ if (detectComponents && reactComponentsIsLike(program, candidate) || hooks.test(candidate.name) || allowed?.test(candidate.name)) {
1014
+ continue;
1015
+ }
1016
+ context.report({
1017
+ node: candidate.node,
1018
+ messageId: "helper",
1019
+ data: { name: candidate.name }
1020
+ });
1021
+ }
1022
+ }
1023
+ };
1024
+ }
1025
+ });
1026
+
1027
+ // src/rules/no-imported-type-alias.ts
1028
+ import { defineRule as defineRule9 } from "@oxlint/plugins";
1029
+ function importedAliasTarget(declaration, imports) {
1030
+ if (declaration?.type !== "TSTypeAliasDeclaration" || declaration.typeAnnotation.type !== "TSTypeReference" || declaration.typeAnnotation.typeName.type !== "Identifier" || !imports.has(declaration.typeAnnotation.typeName.name)) {
1031
+ return;
1032
+ }
1033
+ return declaration.typeAnnotation.typeName.name;
1034
+ }
1035
+ function specifierLocalName(specifier) {
1036
+ return specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value;
1037
+ }
1038
+ var noImportedTypeAlias = defineRule9({
1039
+ meta: {
1040
+ type: "problem",
1041
+ schema: [],
1042
+ messages: {
1043
+ alias: "Exported type '{{alias}}' must not re-alias imported type '{{name}}'."
1044
+ }
1045
+ },
1046
+ createOnce(context) {
1047
+ return {
1048
+ Program(program) {
1049
+ const imports = new Set(program.body.flatMap((statement) => statement.type === "ImportDeclaration" ? statement.specifiers.map((specifier) => specifier.local.name) : []));
1050
+ const aliases = new Map;
1051
+ for (const statement of program.body) {
1052
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
1053
+ const imported = declaration && declaration.type.endsWith("Declaration") ? importedAliasTarget(declaration, imports) : undefined;
1054
+ if (declaration?.type === "TSTypeAliasDeclaration" && imported) {
1055
+ aliases.set(declaration.id.name, imported);
1056
+ }
1057
+ }
1058
+ for (const statement of program.body) {
1059
+ if (statement.type !== "ExportNamedDeclaration") {
1060
+ continue;
1061
+ }
1062
+ const inline = importedAliasTarget(statement.declaration, imports);
1063
+ if (inline && statement.declaration?.type === "TSTypeAliasDeclaration") {
1064
+ context.report({
1065
+ node: statement.declaration,
1066
+ messageId: "alias",
1067
+ data: { alias: statement.declaration.id.name, name: inline }
1068
+ });
1069
+ }
1070
+ for (const specifier of statement.specifiers) {
1071
+ const local = specifierLocalName(specifier);
1072
+ const imported = aliases.get(local);
1073
+ if (imported) {
1074
+ context.report({
1075
+ node: specifier,
1076
+ messageId: "alias",
1077
+ data: { alias: local, name: imported }
1078
+ });
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+ };
1084
+ }
1085
+ });
1086
+
1087
+ // src/rules/no-inline-schema-elements.ts
1088
+ import { defineRule as defineRule10 } from "@oxlint/plugins";
1089
+ var noInlineSchemaElements = defineRule10({
1090
+ meta: {
1091
+ type: "problem",
1092
+ schema: [{
1093
+ type: "object",
1094
+ additionalProperties: false,
1095
+ properties: {
1096
+ namespaces: { type: "array", items: { type: "string" } },
1097
+ methods: { type: "array", items: { type: "string" } },
1098
+ structuralMethods: { type: "array", items: { type: "string" } },
1099
+ allowZodScalars: { type: "boolean" }
1100
+ }
1101
+ }],
1102
+ defaultOptions: [{
1103
+ namespaces: ["z"],
1104
+ methods: ["array", "union", "record", "tuple"],
1105
+ structuralMethods: ["array", "discriminatedUnion", "intersection", "lazy", "object", "record", "tuple", "union"],
1106
+ allowZodScalars: true
1107
+ }],
1108
+ messages: { inline: "z.{{method}}() elements must use named schemas." }
1109
+ },
1110
+ createOnce(context) {
1111
+ return {
1112
+ CallExpression(node) {
1113
+ if (node.callee.type !== "MemberExpression") {
1114
+ return;
1115
+ }
1116
+ const options2 = optionsFirst(context);
1117
+ const namespaces = new Set(options2.namespaces);
1118
+ const method = astStaticMemberName(node.callee);
1119
+ if (!schemasIsCall(node, namespaces) || !method || !options2.methods.includes(method)) {
1120
+ return;
1121
+ }
1122
+ for (const element of schemasElements(node)) {
1123
+ if (!schemasIsAllowedElement(element, options2.allowZodScalars, namespaces, new Set(options2.structuralMethods))) {
1124
+ context.report({
1125
+ node: element,
1126
+ messageId: "inline",
1127
+ data: { method }
1128
+ });
1129
+ }
1130
+ }
1131
+ }
1132
+ };
1133
+ }
1134
+ });
1135
+
1136
+ // src/rules/no-local-schema-construction.ts
1137
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
1138
+ var defaultRuntimeImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
1139
+ var defaultConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
1140
+ function formatMessage(template, data) {
1141
+ return Object.entries(data).reduce((result, [key, value]) => result.replaceAll(`{{${key}}}`, value), template);
1142
+ }
1143
+ var noLocalSchemaConstruction = defineRule11({
1144
+ meta: {
1145
+ type: "problem",
1146
+ schema: [{
1147
+ type: "object",
1148
+ additionalProperties: false,
1149
+ properties: {
1150
+ packages: { type: "array", items: { type: "string" } },
1151
+ namespaces: { type: "array", items: { type: "string" } },
1152
+ allowIn: { type: "array", items: { type: "string" } },
1153
+ allowPathPatterns: { type: "array", items: { type: "string" } },
1154
+ message: { type: "string" },
1155
+ runtimeImportMessage: { type: "string" },
1156
+ constructionMessage: { type: "string" }
1157
+ }
1158
+ }],
1159
+ defaultOptions: [{
1160
+ packages: ["zod"],
1161
+ namespaces: ["z"]
1162
+ }],
1163
+ messages: {
1164
+ runtimeImport: "{{message}}",
1165
+ construction: "{{message}}"
1166
+ }
1167
+ },
1168
+ createOnce(context) {
1169
+ let constructors = new Set;
1170
+ return {
1171
+ before() {
1172
+ const {
1173
+ namespaces = ["z"],
1174
+ allowIn = [],
1175
+ allowPathPatterns = []
1176
+ } = optionsFirst(context, {});
1177
+ const filename = namingPosixPath(context.filename);
1178
+ constructors = new Set(namespaces);
1179
+ if (allowIn.some((suffix) => filename.endsWith(suffix)) || allowPathPatterns.some((pattern) => new RegExp(pattern).test(filename))) {
1180
+ return false;
1181
+ }
1182
+ },
1183
+ ImportDeclaration(node) {
1184
+ const {
1185
+ packages = ["zod"],
1186
+ message,
1187
+ runtimeImportMessage
1188
+ } = optionsFirst(context, {});
1189
+ if (!schemasIsRuntimeImport(node, new Set(packages))) {
1190
+ return;
1191
+ }
1192
+ for (const name of schemasRuntimeImportLocals(node)) {
1193
+ constructors.add(name);
1194
+ }
1195
+ context.report({
1196
+ node,
1197
+ messageId: "runtimeImport",
1198
+ data: {
1199
+ message: formatMessage(runtimeImportMessage ?? message ?? defaultRuntimeImportMessage, { source: node.source.value })
1200
+ }
1201
+ });
1202
+ },
1203
+ ExportNamedDeclaration(node) {
1204
+ const {
1205
+ packages = ["zod"],
1206
+ message,
1207
+ runtimeImportMessage
1208
+ } = optionsFirst(context, {});
1209
+ if (!schemasIsRuntimeReexport(node, new Set(packages)) || !node.source) {
1210
+ return;
1211
+ }
1212
+ context.report({
1213
+ node,
1214
+ messageId: "runtimeImport",
1215
+ data: {
1216
+ message: formatMessage(runtimeImportMessage ?? message ?? defaultRuntimeImportMessage, { source: node.source.value })
1217
+ }
1218
+ });
1219
+ },
1220
+ ExportAllDeclaration(node) {
1221
+ const {
1222
+ packages = ["zod"],
1223
+ message,
1224
+ runtimeImportMessage
1225
+ } = optionsFirst(context, {});
1226
+ if (!schemasIsRuntimeReexport(node, new Set(packages))) {
1227
+ return;
1228
+ }
1229
+ context.report({
1230
+ node,
1231
+ messageId: "runtimeImport",
1232
+ data: {
1233
+ message: formatMessage(runtimeImportMessage ?? message ?? defaultRuntimeImportMessage, { source: node.source.value })
1234
+ }
1235
+ });
1236
+ },
1237
+ CallExpression(node) {
1238
+ if (!schemasIsConstruction(node, constructors)) {
1239
+ return;
1240
+ }
1241
+ const { message, constructionMessage } = optionsFirst(context, {});
1242
+ context.report({
1243
+ node,
1244
+ messageId: "construction",
1245
+ data: {
1246
+ message: formatMessage(constructionMessage ?? message ?? defaultConstructionMessage, {})
1247
+ }
1248
+ });
1249
+ }
1250
+ };
1251
+ }
1252
+ });
1253
+
1254
+ // src/rules/no-rederive-schema.ts
1255
+ import { defineRule as defineRule12 } from "@oxlint/plugins";
1256
+ var noRederiveSchema = defineRule12({
1257
+ meta: {
1258
+ type: "problem",
1259
+ schema: [{
1260
+ type: "object",
1261
+ additionalProperties: false,
1262
+ properties: {
1263
+ from: { type: "array", items: { type: "string" } },
1264
+ namespaces: { type: "array", items: { type: "string" } },
1265
+ operators: { type: "array", items: { type: "string" } }
1266
+ },
1267
+ required: ["from"]
1268
+ }],
1269
+ messages: {
1270
+ rederive: "Do not derive {{operator}} from imported schema {{schema}}; import its DTO type."
1271
+ }
1272
+ },
1273
+ createOnce(context) {
1274
+ let imported = new Set;
1275
+ return {
1276
+ Program(program) {
1277
+ const { from } = optionsFirst(context);
1278
+ imported = new Set(program.body.flatMap((statement) => statement.type === "ImportDeclaration" && from.includes(statement.source.value) ? statement.specifiers.map((specifier) => specifier.local.name) : []));
1279
+ },
1280
+ TSTypeReference(node) {
1281
+ const {
1282
+ namespaces = ["z"],
1283
+ operators = ["infer", "input"]
1284
+ } = optionsFirst(context);
1285
+ const operator = schemasTypeOperator(node, new Set(namespaces), new Set(operators));
1286
+ const parameter = node.typeArguments?.params[0];
1287
+ if (!operator || parameter?.type !== "TSTypeQuery" || parameter.exprName.type !== "Identifier" || !imported.has(parameter.exprName.name)) {
1288
+ return;
1289
+ }
1290
+ context.report({
1291
+ node,
1292
+ messageId: "rederive",
1293
+ data: { operator, schema: parameter.exprName.name }
1294
+ });
1295
+ }
1296
+ };
1297
+ }
1298
+ });
1299
+
1300
+ // src/rules/no-restricted-files.ts
1301
+ import { defineRule as defineRule13 } from "@oxlint/plugins";
1302
+ var noRestrictedFiles = defineRule13({
1303
+ meta: {
1304
+ type: "problem",
1305
+ schema: [{
1306
+ type: "object",
1307
+ additionalProperties: false,
1308
+ properties: {
1309
+ message: { type: "string" }
1310
+ },
1311
+ required: ["message"]
1312
+ }],
1313
+ messages: {
1314
+ restricted: "{{message}}"
1315
+ }
1316
+ },
1317
+ createOnce(context) {
1318
+ return {
1319
+ Program(program) {
1320
+ const { message } = optionsFirst(context);
1321
+ context.report({
1322
+ node: program,
1323
+ messageId: "restricted",
1324
+ data: { message }
1325
+ });
1326
+ }
1327
+ };
1328
+ }
1329
+ });
1330
+
1331
+ // src/rules/no-restricted-token.ts
1332
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
1333
+ function tokenIsTypeOnlyImport(node) {
1334
+ const parent = node.parent;
1335
+ if (parent?.type === "ImportSpecifier") {
1336
+ return parent.importKind === "type" || parent.parent.type === "ImportDeclaration" && parent.parent.importKind === "type";
1337
+ }
1338
+ return (parent?.type === "ImportDefaultSpecifier" || parent?.type === "ImportNamespaceSpecifier") && parent.parent.type === "ImportDeclaration" && parent.parent.importKind === "type";
1339
+ }
1340
+ var noRestrictedToken = defineRule14({
1341
+ meta: {
1342
+ type: "problem",
1343
+ schema: [{
1344
+ type: "object",
1345
+ additionalProperties: false,
1346
+ properties: {
1347
+ token: { type: "string" },
1348
+ allowIn: { type: "array", items: { type: "string" } },
1349
+ allowPathPatterns: { type: "array", items: { type: "string" } }
1350
+ },
1351
+ required: ["token", "allowIn"]
1352
+ }],
1353
+ messages: {
1354
+ restricted: "'{{token}}' may only appear in configured owner paths."
1355
+ }
1356
+ },
1357
+ createOnce(context) {
1358
+ return {
1359
+ before() {
1360
+ const options2 = optionsFirst(context);
1361
+ const filename = namingPosixPath(context.filename);
1362
+ const allowed = options2.allowIn.some((suffix) => filename.endsWith(suffix)) || (options2.allowPathPatterns ?? []).some((pattern) => new RegExp(pattern).test(filename));
1363
+ if (allowed) {
1364
+ return false;
1365
+ }
1366
+ },
1367
+ Identifier(node) {
1368
+ const { token } = optionsFirst(context);
1369
+ if (node.name === token && !tokenIsTypeOnlyImport(node)) {
1370
+ context.report({
1371
+ node,
1372
+ messageId: "restricted",
1373
+ data: { token }
1374
+ });
1375
+ }
1376
+ }
1377
+ };
1378
+ }
1379
+ });
1380
+
1381
+ // src/rules/no-runtime-in-types.ts
1382
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
1383
+ var noRuntimeInTypes = defineRule15({
1384
+ meta: {
1385
+ type: "problem",
1386
+ schema: [{
1387
+ type: "object",
1388
+ additionalProperties: false,
1389
+ properties: {
1390
+ allow: {
1391
+ type: "array",
1392
+ items: { type: "string", enum: ["class", "default", "enum", "function", "variable"] }
1393
+ }
1394
+ }
1395
+ }],
1396
+ messages: {
1397
+ runtimeValue: "Types files must not contain runtime value {{name}}."
1398
+ }
1399
+ },
1400
+ createOnce(context) {
1401
+ return {
1402
+ Program(program) {
1403
+ const { allow = [] } = optionsFirst(context, {});
1404
+ for (const statement of program.body) {
1405
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
1406
+ if (!declaration) {
1407
+ continue;
1408
+ }
1409
+ if (declarationsIsRuntime(declaration)) {
1410
+ if (!declaration.declare && !allow.includes(declarationsRuntimeKind(declaration))) {
1411
+ context.report({
1412
+ node: declaration,
1413
+ messageId: "runtimeValue",
1414
+ data: { name: declarationsRuntimeName(declaration) }
1415
+ });
1416
+ }
1417
+ continue;
1418
+ }
1419
+ if (!allow.includes("default") && statement.type === "ExportDefaultDeclaration" && declaration.type !== "TSInterfaceDeclaration" && declaration.type !== "TSTypeAliasDeclaration") {
1420
+ context.report({
1421
+ node: statement,
1422
+ messageId: "runtimeValue",
1423
+ data: { name: "default" }
1424
+ });
1425
+ }
1426
+ }
1427
+ }
1428
+ };
1429
+ }
1430
+ });
1431
+
1432
+ // src/rules/no-single-use-scalar-schema.ts
1433
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
1434
+ function isScalarSchema(node, namespaces, structuralMethods) {
1435
+ if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
1436
+ return false;
1437
+ }
1438
+ if (schemasIsCall(node, namespaces)) {
1439
+ const method = astStaticMemberName(node.callee);
1440
+ return Boolean(method && !structuralMethods.has(method));
1441
+ }
1442
+ const modifier = astStaticMemberName(node.callee);
1443
+ return Boolean(modifier && !structuralMethods.has(modifier) && node.callee.object.type === "CallExpression" && isScalarSchema(node.callee.object, namespaces, structuralMethods));
1444
+ }
1445
+ function isDirectObjectField(identifier, ancestors, namespaces, structuralMethods) {
1446
+ const property = ancestors.at(-1);
1447
+ const object = ancestors.at(-2);
1448
+ const call = ancestors.at(-3);
1449
+ if (property?.type !== "Property" || property.value !== identifier || object?.type !== "ObjectExpression" || call?.type !== "CallExpression" || !schemasIsCall(call, namespaces)) {
1450
+ return false;
1451
+ }
1452
+ const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
1453
+ return Boolean(method === "object" && structuralMethods.has(method) && call.arguments.includes(object));
1454
+ }
1455
+ function isAllowedStructuralElement(identifier, initializer, ancestors, options2, namespaces, structuralMethods) {
1456
+ if (!options2.allowZodScalars || !schemasIsAllowedElement(initializer, true, namespaces, structuralMethods)) {
1457
+ return false;
1458
+ }
1459
+ const parent = ancestors.at(-1);
1460
+ const call = parent?.type === "ArrayExpression" ? ancestors.at(-2) : parent;
1461
+ if (call?.type !== "CallExpression" || !schemasIsCall(call, namespaces)) {
1462
+ return false;
1463
+ }
1464
+ const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
1465
+ return Boolean(method && options2.methods.includes(method) && schemasElements(call).includes(identifier));
1466
+ }
1467
+ var noSingleUseScalarSchema = defineRule16({
1468
+ meta: {
1469
+ type: "suggestion",
1470
+ schema: [{
1471
+ type: "object",
1472
+ additionalProperties: false,
1473
+ properties: {
1474
+ namespaces: { type: "array", items: { type: "string" } },
1475
+ methods: { type: "array", items: { type: "string" } },
1476
+ structuralMethods: { type: "array", items: { type: "string" } },
1477
+ allowZodScalars: { type: "boolean" }
1478
+ }
1479
+ }],
1480
+ defaultOptions: [{
1481
+ namespaces: ["z"],
1482
+ methods: ["array", "union", "record", "tuple"],
1483
+ structuralMethods: ["and", "array", "discriminatedUnion", "intersection", "lazy", "object", "or", "pipe", "record", "transform", "tuple", "union"],
1484
+ allowZodScalars: true
1485
+ }],
1486
+ messages: { inline: "Inline single-use scalar schema {{name}} at its only structural use." }
1487
+ },
1488
+ create(context) {
1489
+ const candidates = [];
1490
+ return {
1491
+ VariableDeclaration(node) {
1492
+ if (node.kind !== "const") {
1493
+ return;
1494
+ }
1495
+ for (const declarator of node.declarations) {
1496
+ if (declarator.id.type === "Identifier" && declarator.init) {
1497
+ candidates.push(declarator);
1498
+ }
1499
+ }
1500
+ },
1501
+ "Program:exit"(program) {
1502
+ const options2 = optionsFirst(context);
1503
+ const namespaces = new Set(options2.namespaces);
1504
+ const structuralMethods = new Set(options2.structuralMethods);
1505
+ const exportedNames = new Set(exportsCollect(program).flatMap((binding) => binding.localName ? [binding.localName] : []));
1506
+ for (const declarator of candidates) {
1507
+ const name = declarator.id.type === "Identifier" ? declarator.id.name : undefined;
1508
+ const initializer = declarator.init;
1509
+ if (!name || !initializer || exportedNames.has(name) || !isScalarSchema(initializer, namespaces, structuralMethods)) {
1510
+ continue;
1511
+ }
1512
+ const variable = context.sourceCode.getDeclaredVariables(declarator).find((candidate) => candidate.name === name);
1513
+ const references = variable?.references.filter((reference2) => reference2.isRead()) ?? [];
1514
+ if (references.length !== 1) {
1515
+ continue;
1516
+ }
1517
+ const reference = references[0];
1518
+ const identifier = reference.identifier;
1519
+ const ancestors = context.sourceCode.getAncestors(identifier);
1520
+ const inlineable = isDirectObjectField(identifier, ancestors, namespaces, structuralMethods) || isAllowedStructuralElement(identifier, initializer, ancestors, options2, namespaces, structuralMethods);
1521
+ if (inlineable) {
1522
+ context.report({
1523
+ node: declarator.id,
1524
+ messageId: "inline",
1525
+ data: { name }
1526
+ });
1527
+ }
1528
+ }
1529
+ }
1530
+ };
1531
+ }
1532
+ });
1533
+
1534
+ // src/rules/no-top-level-functions.ts
1535
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
1536
+ var noTopLevelFunctions = defineRule17({
1537
+ meta: {
1538
+ type: "problem",
1539
+ schema: [{
1540
+ type: "object",
1541
+ additionalProperties: false,
1542
+ properties: {
1543
+ banReExports: { type: "boolean" },
1544
+ allowPattern: { type: "string" }
1545
+ }
1546
+ }],
1547
+ defaultOptions: [{ banReExports: true }],
1548
+ messages: {
1549
+ noFunction: "Files in this scope must not contain top-level function {{name}}.",
1550
+ noReExport: "Files in this scope must not re-export values."
1551
+ }
1552
+ },
1553
+ createOnce(context) {
1554
+ return {
1555
+ Program(program) {
1556
+ const { banReExports, allowPattern } = optionsFirst(context);
1557
+ const allowed = allowPattern ? new RegExp(allowPattern) : null;
1558
+ for (const statement of program.body) {
1559
+ if (banReExports && (statement.type === "ExportAllDeclaration" || statement.type === "ExportNamedDeclaration" && statement.source)) {
1560
+ context.report({ node: statement, messageId: "noReExport" });
1561
+ continue;
1562
+ }
1563
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
1564
+ if (declaration?.type === "FunctionDeclaration") {
1565
+ if (!allowed?.test(declarationsFunctionName(declaration))) {
1566
+ context.report({
1567
+ node: declaration,
1568
+ messageId: "noFunction",
1569
+ data: { name: declarationsFunctionName(declaration) }
1570
+ });
1571
+ }
1572
+ continue;
1573
+ }
1574
+ if (declaration?.type !== "VariableDeclaration") {
1575
+ continue;
1576
+ }
1577
+ for (const item of declaration.declarations) {
1578
+ if ((item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") && !allowed?.test(declarationsFunctionName(item))) {
1579
+ context.report({
1580
+ node: item,
1581
+ messageId: "noFunction",
1582
+ data: { name: declarationsFunctionName(item) }
1583
+ });
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+ };
1589
+ }
1590
+ });
1591
+
1592
+ // src/rules/no-trivial-functions.ts
1593
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
1594
+ var noTrivialFunctions = defineRule18({
1595
+ meta: {
1596
+ type: "problem",
1597
+ schema: [{
1598
+ type: "object",
1599
+ additionalProperties: false,
1600
+ properties: {
1601
+ allowPattern: { type: "string" }
1602
+ }
1603
+ }],
1604
+ messages: {
1605
+ trivial: "Function {{name}} does nothing useful; inline it at the call site."
1606
+ }
1607
+ },
1608
+ createOnce(context) {
1609
+ return {
1610
+ Program(program) {
1611
+ const { allowPattern } = optionsFirst(context, {});
1612
+ const allowed = allowPattern ? new RegExp(allowPattern) : null;
1613
+ const seen = new Set;
1614
+ for (const item of [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)]) {
1615
+ const key = `${item.name}:${item.node.start}:${item.node.end}`;
1616
+ if (seen.has(key) || allowed?.test(item.name) || !declarationsIsTrivialFunction(item.node)) {
1617
+ continue;
1618
+ }
1619
+ seen.add(key);
1620
+ context.report({
1621
+ node: item.node,
1622
+ messageId: "trivial",
1623
+ data: { name: item.name }
1624
+ });
1625
+ }
1626
+ }
1627
+ };
1628
+ }
1629
+ });
1630
+
1631
+ // src/rules/no-unescaped-like.ts
1632
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
1633
+ var noUnescapedLike = defineRule19({
1634
+ meta: {
1635
+ type: "problem",
1636
+ schema: [{
1637
+ type: "object",
1638
+ additionalProperties: false,
1639
+ properties: {
1640
+ methods: { type: "array", items: { type: "string" } },
1641
+ sanitizers: { type: "array", items: { type: "string" } },
1642
+ allowSanitizedBindings: { type: "boolean" }
1643
+ },
1644
+ required: ["methods", "sanitizers"]
1645
+ }],
1646
+ messages: { unescaped: ".{{method}}() values must use a configured sanitizer." }
1647
+ },
1648
+ createOnce(context) {
1649
+ let sanitized = new Set;
1650
+ return {
1651
+ Program(program) {
1652
+ const { sanitizers: sanitizers2, allowSanitizedBindings = true } = optionsFirst(context);
1653
+ sanitized = allowSanitizedBindings ? sanitizersConfiguredBindings(program, new Set(sanitizers2)) : new Set;
1654
+ },
1655
+ CallExpression(node) {
1656
+ if (node.callee.type !== "MemberExpression") {
1657
+ return;
1658
+ }
1659
+ const { methods, sanitizers: sanitizers2 } = optionsFirst(context);
1660
+ const method = astStaticMemberName(node.callee);
1661
+ if (!method || !methods.includes(method)) {
1662
+ return;
1663
+ }
1664
+ const value = node.arguments.at(-1);
1665
+ const allowedIdentifier = value?.type === "Identifier" && sanitized.has(value.name);
1666
+ if (!sanitizersCallsConfigured(value, new Set(sanitizers2)) && !allowedIdentifier) {
1667
+ context.report({
1668
+ node: value ?? node,
1669
+ messageId: "unescaped",
1670
+ data: { method }
1671
+ });
1672
+ }
1673
+ }
1674
+ };
1675
+ }
1676
+ });
1677
+
1678
+ // src/rules/only-export-components.ts
1679
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
1680
+ var onlyExportComponents = defineRule20({
1681
+ meta: {
1682
+ type: "problem",
1683
+ schema: [{
1684
+ type: "object",
1685
+ additionalProperties: false,
1686
+ properties: {
1687
+ matchFileName: { type: "boolean" }
1688
+ }
1689
+ }],
1690
+ messages: {
1691
+ nonComponent: "Export '{{name}}' must be a React component or type.",
1692
+ nameMismatch: "Component export '{{name}}' must match '{{expected}}' or its prefix."
1693
+ }
1694
+ },
1695
+ createOnce(context) {
1696
+ return {
1697
+ Program(program) {
1698
+ const { matchFileName = true } = optionsFirst(context, {});
1699
+ const expected = reactComponentsPascalFromBasename(namingFileBasename(context.filename));
1700
+ const components = [];
1701
+ for (const candidate of reactComponentsExportCandidates(program)) {
1702
+ if (candidate.typeOnly) {
1703
+ continue;
1704
+ }
1705
+ if (!reactComponentsIsLike(program, candidate)) {
1706
+ context.report({
1707
+ node: candidate.node,
1708
+ messageId: "nonComponent",
1709
+ data: { name: candidate.name, expected }
1710
+ });
1711
+ continue;
1712
+ }
1713
+ components.push(candidate);
1714
+ }
1715
+ if (components.length === 0 || !matchFileName) {
1716
+ return;
1717
+ }
1718
+ const expectedKey = expected.toLowerCase();
1719
+ const exact = components.some((candidate) => candidate.name.toLowerCase() === expectedKey);
1720
+ const allPrefixed = components.every((candidate) => candidate.name.toLowerCase().startsWith(expectedKey));
1721
+ if (!exact && !allPrefixed) {
1722
+ for (const candidate of components) {
1723
+ context.report({
1724
+ node: candidate.node,
1725
+ messageId: "nameMismatch",
1726
+ data: { name: candidate.name, expected }
1727
+ });
1728
+ }
1729
+ }
1730
+ }
1731
+ };
1732
+ }
1733
+ });
1734
+
1735
+ // src/rules/require-file-factory.ts
1736
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
1737
+ var requireFileFactory = defineRule21({
1738
+ meta: {
1739
+ type: "problem",
1740
+ schema: [{
1741
+ type: "object",
1742
+ additionalProperties: false,
1743
+ properties: {
1744
+ factory: { type: "string" },
1745
+ allDeclarations: { type: "boolean" }
1746
+ },
1747
+ required: ["factory"]
1748
+ }],
1749
+ messages: {
1750
+ factory: "Function '{{actual}}' must be named '{{expected}}'."
1751
+ }
1752
+ },
1753
+ createOnce(context) {
1754
+ return {
1755
+ Program(program) {
1756
+ const { factory, allDeclarations = false } = optionsFirst(context);
1757
+ const stem = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
1758
+ const expected = factory.replaceAll("{Stem}", namingPascalCase(stem)).replaceAll("{stem}", stem);
1759
+ const functions = allDeclarations ? declarationsCollectFunctions(program) : exportsCollectFunctions(program);
1760
+ if (functions.length === 0) {
1761
+ context.report({
1762
+ node: program,
1763
+ messageId: "factory",
1764
+ data: { actual: "<none>", expected }
1765
+ });
1766
+ return;
1767
+ }
1768
+ for (const item of functions) {
1769
+ if (item.name !== expected) {
1770
+ context.report({
1771
+ node: item.node,
1772
+ messageId: "factory",
1773
+ data: { actual: item.name, expected }
1774
+ });
1775
+ }
1776
+ }
1777
+ }
1778
+ };
1779
+ }
1780
+ });
1781
+
1782
+ // src/rules/require-object-params.ts
1783
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
1784
+ var requireObjectParams = defineRule22({
1785
+ meta: {
1786
+ type: "problem",
1787
+ schema: [],
1788
+ messages: {
1789
+ objectParams: "Exported function {{name}} must accept at most one object parameter."
1790
+ }
1791
+ },
1792
+ createOnce(context) {
1793
+ return {
1794
+ Program(program) {
1795
+ for (const item of exportsCollectFunctions(program)) {
1796
+ if (item.node.params.length > 1 || item.node.params.some((param) => !declarationsIsObjectParam(param))) {
1797
+ context.report({
1798
+ node: item.node,
1799
+ messageId: "objectParams",
1800
+ data: { name: item.name }
1801
+ });
1802
+ }
1803
+ }
1804
+ }
1805
+ };
1806
+ }
1807
+ });
1808
+
1809
+ // src/rules/require-orpc-output.ts
1810
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
1811
+ var requireOrpcOutput = defineRule23({
1812
+ meta: {
1813
+ type: "problem",
1814
+ schema: [{
1815
+ type: "object",
1816
+ additionalProperties: false,
1817
+ properties: {
1818
+ composers: { type: "array", items: { type: "string" } },
1819
+ handlerMethod: { type: "string" },
1820
+ outputMethod: { type: "string" }
1821
+ },
1822
+ required: ["composers"]
1823
+ }],
1824
+ messages: {
1825
+ missing: "oRPC procedures must declare .output() before .handler().",
1826
+ invalid: "oRPC .output() must use a named schema or configured composer with a named schema."
1827
+ }
1828
+ },
1829
+ createOnce(context) {
1830
+ return {
1831
+ CallExpression(node) {
1832
+ if (node.callee.type !== "MemberExpression") {
1833
+ return;
1834
+ }
1835
+ const { composers, handlerMethod = "handler", outputMethod = "output" } = optionsFirst(context);
1836
+ if (astStaticMemberName(node.callee) !== handlerMethod) {
1837
+ return;
1838
+ }
1839
+ const output = orpcFindChainCall(node.callee.object, outputMethod);
1840
+ if (!output) {
1841
+ context.report({ node, messageId: "missing" });
1842
+ return;
1843
+ }
1844
+ if (!orpcIsNamedOrComposedArgument(output.arguments[0], new Set(composers))) {
1845
+ context.report({ node: output, messageId: "invalid" });
1846
+ }
1847
+ }
1848
+ };
1849
+ }
1850
+ });
1851
+
1852
+ // src/rules/require-paired-call.ts
1853
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
1854
+ var requirePairedCall = defineRule24({
1855
+ meta: {
1856
+ type: "problem",
1857
+ schema: [{
1858
+ type: "object",
1859
+ additionalProperties: false,
1860
+ properties: {
1861
+ when: { type: "string" },
1862
+ require: { type: "string" }
1863
+ },
1864
+ required: ["when", "require"]
1865
+ }],
1866
+ messages: {
1867
+ paired: "{{when}} requires a call to {{require}} in the same file."
1868
+ }
1869
+ },
1870
+ createOnce(context) {
1871
+ let aliases = new Map;
1872
+ let firstWhen = null;
1873
+ let sawRequired = false;
1874
+ return {
1875
+ before() {
1876
+ aliases = new Map;
1877
+ firstWhen = null;
1878
+ sawRequired = false;
1879
+ },
1880
+ Program(program) {
1881
+ aliases = astImportedCallAliases(program);
1882
+ },
1883
+ CallExpression(node) {
1884
+ const { when, require: require2 } = optionsFirst(context);
1885
+ const name = astResolvedCallName(node, aliases);
1886
+ if (name === when && !firstWhen) {
1887
+ firstWhen = node;
1888
+ }
1889
+ if (name === require2) {
1890
+ sawRequired = true;
1891
+ }
1892
+ },
1893
+ after() {
1894
+ if (!firstWhen || sawRequired) {
1895
+ return;
1896
+ }
1897
+ const { when, require: require2 } = optionsFirst(context);
1898
+ context.report({
1899
+ node: firstWhen,
1900
+ messageId: "paired",
1901
+ data: { when, require: require2 }
1902
+ });
1903
+ }
1904
+ };
1905
+ }
1906
+ });
1907
+
1908
+ // src/rules/route-surface.ts
1909
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
1910
+ var routeSurface = defineRule25({
1911
+ meta: {
1912
+ type: "problem",
1913
+ schema: [{
1914
+ type: "object",
1915
+ additionalProperties: false,
1916
+ properties: {
1917
+ exportName: { type: "string" },
1918
+ bannedHooks: { type: "array", items: { type: "string" } },
1919
+ banIntrinsicJsx: { type: "boolean" }
1920
+ }
1921
+ }],
1922
+ defaultOptions: [{ exportName: "Route", bannedHooks: ["useState", "useEffect", "useMutation"], banIntrinsicJsx: true }],
1923
+ messages: {
1924
+ extraExport: "Route files may only export '{{expected}}'; found '{{actual}}'.",
1925
+ hostJsx: "Route adapter {{name}} must not render intrinsic JSX.",
1926
+ bannedHook: "Route adapter {{name}} must not call {{hook}}."
1927
+ }
1928
+ },
1929
+ createOnce(context) {
1930
+ return {
1931
+ Program(program) {
1932
+ const { exportName, bannedHooks, banIntrinsicJsx } = optionsFirst(context);
1933
+ for (const binding of exportsCollect(program)) {
1934
+ if (binding.exportedName !== exportName) {
1935
+ context.report({
1936
+ node: binding.node,
1937
+ messageId: "extraExport",
1938
+ data: { expected: exportName, actual: binding.exportedName }
1939
+ });
1940
+ }
1941
+ }
1942
+ const banned = new Set(bannedHooks);
1943
+ for (const item of declarationsTopLevelUnexportedFunctions(program)) {
1944
+ routesInspectFunction(context, item, banned, banIntrinsicJsx);
1945
+ }
1946
+ }
1947
+ };
1948
+ }
1949
+ });
1950
+
1951
+ // src/index.ts
1952
+ var src_default = eslintCompatPlugin({
1953
+ meta: { name: "arch" },
1954
+ rules: {
1955
+ "export-file-prefix": exportFilePrefix,
1956
+ "export-name-pattern": exportNamePattern,
1957
+ "filename-export-name": filenameExportName,
1958
+ "filename-match": filenameMatch,
1959
+ "folder-prefix": folderPrefix,
1960
+ "no-extra-exports": noExtraExports,
1961
+ "no-extra-factory-keys": noExtraFactoryKeys,
1962
+ "no-file-level-helpers": noFileLevelHelpers,
1963
+ "no-imported-type-alias": noImportedTypeAlias,
1964
+ "no-inline-schema-elements": noInlineSchemaElements,
1965
+ "no-local-schema-construction": noLocalSchemaConstruction,
1966
+ "no-rederive-schema": noRederiveSchema,
1967
+ "no-restricted-files": noRestrictedFiles,
1968
+ "no-restricted-token": noRestrictedToken,
1969
+ "no-runtime-in-types": noRuntimeInTypes,
1970
+ "no-single-use-scalar-schema": noSingleUseScalarSchema,
1971
+ "no-top-level-functions": noTopLevelFunctions,
1972
+ "no-trivial-functions": noTrivialFunctions,
1973
+ "no-unescaped-like": noUnescapedLike,
1974
+ "only-export-components": onlyExportComponents,
1975
+ "require-file-factory": requireFileFactory,
1976
+ "require-object-params": requireObjectParams,
1977
+ "require-orpc-output": requireOrpcOutput,
1978
+ "require-paired-call": requirePairedCall,
1979
+ "route-surface": routeSurface
1980
+ }
1981
+ });
1982
+ export {
1983
+ src_default as default
1984
+ };