oxlint-plugin-arch 0.2.3 → 0.2.5
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/CHANGELOG.md +11 -0
- package/README.md +63 -4
- package/dist/index.js +404 -72
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.5
|
|
4
|
+
|
|
5
|
+
- Add `no-inline-types` to require named object and function types in function signatures, with separate parameter, return, and function-type options.
|
|
6
|
+
- Add `only-export-constants` to restrict exports to local `const` bindings, with opt-ins for function values, type exports, and re-exports.
|
|
7
|
+
- Add `no-member-comments` to remove comments on object/type members while preserving directives and optional `Why:` explanations.
|
|
8
|
+
- Document the new rule options and syntax-only limits, extend the full configuration example to all 30 rules, and sync the optional vendored skill assets.
|
|
9
|
+
|
|
10
|
+
## 0.2.4
|
|
11
|
+
|
|
12
|
+
- Add `trailingRoles` and `roleSeparators` so `export-file-prefix`, `declaration-name`, and `no-extra-exports` can treat `onchain-utils.ts` like `onchain.utils.ts`.
|
|
13
|
+
|
|
3
14
|
## 0.2.3
|
|
4
15
|
|
|
5
16
|
- Add `declaration-name` so selected declarations can follow a file prefix or pattern per glob.
|
package/README.md
CHANGED
|
@@ -41,23 +41,24 @@ Then point `jsPlugins[].specifier` at the copied entry file instead of the packa
|
|
|
41
41
|
## Configuration examples
|
|
42
42
|
|
|
43
43
|
- [`examples/minimal.oxlint.config.ts`](examples/minimal.oxlint.config.ts) — three small rules showing plugin registration, glob ownership, and options.
|
|
44
|
-
- [`examples/full.oxlint.config.ts`](examples/full.oxlint.config.ts) — all
|
|
44
|
+
- [`examples/full.oxlint.config.ts`](examples/full.oxlint.config.ts) — all 30 rules across representative component, action, service, constant, route, API, schema, and database scopes.
|
|
45
45
|
|
|
46
46
|
Copy the shapes that match your repository; do not copy globs or naming policy blindly.
|
|
47
47
|
|
|
48
48
|
## Rules
|
|
49
49
|
|
|
50
|
-
- `declaration-name` — require selected declarations to match a filename-derived prefix or a consumer pattern.
|
|
51
|
-
- `export-file-prefix` — require export names, or every function and type, to start with a filename-derived prefix, optionally singularized.
|
|
50
|
+
- `declaration-name` — require selected declarations to match a filename-derived prefix or a consumer pattern. Optional `trailingRoles` treat `onchain-utils.ts` like `onchain.utils.ts`.
|
|
51
|
+
- `export-file-prefix` — require export names, or every function and type, to start with a filename-derived prefix, optionally singularized. Optional `trailingRoles` drop a configured role suffix from that prefix.
|
|
52
52
|
- `export-name-pattern` — require export names, or every function and type, to match a configured regular expression.
|
|
53
53
|
- `filename-export-name` — derive expected function names from filename templates, optionally including locals.
|
|
54
54
|
- `filename-match` — require filenames to match a configured pattern.
|
|
55
55
|
- `folder-prefix` — require filenames to start with their parent folder name, or the folders after a configured root.
|
|
56
|
-
- `no-extra-exports` — restrict files to configured export templates.
|
|
56
|
+
- `no-extra-exports` — restrict files to configured export templates. Optional `trailingRoles` apply to `{Domain}` the same way as the prefix rules.
|
|
57
57
|
- `no-extra-factory-keys` — restrict direct factory return keys.
|
|
58
58
|
- `no-file-level-helpers` — keep unapproved helpers out of module scope.
|
|
59
59
|
- `no-imported-type-alias` — reject exported aliases that only rename imported types.
|
|
60
60
|
- `no-inline-schema-elements` — require named schemas inside configured combinators.
|
|
61
|
+
- `no-inline-types` — require named object and function types in parameter and return annotations. See [options and scope](#no-inline-types).
|
|
61
62
|
- `no-local-schema-construction` — reject runtime schema-library imports and local schema construction, with consumer-chosen severity, messages, and path exceptions.
|
|
62
63
|
- `no-rederive-schema` — reject type derivation from configured imported schemas.
|
|
63
64
|
- `no-restricted-files` — reject files selected by a consumer-owned forbidden glob.
|
|
@@ -67,8 +68,10 @@ Copy the shapes that match your repository; do not copy globs or naming policy b
|
|
|
67
68
|
- `no-top-level-functions` — reject top-level functions and optional re-exports.
|
|
68
69
|
- `no-trivial-functions` — reject empty or passthrough top-level functions.
|
|
69
70
|
- `no-type-declarations` — reject type aliases and interfaces in matched files.
|
|
71
|
+
- `no-member-comments` — remove leading and same-line trailing comments on object-type members: interface properties and methods, index/call/construct signatures, type-literal members (including inline function parameter types), object literal properties, and object spreads. Autofix removes the comment and any blank line that followed it, consumes the spacing in front of a trailing comment so no line is left with trailing whitespace, and never removes the member. A contiguous run of comments is reported once and removed by a single fix, so `--fix` converges in one pass. `allowWhy` defaults to `true`: comments beginning with `Why: ` (after whitespace/JSDoc decoration), including contiguous `//` continuation lines, are preserved. Set `{ allowWhy: false }` to ban those too. The rule checks the prefix, not whether the rationale is meaningful. Directive comments are always preserved, whatever `allowWhy` says: `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, `eslint-`/`oxlint-disable`/`enable`, `prettier-ignore`, `biome-ignore`, `c8`/`v8`/`istanbul ignore`, and webpack magic comments. Declaration, function-body, class-field, enum-member, union-member, and plain function-parameter comments are out of scope, as are comments inside destructuring patterns and array literals. Enable with `'arch/no-member-comments': ['error', { allowWhy: true }]` in a `**/*.{ts,tsx}` override.
|
|
70
72
|
- `no-unescaped-like` — require configured sanitizers for configured query methods.
|
|
71
73
|
- `only-export-components` — allow only React component and type exports.
|
|
74
|
+
- `only-export-constants` — allow only local `const` exports, with opt-ins for function values, types, and re-exports. See [options and scope](#only-export-constants).
|
|
72
75
|
- `require-file-factory` — derive and require a filename-based factory function.
|
|
73
76
|
- `require-object-params` — require exported functions to use object-shaped parameters.
|
|
74
77
|
- `require-orpc-output` — require named oRPC output schemas.
|
|
@@ -77,6 +80,62 @@ Copy the shapes that match your repository; do not copy globs or naming policy b
|
|
|
77
80
|
|
|
78
81
|
Every rule file includes plain-English behavior and examples. Rule tests under `src/tests/` show complete option shapes.
|
|
79
82
|
|
|
83
|
+
### `no-inline-types`
|
|
84
|
+
|
|
85
|
+
Require named types in function signatures, including local functions, callbacks, methods, constructors, and overloads. Checks explicit annotations only; inferred types, variable annotations, generic constraints, and named type/interface definitions are not changed or banned. Use `no-type-declarations` separately to control where named types may live.
|
|
86
|
+
|
|
87
|
+
| Option | Default | Behavior |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `parameters` | `true` | Check parameter annotations, including destructuring, defaults, rest parameters, and constructor parameter properties. |
|
|
90
|
+
| `returns` | `true` | Check explicit return annotations. |
|
|
91
|
+
| `functionTypes` | `true` | Also reject inline function and constructor types. When `false`, object types nested inside those annotations are still checked. |
|
|
92
|
+
|
|
93
|
+
Object/function types nested in arrays, tuples, unions, intersections, or generic arguments are checked too (`Promise<{ id: string }>` fails). A rejected outer type is reported once, not again for each nested member.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// In the consumer's override for service files:
|
|
97
|
+
'arch/no-inline-types': ['error', {
|
|
98
|
+
parameters: true,
|
|
99
|
+
returns: true,
|
|
100
|
+
functionTypes: true,
|
|
101
|
+
}]
|
|
102
|
+
|
|
103
|
+
// Pass
|
|
104
|
+
function save(input: SaveInput): SaveResult { return persist(input) }
|
|
105
|
+
// Fail
|
|
106
|
+
function save(input: { id: string }): { ok: boolean } { return persist(input) }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `only-export-constants`
|
|
110
|
+
|
|
111
|
+
Require every runtime export to reference a local `const` binding. Supports direct exports, destructuring, renamed exports, and default exports of local constants. Non-exported declarations are unrestricted.
|
|
112
|
+
|
|
113
|
+
| Option | Default | Behavior |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `allowFunctionValues` | `false` | Allow function-valued `const` bindings, not exported function declarations. |
|
|
116
|
+
| `allowTypeExports` | `false` | Allow type aliases, interfaces, and type-only exports, including type-only re-exports. |
|
|
117
|
+
| `allowReExports` | `false` | Allow runtime re-exports (`export ... from`, `export *`) and exports of imported bindings. Their declaration kind cannot be verified locally. |
|
|
118
|
+
|
|
119
|
+
`let`, `var`, function/class/enum/namespace declarations, class-valued constants, bare default expressions, and TypeScript `export =` / `export as namespace` are rejected. Name the default value with a local `const` first. Type-only re-exports use `allowTypeExports`, independently of `allowReExports`.
|
|
120
|
+
|
|
121
|
+
This is a syntax rule, not a deep-immutability or runtime-type check. It recognizes direct function/class values, TypeScript expression wrappers, and local identifier aliases. It does not infer values returned by calls, imported values, member accesses, destructuring, or object contents. Objects and arrays do not need `as const` or `Object.freeze()`.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// In the consumer's override for constant files:
|
|
125
|
+
'arch/only-export-constants': ['error', {
|
|
126
|
+
allowFunctionValues: false,
|
|
127
|
+
allowTypeExports: false,
|
|
128
|
+
allowReExports: false,
|
|
129
|
+
}]
|
|
130
|
+
|
|
131
|
+
// Pass
|
|
132
|
+
const retryLimit = 3
|
|
133
|
+
export { retryLimit }
|
|
134
|
+
// Fail
|
|
135
|
+
export let retryCount = 0
|
|
136
|
+
export const retry = () => run()
|
|
137
|
+
```
|
|
138
|
+
|
|
80
139
|
## Development
|
|
81
140
|
|
|
82
141
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -181,7 +181,7 @@ function declarationsIsTrivialFunction(node) {
|
|
|
181
181
|
if (!node.body || node.body.type !== "BlockStatement") {
|
|
182
182
|
return false;
|
|
183
183
|
}
|
|
184
|
-
const statements = node.body.body.filter((
|
|
184
|
+
const statements = node.body.body.filter((statement) => statement.type !== "EmptyStatement" && !(statement.type === "ExpressionStatement" && statement.directive));
|
|
185
185
|
if (statements.length === 0) {
|
|
186
186
|
return true;
|
|
187
187
|
}
|
|
@@ -422,9 +422,37 @@ function namingMatchTemplate(basename, template, placeholderPattern = "[a-z0-9-]
|
|
|
422
422
|
}
|
|
423
423
|
return new RegExp(`^${pattern}$`).exec(basename)?.groups ?? null;
|
|
424
424
|
}
|
|
425
|
+
var namingStemModes = ["before-first-dot", "full-basename"];
|
|
425
426
|
function namingNormalized(value, normalize) {
|
|
426
427
|
return normalize === "none" ? value : value.replaceAll(/[-_]/g, "").toLowerCase();
|
|
427
428
|
}
|
|
429
|
+
function namingFileStem(basename, mode = "before-first-dot", trailingRoles = [], roleSeparators = [".", "-"]) {
|
|
430
|
+
const stem = mode === "full-basename" ? basename : basename.split(".")[0] ?? "";
|
|
431
|
+
const roles = [...new Set(trailingRoles.filter(Boolean))].sort((left, right) => right.length - left.length);
|
|
432
|
+
const separators = [...new Set(roleSeparators.filter(Boolean))];
|
|
433
|
+
if (roles.length === 0 || separators.length === 0) {
|
|
434
|
+
return stem;
|
|
435
|
+
}
|
|
436
|
+
let remaining = stem;
|
|
437
|
+
let changed = true;
|
|
438
|
+
while (changed) {
|
|
439
|
+
changed = false;
|
|
440
|
+
for (const role of roles) {
|
|
441
|
+
for (const separator of separators) {
|
|
442
|
+
const suffix = `${separator}${role}`;
|
|
443
|
+
if (remaining.length > suffix.length && remaining.endsWith(suffix)) {
|
|
444
|
+
remaining = remaining.slice(0, -suffix.length);
|
|
445
|
+
changed = true;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (changed) {
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return remaining;
|
|
455
|
+
}
|
|
428
456
|
function namingFilePrefixes(stem, normalize = "remove-separators", singularize = "none") {
|
|
429
457
|
const prefixes = [namingNormalized(stem, normalize)];
|
|
430
458
|
const parts = stem.split(/[-_]+/).filter(Boolean);
|
|
@@ -699,7 +727,9 @@ var declarationName = defineRule({
|
|
|
699
727
|
},
|
|
700
728
|
pattern: { type: "string" },
|
|
701
729
|
flags: { type: "string" },
|
|
702
|
-
stem: { type: "string", enum: [
|
|
730
|
+
stem: { type: "string", enum: [...namingStemModes] },
|
|
731
|
+
trailingRoles: { type: "array", items: { type: "string" } },
|
|
732
|
+
roleSeparators: { type: "array", items: { type: "string" } },
|
|
703
733
|
normalize: { type: "string", enum: ["remove-separators", "none"] },
|
|
704
734
|
singularize: { type: "string", enum: ["none", "trailing-s"] },
|
|
705
735
|
allowPattern: { type: "string" }
|
|
@@ -713,19 +743,19 @@ var declarationName = defineRule({
|
|
|
713
743
|
createOnce(context) {
|
|
714
744
|
return {
|
|
715
745
|
Program(program) {
|
|
716
|
-
const
|
|
746
|
+
const options = optionsFirst(context, {
|
|
717
747
|
stem: "before-first-dot",
|
|
718
748
|
normalize: "remove-separators"
|
|
719
749
|
});
|
|
720
|
-
const allowed =
|
|
721
|
-
const expected =
|
|
750
|
+
const allowed = options.allowPattern ? new RegExp(options.allowPattern) : null;
|
|
751
|
+
const expected = options.pattern ? new RegExp(options.pattern, options.flags) : null;
|
|
722
752
|
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
723
|
-
const stem =
|
|
724
|
-
const prefixes = namingFilePrefixes(stem,
|
|
753
|
+
const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
|
|
754
|
+
const prefixes = namingFilePrefixes(stem, options.normalize ?? "remove-separators", options.singularize ?? "none");
|
|
725
755
|
const seen = new Set;
|
|
726
|
-
for (const item of declarationsCollectNamed(program,
|
|
756
|
+
for (const item of declarationsCollectNamed(program, options.kinds)) {
|
|
727
757
|
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
728
|
-
const comparableName =
|
|
758
|
+
const comparableName = options.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
|
|
729
759
|
const matches = expected ? expected.test(item.name) : prefixes.some((prefix) => comparableName.startsWith(prefix));
|
|
730
760
|
if (seen.has(key) || allowed?.test(item.name) || matches) {
|
|
731
761
|
continue;
|
|
@@ -734,7 +764,7 @@ var declarationName = defineRule({
|
|
|
734
764
|
context.report({
|
|
735
765
|
node: item.node,
|
|
736
766
|
messageId: expected ? "pattern" : "prefix",
|
|
737
|
-
data: expected ? { name: item.name, pattern:
|
|
767
|
+
data: expected ? { name: item.name, pattern: options.pattern ?? "" } : { name: item.name, prefix: prefixes.join(" or ") }
|
|
738
768
|
});
|
|
739
769
|
}
|
|
740
770
|
}
|
|
@@ -751,7 +781,9 @@ var exportFilePrefix = defineRule2({
|
|
|
751
781
|
type: "object",
|
|
752
782
|
additionalProperties: false,
|
|
753
783
|
properties: {
|
|
754
|
-
stem: { type: "string", enum: [
|
|
784
|
+
stem: { type: "string", enum: [...namingStemModes] },
|
|
785
|
+
trailingRoles: { type: "array", items: { type: "string" } },
|
|
786
|
+
roleSeparators: { type: "array", items: { type: "string" } },
|
|
755
787
|
normalize: { type: "string", enum: ["remove-separators", "none"] },
|
|
756
788
|
singularize: { type: "string", enum: ["none", "trailing-s"] },
|
|
757
789
|
allDeclarations: { type: "boolean" },
|
|
@@ -765,22 +797,22 @@ var exportFilePrefix = defineRule2({
|
|
|
765
797
|
createOnce(context) {
|
|
766
798
|
return {
|
|
767
799
|
Program(program) {
|
|
768
|
-
const
|
|
800
|
+
const options = optionsFirst(context, {
|
|
769
801
|
stem: "before-first-dot",
|
|
770
802
|
normalize: "remove-separators"
|
|
771
803
|
});
|
|
772
804
|
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
773
|
-
const stem =
|
|
774
|
-
const prefixes = namingFilePrefixes(stem,
|
|
775
|
-
const allowed =
|
|
776
|
-
const names =
|
|
805
|
+
const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
|
|
806
|
+
const prefixes = namingFilePrefixes(stem, options.normalize ?? "remove-separators", options.singularize ?? "none");
|
|
807
|
+
const allowed = options.allowPattern ? new RegExp(options.allowPattern) : null;
|
|
808
|
+
const names = options.allDeclarations ? declarationsCollectNamed(program) : exportsCollect(program).map((binding) => ({
|
|
777
809
|
name: binding.localName ?? binding.exportedName,
|
|
778
810
|
node: binding.node
|
|
779
811
|
}));
|
|
780
812
|
const seen = new Set;
|
|
781
813
|
for (const item of names) {
|
|
782
814
|
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
783
|
-
const comparableName =
|
|
815
|
+
const comparableName = options.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
|
|
784
816
|
if (seen.has(key) || allowed?.test(item.name) || prefixes.some((prefix) => comparableName.startsWith(prefix))) {
|
|
785
817
|
continue;
|
|
786
818
|
}
|
|
@@ -1003,7 +1035,9 @@ var noExtraExports = defineRule7({
|
|
|
1003
1035
|
additionalProperties: false,
|
|
1004
1036
|
properties: {
|
|
1005
1037
|
names: { type: "array", items: { type: "string" } },
|
|
1006
|
-
domainStem: { type: "string", enum: [
|
|
1038
|
+
domainStem: { type: "string", enum: [...namingStemModes] },
|
|
1039
|
+
trailingRoles: { type: "array", items: { type: "string" } },
|
|
1040
|
+
roleSeparators: { type: "array", items: { type: "string" } },
|
|
1007
1041
|
allowTypeExports: { type: "boolean" }
|
|
1008
1042
|
},
|
|
1009
1043
|
required: ["names"]
|
|
@@ -1015,12 +1049,12 @@ var noExtraExports = defineRule7({
|
|
|
1015
1049
|
createOnce(context) {
|
|
1016
1050
|
return {
|
|
1017
1051
|
Program(program) {
|
|
1018
|
-
const
|
|
1052
|
+
const options = optionsFirst(context);
|
|
1019
1053
|
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
1020
|
-
const domain =
|
|
1021
|
-
const allowed =
|
|
1054
|
+
const domain = namingFileStem(basename, options.domainStem, options.trailingRoles, options.roleSeparators);
|
|
1055
|
+
const allowed = options.names.map((template) => template.replaceAll("{Domain}", namingPascalCase(domain)).replaceAll("{domain}", namingCamelCase(domain)));
|
|
1022
1056
|
for (const binding of exportsCollect(program)) {
|
|
1023
|
-
if (
|
|
1057
|
+
if (options.allowTypeExports && binding.typeOnly) {
|
|
1024
1058
|
continue;
|
|
1025
1059
|
}
|
|
1026
1060
|
const name = binding.localName ?? binding.exportedName;
|
|
@@ -1210,14 +1244,14 @@ var noInlineSchemaElements = defineRule11({
|
|
|
1210
1244
|
if (node.callee.type !== "MemberExpression") {
|
|
1211
1245
|
return;
|
|
1212
1246
|
}
|
|
1213
|
-
const
|
|
1214
|
-
const namespaces = new Set(
|
|
1247
|
+
const options = optionsFirst(context);
|
|
1248
|
+
const namespaces = new Set(options.namespaces);
|
|
1215
1249
|
const method = astStaticMemberName(node.callee);
|
|
1216
|
-
if (!schemasIsCall(node, namespaces) || !method || !
|
|
1250
|
+
if (!schemasIsCall(node, namespaces) || !method || !options.methods.includes(method)) {
|
|
1217
1251
|
return;
|
|
1218
1252
|
}
|
|
1219
1253
|
for (const element of schemasElements(node)) {
|
|
1220
|
-
if (!schemasIsAllowedElement(element,
|
|
1254
|
+
if (!schemasIsAllowedElement(element, options.allowZodScalars, namespaces, new Set(options.structuralMethods))) {
|
|
1221
1255
|
context.report({
|
|
1222
1256
|
node: element,
|
|
1223
1257
|
messageId: "inline",
|
|
@@ -1230,14 +1264,76 @@ var noInlineSchemaElements = defineRule11({
|
|
|
1230
1264
|
}
|
|
1231
1265
|
});
|
|
1232
1266
|
|
|
1233
|
-
// src/rules/no-
|
|
1267
|
+
// src/rules/no-inline-types.ts
|
|
1234
1268
|
import { defineRule as defineRule12 } from "@oxlint/plugins";
|
|
1269
|
+
function parameterAnnotation(parameter) {
|
|
1270
|
+
if (parameter.type === "TSParameterProperty") {
|
|
1271
|
+
return parameterAnnotation(parameter.parameter);
|
|
1272
|
+
}
|
|
1273
|
+
if (parameter.type === "AssignmentPattern") {
|
|
1274
|
+
return parameterAnnotation(parameter.left);
|
|
1275
|
+
}
|
|
1276
|
+
return parameter.typeAnnotation;
|
|
1277
|
+
}
|
|
1278
|
+
var noInlineTypes = defineRule12({
|
|
1279
|
+
meta: {
|
|
1280
|
+
type: "problem",
|
|
1281
|
+
schema: [{
|
|
1282
|
+
type: "object",
|
|
1283
|
+
additionalProperties: false,
|
|
1284
|
+
properties: {
|
|
1285
|
+
parameters: { type: "boolean" },
|
|
1286
|
+
returns: { type: "boolean" },
|
|
1287
|
+
functionTypes: { type: "boolean" }
|
|
1288
|
+
}
|
|
1289
|
+
}],
|
|
1290
|
+
messages: {
|
|
1291
|
+
inlineType: "Use a named type instead of an inline {{kind}} type in this {{position}} annotation."
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
createOnce(context) {
|
|
1295
|
+
return {
|
|
1296
|
+
Program(program) {
|
|
1297
|
+
const { parameters = true, returns = true, functionTypes = true } = optionsFirst(context, {});
|
|
1298
|
+
const isInline = (node) => node.type === "TSTypeLiteral" || functionTypes && (node.type === "TSFunctionType" || node.type === "TSConstructorType");
|
|
1299
|
+
function check(annotation, position) {
|
|
1300
|
+
astVisit(annotation, [], (node, ancestors) => {
|
|
1301
|
+
if (!isInline(node) || ancestors.some(isInline)) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
context.report({
|
|
1305
|
+
node,
|
|
1306
|
+
messageId: "inlineType",
|
|
1307
|
+
data: { kind: node.type === "TSTypeLiteral" ? "object" : "function", position }
|
|
1308
|
+
});
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
astVisit(program, [], (node) => {
|
|
1312
|
+
if (node.type !== "FunctionDeclaration" && node.type !== "FunctionExpression" && node.type !== "ArrowFunctionExpression" && node.type !== "TSDeclareFunction" && node.type !== "TSEmptyBodyFunctionExpression") {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (parameters) {
|
|
1316
|
+
for (const parameter of node.params) {
|
|
1317
|
+
check(parameterAnnotation(parameter), "parameter");
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (returns) {
|
|
1321
|
+
check(node.returnType, "return");
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
// src/rules/no-local-schema-construction.ts
|
|
1330
|
+
import { defineRule as defineRule13 } from "@oxlint/plugins";
|
|
1235
1331
|
var defaultRuntimeImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
|
|
1236
1332
|
var defaultConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
|
|
1237
1333
|
function formatMessage(template, data) {
|
|
1238
1334
|
return Object.entries(data).reduce((result, [key, value]) => result.replaceAll(`{{${key}}}`, value), template);
|
|
1239
1335
|
}
|
|
1240
|
-
var noLocalSchemaConstruction =
|
|
1336
|
+
var noLocalSchemaConstruction = defineRule13({
|
|
1241
1337
|
meta: {
|
|
1242
1338
|
type: "problem",
|
|
1243
1339
|
schema: [{
|
|
@@ -1348,9 +1444,105 @@ var noLocalSchemaConstruction = defineRule12({
|
|
|
1348
1444
|
}
|
|
1349
1445
|
});
|
|
1350
1446
|
|
|
1447
|
+
// src/rules/no-member-comments.ts
|
|
1448
|
+
import { defineRule as defineRule14 } from "@oxlint/plugins";
|
|
1449
|
+
var DIRECTIVE = /^\s*(?:@ts-(?:ignore|expect-error|nocheck)|(?:es|ox)lint-(?:disable|enable)|prettier-ignore|biome-ignore|[cv]8 ignore|istanbul ignore|webpack[A-Z])/;
|
|
1450
|
+
var noMemberComments = defineRule14({
|
|
1451
|
+
meta: {
|
|
1452
|
+
type: "suggestion",
|
|
1453
|
+
fixable: "code",
|
|
1454
|
+
schema: [{
|
|
1455
|
+
type: "object",
|
|
1456
|
+
additionalProperties: false,
|
|
1457
|
+
properties: {
|
|
1458
|
+
allowWhy: { type: "boolean", default: true }
|
|
1459
|
+
}
|
|
1460
|
+
}],
|
|
1461
|
+
messages: {
|
|
1462
|
+
memberComment: "Remove this comment from the type member."
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
createOnce(context) {
|
|
1466
|
+
const reported = new Set;
|
|
1467
|
+
function checkMember(node) {
|
|
1468
|
+
const parent = node.parent;
|
|
1469
|
+
if (parent.type !== "TSInterfaceBody" && parent.type !== "TSTypeLiteral" && parent.type !== "ObjectExpression")
|
|
1470
|
+
return;
|
|
1471
|
+
const source = context.sourceCode;
|
|
1472
|
+
const { allowWhy = true } = optionsFirst(context, {});
|
|
1473
|
+
const previousToken = source.getTokenBefore(node);
|
|
1474
|
+
const comments = [
|
|
1475
|
+
...source.getCommentsBefore(node).filter((comment) => !previousToken || previousToken.range[0] === parent.range[0] || previousToken.loc.end.line !== comment.loc.start.line),
|
|
1476
|
+
...source.getCommentsAfter(node).filter((comment) => comment.loc.start.line === node.loc.end.line)
|
|
1477
|
+
].filter((comment) => comment.range[0] > parent.range[0] && comment.range[1] < parent.range[1]);
|
|
1478
|
+
let whyContinuation = false;
|
|
1479
|
+
let previousLine = -1;
|
|
1480
|
+
const removable = [];
|
|
1481
|
+
for (const comment of comments) {
|
|
1482
|
+
const value = (comment.type === "Block" ? comment.value.replace(/^\s*\* ?/gm, "") : comment.value).trimStart();
|
|
1483
|
+
const isWhy = value.startsWith("Why: ") || comment.type === "Line" && whyContinuation && comment.loc.start.line === previousLine + 1;
|
|
1484
|
+
whyContinuation = comment.type === "Line" && isWhy;
|
|
1485
|
+
previousLine = comment.loc.end.line;
|
|
1486
|
+
if (reported.has(comment.range[0]))
|
|
1487
|
+
continue;
|
|
1488
|
+
reported.add(comment.range[0]);
|
|
1489
|
+
if (allowWhy && isWhy)
|
|
1490
|
+
continue;
|
|
1491
|
+
if (DIRECTIVE.test(value))
|
|
1492
|
+
continue;
|
|
1493
|
+
removable.push(comment);
|
|
1494
|
+
}
|
|
1495
|
+
for (let index = 0;index < removable.length; ) {
|
|
1496
|
+
let last = index;
|
|
1497
|
+
while (last + 1 < removable.length && removable[last + 1].loc.start.line === removable[last].loc.end.line + 1)
|
|
1498
|
+
last++;
|
|
1499
|
+
const first = removable[index];
|
|
1500
|
+
const final = removable[last];
|
|
1501
|
+
index = last + 1;
|
|
1502
|
+
context.report({
|
|
1503
|
+
loc: { start: first.loc.start, end: final.loc.end },
|
|
1504
|
+
messageId: "memberComment",
|
|
1505
|
+
fix(fixer) {
|
|
1506
|
+
const text = source.text;
|
|
1507
|
+
const lineStart = text.lastIndexOf(`
|
|
1508
|
+
`, first.range[0] - 1) + 1;
|
|
1509
|
+
const before = text.slice(lineStart, first.range[0]);
|
|
1510
|
+
const start = first.range[0] - (/[\t ]*$/.exec(before)?.[0].length ?? 0);
|
|
1511
|
+
const after = text.slice(final.range[1]);
|
|
1512
|
+
const trailingLines = after.match(/^[\t ]*\r?\n(?:[\t ]*\r?\n)*/);
|
|
1513
|
+
if (trailingLines) {
|
|
1514
|
+
const end = final.range[1] + trailingLines[0].length;
|
|
1515
|
+
if (/^[\t ]*$/.test(before))
|
|
1516
|
+
return fixer.removeRange([lineStart, end]);
|
|
1517
|
+
const newline = trailingLines[0].includes(`\r
|
|
1518
|
+
`) ? `\r
|
|
1519
|
+
` : `
|
|
1520
|
+
`;
|
|
1521
|
+
return fixer.replaceTextRange([start, end], newline);
|
|
1522
|
+
}
|
|
1523
|
+
return fixer.removeRange([start, final.range[1]]);
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
Program() {
|
|
1530
|
+
reported.clear();
|
|
1531
|
+
},
|
|
1532
|
+
TSPropertySignature: checkMember,
|
|
1533
|
+
TSMethodSignature: checkMember,
|
|
1534
|
+
TSIndexSignature: checkMember,
|
|
1535
|
+
TSCallSignatureDeclaration: checkMember,
|
|
1536
|
+
TSConstructSignatureDeclaration: checkMember,
|
|
1537
|
+
Property: checkMember,
|
|
1538
|
+
SpreadElement: checkMember
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1351
1543
|
// src/rules/no-rederive-schema.ts
|
|
1352
|
-
import { defineRule as
|
|
1353
|
-
var noRederiveSchema =
|
|
1544
|
+
import { defineRule as defineRule15 } from "@oxlint/plugins";
|
|
1545
|
+
var noRederiveSchema = defineRule15({
|
|
1354
1546
|
meta: {
|
|
1355
1547
|
type: "problem",
|
|
1356
1548
|
schema: [{
|
|
@@ -1395,8 +1587,8 @@ var noRederiveSchema = defineRule13({
|
|
|
1395
1587
|
});
|
|
1396
1588
|
|
|
1397
1589
|
// src/rules/no-restricted-files.ts
|
|
1398
|
-
import { defineRule as
|
|
1399
|
-
var noRestrictedFiles =
|
|
1590
|
+
import { defineRule as defineRule16 } from "@oxlint/plugins";
|
|
1591
|
+
var noRestrictedFiles = defineRule16({
|
|
1400
1592
|
meta: {
|
|
1401
1593
|
type: "problem",
|
|
1402
1594
|
schema: [{
|
|
@@ -1426,7 +1618,7 @@ var noRestrictedFiles = defineRule14({
|
|
|
1426
1618
|
});
|
|
1427
1619
|
|
|
1428
1620
|
// src/rules/no-restricted-token.ts
|
|
1429
|
-
import { defineRule as
|
|
1621
|
+
import { defineRule as defineRule17 } from "@oxlint/plugins";
|
|
1430
1622
|
function tokenIsTypeOnlyImport(node) {
|
|
1431
1623
|
const parent = node.parent;
|
|
1432
1624
|
if (parent?.type === "ImportSpecifier") {
|
|
@@ -1434,7 +1626,7 @@ function tokenIsTypeOnlyImport(node) {
|
|
|
1434
1626
|
}
|
|
1435
1627
|
return (parent?.type === "ImportDefaultSpecifier" || parent?.type === "ImportNamespaceSpecifier") && parent.parent.type === "ImportDeclaration" && parent.parent.importKind === "type";
|
|
1436
1628
|
}
|
|
1437
|
-
var noRestrictedToken =
|
|
1629
|
+
var noRestrictedToken = defineRule17({
|
|
1438
1630
|
meta: {
|
|
1439
1631
|
type: "problem",
|
|
1440
1632
|
schema: [{
|
|
@@ -1454,9 +1646,9 @@ var noRestrictedToken = defineRule15({
|
|
|
1454
1646
|
createOnce(context) {
|
|
1455
1647
|
return {
|
|
1456
1648
|
before() {
|
|
1457
|
-
const
|
|
1649
|
+
const options = optionsFirst(context);
|
|
1458
1650
|
const filename = namingPosixPath(context.filename);
|
|
1459
|
-
const allowed =
|
|
1651
|
+
const allowed = options.allowIn.some((suffix) => filename.endsWith(suffix)) || (options.allowPathPatterns ?? []).some((pattern) => new RegExp(pattern).test(filename));
|
|
1460
1652
|
if (allowed) {
|
|
1461
1653
|
return false;
|
|
1462
1654
|
}
|
|
@@ -1476,8 +1668,8 @@ var noRestrictedToken = defineRule15({
|
|
|
1476
1668
|
});
|
|
1477
1669
|
|
|
1478
1670
|
// src/rules/no-runtime-in-types.ts
|
|
1479
|
-
import { defineRule as
|
|
1480
|
-
var noRuntimeInTypes =
|
|
1671
|
+
import { defineRule as defineRule18 } from "@oxlint/plugins";
|
|
1672
|
+
var noRuntimeInTypes = defineRule18({
|
|
1481
1673
|
meta: {
|
|
1482
1674
|
type: "problem",
|
|
1483
1675
|
schema: [{
|
|
@@ -1527,7 +1719,7 @@ var noRuntimeInTypes = defineRule16({
|
|
|
1527
1719
|
});
|
|
1528
1720
|
|
|
1529
1721
|
// src/rules/no-single-use-scalar-schema.ts
|
|
1530
|
-
import { defineRule as
|
|
1722
|
+
import { defineRule as defineRule19 } from "@oxlint/plugins";
|
|
1531
1723
|
function isScalarSchema(node, namespaces, structuralMethods) {
|
|
1532
1724
|
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
1533
1725
|
return false;
|
|
@@ -1549,8 +1741,8 @@ function isDirectObjectField(identifier, ancestors, namespaces, structuralMethod
|
|
|
1549
1741
|
const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
|
|
1550
1742
|
return Boolean(method === "object" && structuralMethods.has(method) && call.arguments.includes(object));
|
|
1551
1743
|
}
|
|
1552
|
-
function isAllowedStructuralElement(identifier, initializer, ancestors,
|
|
1553
|
-
if (!
|
|
1744
|
+
function isAllowedStructuralElement(identifier, initializer, ancestors, options, namespaces, structuralMethods) {
|
|
1745
|
+
if (!options.allowZodScalars || !schemasIsAllowedElement(initializer, true, namespaces, structuralMethods)) {
|
|
1554
1746
|
return false;
|
|
1555
1747
|
}
|
|
1556
1748
|
const parent = ancestors.at(-1);
|
|
@@ -1559,9 +1751,9 @@ function isAllowedStructuralElement(identifier, initializer, ancestors, options2
|
|
|
1559
1751
|
return false;
|
|
1560
1752
|
}
|
|
1561
1753
|
const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
|
|
1562
|
-
return Boolean(method &&
|
|
1754
|
+
return Boolean(method && options.methods.includes(method) && schemasElements(call).includes(identifier));
|
|
1563
1755
|
}
|
|
1564
|
-
var noSingleUseScalarSchema =
|
|
1756
|
+
var noSingleUseScalarSchema = defineRule19({
|
|
1565
1757
|
meta: {
|
|
1566
1758
|
type: "suggestion",
|
|
1567
1759
|
schema: [{
|
|
@@ -1596,9 +1788,9 @@ var noSingleUseScalarSchema = defineRule17({
|
|
|
1596
1788
|
}
|
|
1597
1789
|
},
|
|
1598
1790
|
"Program:exit"(program) {
|
|
1599
|
-
const
|
|
1600
|
-
const namespaces = new Set(
|
|
1601
|
-
const structuralMethods = new Set(
|
|
1791
|
+
const options = optionsFirst(context);
|
|
1792
|
+
const namespaces = new Set(options.namespaces);
|
|
1793
|
+
const structuralMethods = new Set(options.structuralMethods);
|
|
1602
1794
|
const exportedNames = new Set(exportsCollect(program).flatMap((binding) => binding.localName ? [binding.localName] : []));
|
|
1603
1795
|
for (const declarator of candidates) {
|
|
1604
1796
|
const name = declarator.id.type === "Identifier" ? declarator.id.name : undefined;
|
|
@@ -1607,14 +1799,14 @@ var noSingleUseScalarSchema = defineRule17({
|
|
|
1607
1799
|
continue;
|
|
1608
1800
|
}
|
|
1609
1801
|
const variable = context.sourceCode.getDeclaredVariables(declarator).find((candidate) => candidate.name === name);
|
|
1610
|
-
const references = variable?.references.filter((
|
|
1802
|
+
const references = variable?.references.filter((reference) => reference.isRead()) ?? [];
|
|
1611
1803
|
if (references.length !== 1) {
|
|
1612
1804
|
continue;
|
|
1613
1805
|
}
|
|
1614
1806
|
const reference = references[0];
|
|
1615
1807
|
const identifier = reference.identifier;
|
|
1616
1808
|
const ancestors = context.sourceCode.getAncestors(identifier);
|
|
1617
|
-
const inlineable = isDirectObjectField(identifier, ancestors, namespaces, structuralMethods) || isAllowedStructuralElement(identifier, initializer, ancestors,
|
|
1809
|
+
const inlineable = isDirectObjectField(identifier, ancestors, namespaces, structuralMethods) || isAllowedStructuralElement(identifier, initializer, ancestors, options, namespaces, structuralMethods);
|
|
1618
1810
|
if (inlineable) {
|
|
1619
1811
|
context.report({
|
|
1620
1812
|
node: declarator.id,
|
|
@@ -1629,8 +1821,8 @@ var noSingleUseScalarSchema = defineRule17({
|
|
|
1629
1821
|
});
|
|
1630
1822
|
|
|
1631
1823
|
// src/rules/no-top-level-functions.ts
|
|
1632
|
-
import { defineRule as
|
|
1633
|
-
var noTopLevelFunctions =
|
|
1824
|
+
import { defineRule as defineRule20 } from "@oxlint/plugins";
|
|
1825
|
+
var noTopLevelFunctions = defineRule20({
|
|
1634
1826
|
meta: {
|
|
1635
1827
|
type: "problem",
|
|
1636
1828
|
schema: [{
|
|
@@ -1687,8 +1879,8 @@ var noTopLevelFunctions = defineRule18({
|
|
|
1687
1879
|
});
|
|
1688
1880
|
|
|
1689
1881
|
// src/rules/no-trivial-functions.ts
|
|
1690
|
-
import { defineRule as
|
|
1691
|
-
var noTrivialFunctions =
|
|
1882
|
+
import { defineRule as defineRule21 } from "@oxlint/plugins";
|
|
1883
|
+
var noTrivialFunctions = defineRule21({
|
|
1692
1884
|
meta: {
|
|
1693
1885
|
type: "problem",
|
|
1694
1886
|
schema: [{
|
|
@@ -1726,8 +1918,8 @@ var noTrivialFunctions = defineRule19({
|
|
|
1726
1918
|
});
|
|
1727
1919
|
|
|
1728
1920
|
// src/rules/no-type-declarations.ts
|
|
1729
|
-
import { defineRule as
|
|
1730
|
-
var noTypeDeclarations =
|
|
1921
|
+
import { defineRule as defineRule22 } from "@oxlint/plugins";
|
|
1922
|
+
var noTypeDeclarations = defineRule22({
|
|
1731
1923
|
meta: {
|
|
1732
1924
|
type: "problem",
|
|
1733
1925
|
schema: [{
|
|
@@ -1762,8 +1954,8 @@ var noTypeDeclarations = defineRule20({
|
|
|
1762
1954
|
});
|
|
1763
1955
|
|
|
1764
1956
|
// src/rules/no-unescaped-like.ts
|
|
1765
|
-
import { defineRule as
|
|
1766
|
-
var noUnescapedLike =
|
|
1957
|
+
import { defineRule as defineRule23 } from "@oxlint/plugins";
|
|
1958
|
+
var noUnescapedLike = defineRule23({
|
|
1767
1959
|
meta: {
|
|
1768
1960
|
type: "problem",
|
|
1769
1961
|
schema: [{
|
|
@@ -1782,21 +1974,21 @@ var noUnescapedLike = defineRule21({
|
|
|
1782
1974
|
let sanitized = new Set;
|
|
1783
1975
|
return {
|
|
1784
1976
|
Program(program) {
|
|
1785
|
-
const { sanitizers
|
|
1786
|
-
sanitized = allowSanitizedBindings ? sanitizersConfiguredBindings(program, new Set(
|
|
1977
|
+
const { sanitizers, allowSanitizedBindings = true } = optionsFirst(context);
|
|
1978
|
+
sanitized = allowSanitizedBindings ? sanitizersConfiguredBindings(program, new Set(sanitizers)) : new Set;
|
|
1787
1979
|
},
|
|
1788
1980
|
CallExpression(node) {
|
|
1789
1981
|
if (node.callee.type !== "MemberExpression") {
|
|
1790
1982
|
return;
|
|
1791
1983
|
}
|
|
1792
|
-
const { methods, sanitizers
|
|
1984
|
+
const { methods, sanitizers } = optionsFirst(context);
|
|
1793
1985
|
const method = astStaticMemberName(node.callee);
|
|
1794
1986
|
if (!method || !methods.includes(method)) {
|
|
1795
1987
|
return;
|
|
1796
1988
|
}
|
|
1797
1989
|
const value = node.arguments.at(-1);
|
|
1798
1990
|
const allowedIdentifier = value?.type === "Identifier" && sanitized.has(value.name);
|
|
1799
|
-
if (!sanitizersCallsConfigured(value, new Set(
|
|
1991
|
+
if (!sanitizersCallsConfigured(value, new Set(sanitizers)) && !allowedIdentifier) {
|
|
1800
1992
|
context.report({
|
|
1801
1993
|
node: value ?? node,
|
|
1802
1994
|
messageId: "unescaped",
|
|
@@ -1809,8 +2001,8 @@ var noUnescapedLike = defineRule21({
|
|
|
1809
2001
|
});
|
|
1810
2002
|
|
|
1811
2003
|
// src/rules/only-export-components.ts
|
|
1812
|
-
import { defineRule as
|
|
1813
|
-
var onlyExportComponents =
|
|
2004
|
+
import { defineRule as defineRule24 } from "@oxlint/plugins";
|
|
2005
|
+
var onlyExportComponents = defineRule24({
|
|
1814
2006
|
meta: {
|
|
1815
2007
|
type: "problem",
|
|
1816
2008
|
schema: [{
|
|
@@ -1865,9 +2057,146 @@ var onlyExportComponents = defineRule22({
|
|
|
1865
2057
|
}
|
|
1866
2058
|
});
|
|
1867
2059
|
|
|
2060
|
+
// src/rules/only-export-constants.ts
|
|
2061
|
+
import { defineRule as defineRule25 } from "@oxlint/plugins";
|
|
2062
|
+
function bindingNames(pattern) {
|
|
2063
|
+
switch (pattern.type) {
|
|
2064
|
+
case "Identifier":
|
|
2065
|
+
return [pattern.name];
|
|
2066
|
+
case "AssignmentPattern":
|
|
2067
|
+
return bindingNames(pattern.left);
|
|
2068
|
+
case "RestElement":
|
|
2069
|
+
return bindingNames(pattern.argument);
|
|
2070
|
+
case "ArrayPattern":
|
|
2071
|
+
return pattern.elements.flatMap((element) => element ? bindingNames(element) : []);
|
|
2072
|
+
case "ObjectPattern":
|
|
2073
|
+
return pattern.properties.flatMap((property) => bindingNames(property.type === "RestElement" ? property : property.value));
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
function moduleName(node) {
|
|
2077
|
+
return node.type === "Identifier" ? node.name : node.value;
|
|
2078
|
+
}
|
|
2079
|
+
function unwrap(expression) {
|
|
2080
|
+
if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "TSInstantiationExpression") {
|
|
2081
|
+
return unwrap(expression.expression);
|
|
2082
|
+
}
|
|
2083
|
+
return expression;
|
|
2084
|
+
}
|
|
2085
|
+
function collectBindings(program) {
|
|
2086
|
+
const bindings = new Map;
|
|
2087
|
+
for (const statement of program.body) {
|
|
2088
|
+
if (statement.type === "ImportDeclaration") {
|
|
2089
|
+
for (const specifier of statement.specifiers) {
|
|
2090
|
+
bindings.set(specifier.local.name, {
|
|
2091
|
+
kind: statement.importKind === "type" || specifier.type === "ImportSpecifier" && specifier.importKind === "type" ? "type" : "import"
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
2097
|
+
if (declaration?.type === "TSImportEqualsDeclaration") {
|
|
2098
|
+
bindings.set(declaration.id.name, { kind: declaration.importKind === "type" ? "type" : "import" });
|
|
2099
|
+
} else if (declaration?.type === "VariableDeclaration") {
|
|
2100
|
+
for (const item of declaration.declarations) {
|
|
2101
|
+
for (const name of bindingNames(item.id)) {
|
|
2102
|
+
bindings.set(name, {
|
|
2103
|
+
kind: declaration.kind === "const" ? "const" : "other",
|
|
2104
|
+
value: item.id.type === "Identifier" ? item.init : undefined
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
} else if (declaration && "id" in declaration && declaration.id?.type === "Identifier") {
|
|
2109
|
+
bindings.set(declaration.id.name, {
|
|
2110
|
+
kind: declaration.type === "TSTypeAliasDeclaration" || declaration.type === "TSInterfaceDeclaration" ? "type" : declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction" ? "function" : declaration.type === "ClassDeclaration" ? "class" : "other"
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return bindings;
|
|
2115
|
+
}
|
|
2116
|
+
var onlyExportConstants = defineRule25({
|
|
2117
|
+
meta: {
|
|
2118
|
+
type: "problem",
|
|
2119
|
+
schema: [{
|
|
2120
|
+
type: "object",
|
|
2121
|
+
additionalProperties: false,
|
|
2122
|
+
properties: {
|
|
2123
|
+
allowFunctionValues: { type: "boolean" },
|
|
2124
|
+
allowTypeExports: { type: "boolean" },
|
|
2125
|
+
allowReExports: { type: "boolean" }
|
|
2126
|
+
}
|
|
2127
|
+
}],
|
|
2128
|
+
messages: {
|
|
2129
|
+
nonConstant: "Export '{{name}}' must be a local const binding with an allowed value; types and re-exports require explicit options."
|
|
2130
|
+
}
|
|
2131
|
+
},
|
|
2132
|
+
createOnce(context) {
|
|
2133
|
+
return {
|
|
2134
|
+
Program(program) {
|
|
2135
|
+
const { allowFunctionValues = false, allowTypeExports = false, allowReExports = false } = optionsFirst(context, {});
|
|
2136
|
+
const bindings = collectBindings(program);
|
|
2137
|
+
function hasForbiddenValue(binding, seen = new Set) {
|
|
2138
|
+
if (seen.has(binding) || binding.kind === "class") {
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
if (binding.kind === "function") {
|
|
2142
|
+
return !allowFunctionValues;
|
|
2143
|
+
}
|
|
2144
|
+
if (!binding.value) {
|
|
2145
|
+
return false;
|
|
2146
|
+
}
|
|
2147
|
+
seen.add(binding);
|
|
2148
|
+
const value = unwrap(binding.value);
|
|
2149
|
+
if (value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") {
|
|
2150
|
+
return !allowFunctionValues;
|
|
2151
|
+
}
|
|
2152
|
+
if (value.type === "ClassExpression") {
|
|
2153
|
+
return true;
|
|
2154
|
+
}
|
|
2155
|
+
const target = value.type === "Identifier" ? bindings.get(value.name) : undefined;
|
|
2156
|
+
return target ? hasForbiddenValue(target, seen) : false;
|
|
2157
|
+
}
|
|
2158
|
+
function check(node, name, localName, typeOnly = false, reExport = false) {
|
|
2159
|
+
const binding = localName ? bindings.get(localName) : undefined;
|
|
2160
|
+
const allowed = typeOnly || !reExport && binding?.kind === "type" ? allowTypeExports : reExport || binding?.kind === "import" ? allowReExports : binding?.kind === "const" && !hasForbiddenValue(binding);
|
|
2161
|
+
if (!allowed) {
|
|
2162
|
+
context.report({ node, messageId: "nonConstant", data: { name } });
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
for (const statement of program.body) {
|
|
2166
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
2167
|
+
check(statement, "*", undefined, statement.exportKind === "type", true);
|
|
2168
|
+
} else if (statement.type === "ExportDefaultDeclaration") {
|
|
2169
|
+
const declaration = statement.declaration;
|
|
2170
|
+
check(statement, "default", declaration.type === "Identifier" ? declaration.name : undefined, declaration.type === "TSInterfaceDeclaration");
|
|
2171
|
+
} else if (statement.type === "TSExportAssignment") {
|
|
2172
|
+
check(statement, "export =");
|
|
2173
|
+
} else if (statement.type === "TSNamespaceExportDeclaration") {
|
|
2174
|
+
check(statement, statement.id.name);
|
|
2175
|
+
} else if (statement.type === "ExportNamedDeclaration") {
|
|
2176
|
+
const declaration = statement.declaration;
|
|
2177
|
+
if (declaration?.type === "VariableDeclaration") {
|
|
2178
|
+
for (const item of declaration.declarations) {
|
|
2179
|
+
for (const name of bindingNames(item.id)) {
|
|
2180
|
+
check(item, name, name);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
} else if (declaration) {
|
|
2184
|
+
const name = "id" in declaration && declaration.id?.type === "Identifier" ? declaration.id.name : "<anonymous>";
|
|
2185
|
+
check(declaration, name, name);
|
|
2186
|
+
}
|
|
2187
|
+
for (const specifier of statement.specifiers) {
|
|
2188
|
+
check(specifier, moduleName(specifier.exported), moduleName(specifier.local), statement.exportKind === "type" || specifier.exportKind === "type", Boolean(statement.source));
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
|
|
1868
2197
|
// src/rules/require-file-factory.ts
|
|
1869
|
-
import { defineRule as
|
|
1870
|
-
var requireFileFactory =
|
|
2198
|
+
import { defineRule as defineRule26 } from "@oxlint/plugins";
|
|
2199
|
+
var requireFileFactory = defineRule26({
|
|
1871
2200
|
meta: {
|
|
1872
2201
|
type: "problem",
|
|
1873
2202
|
schema: [{
|
|
@@ -1913,8 +2242,8 @@ var requireFileFactory = defineRule23({
|
|
|
1913
2242
|
});
|
|
1914
2243
|
|
|
1915
2244
|
// src/rules/require-object-params.ts
|
|
1916
|
-
import { defineRule as
|
|
1917
|
-
var requireObjectParams =
|
|
2245
|
+
import { defineRule as defineRule27 } from "@oxlint/plugins";
|
|
2246
|
+
var requireObjectParams = defineRule27({
|
|
1918
2247
|
meta: {
|
|
1919
2248
|
type: "problem",
|
|
1920
2249
|
schema: [],
|
|
@@ -1940,8 +2269,8 @@ var requireObjectParams = defineRule24({
|
|
|
1940
2269
|
});
|
|
1941
2270
|
|
|
1942
2271
|
// src/rules/require-orpc-output.ts
|
|
1943
|
-
import { defineRule as
|
|
1944
|
-
var requireOrpcOutput =
|
|
2272
|
+
import { defineRule as defineRule28 } from "@oxlint/plugins";
|
|
2273
|
+
var requireOrpcOutput = defineRule28({
|
|
1945
2274
|
meta: {
|
|
1946
2275
|
type: "problem",
|
|
1947
2276
|
schema: [{
|
|
@@ -1983,8 +2312,8 @@ var requireOrpcOutput = defineRule25({
|
|
|
1983
2312
|
});
|
|
1984
2313
|
|
|
1985
2314
|
// src/rules/require-paired-call.ts
|
|
1986
|
-
import { defineRule as
|
|
1987
|
-
var requirePairedCall =
|
|
2315
|
+
import { defineRule as defineRule29 } from "@oxlint/plugins";
|
|
2316
|
+
var requirePairedCall = defineRule29({
|
|
1988
2317
|
meta: {
|
|
1989
2318
|
type: "problem",
|
|
1990
2319
|
schema: [{
|
|
@@ -2039,8 +2368,8 @@ var requirePairedCall = defineRule26({
|
|
|
2039
2368
|
});
|
|
2040
2369
|
|
|
2041
2370
|
// src/rules/route-surface.ts
|
|
2042
|
-
import { defineRule as
|
|
2043
|
-
var routeSurface =
|
|
2371
|
+
import { defineRule as defineRule30 } from "@oxlint/plugins";
|
|
2372
|
+
var routeSurface = defineRule30({
|
|
2044
2373
|
meta: {
|
|
2045
2374
|
type: "problem",
|
|
2046
2375
|
schema: [{
|
|
@@ -2096,7 +2425,9 @@ var src_default = eslintCompatPlugin({
|
|
|
2096
2425
|
"no-file-level-helpers": noFileLevelHelpers,
|
|
2097
2426
|
"no-imported-type-alias": noImportedTypeAlias,
|
|
2098
2427
|
"no-inline-schema-elements": noInlineSchemaElements,
|
|
2428
|
+
"no-inline-types": noInlineTypes,
|
|
2099
2429
|
"no-local-schema-construction": noLocalSchemaConstruction,
|
|
2430
|
+
"no-member-comments": noMemberComments,
|
|
2100
2431
|
"no-rederive-schema": noRederiveSchema,
|
|
2101
2432
|
"no-restricted-files": noRestrictedFiles,
|
|
2102
2433
|
"no-restricted-token": noRestrictedToken,
|
|
@@ -2107,6 +2438,7 @@ var src_default = eslintCompatPlugin({
|
|
|
2107
2438
|
"no-type-declarations": noTypeDeclarations,
|
|
2108
2439
|
"no-unescaped-like": noUnescapedLike,
|
|
2109
2440
|
"only-export-components": onlyExportComponents,
|
|
2441
|
+
"only-export-constants": onlyExportConstants,
|
|
2110
2442
|
"require-file-factory": requireFileFactory,
|
|
2111
2443
|
"require-object-params": requireObjectParams,
|
|
2112
2444
|
"require-orpc-output": requireOrpcOutput,
|