oxlint-frontier-style 1.0.0 → 1.1.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Steve Brand
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Brand
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -28,6 +28,11 @@ All declarations must follow the following order:
28
28
 
29
29
  This order apparently makes natural sense to some developers.
30
30
 
31
+ _Configurable_:
32
+
33
+ - `exceptions` match against literal names (e.g. `RULE`, `COMPONENT`, `TRANSFORMER`)
34
+ - `exceptionPatterns` match against RegEx patterns (e.g. `.*_OUTPUT` to except names like `DATA_OUTPUT`)
35
+
31
36
  ### `SCREAMING_SNAKE_CASE` for module-level constants
32
37
 
33
38
  All module-level constants must be spelled in `SCREAMING_SNAKE_CASE`.
@@ -13,22 +13,22 @@ declare const _default: {
13
13
  notScreamingSnake: string;
14
14
  };
15
15
  schema: {
16
- type: "object";
17
- properties: {
18
- exceptions: {
19
- type: "array";
20
- items: {
21
- type: "string";
16
+ readonly type: "object";
17
+ readonly properties: {
18
+ readonly exceptions: {
19
+ readonly type: "array";
20
+ readonly items: {
21
+ readonly type: "string";
22
22
  };
23
23
  };
24
- exceptionPatterns: {
25
- type: "array";
26
- items: {
27
- type: "string";
24
+ readonly exceptionPatterns: {
25
+ readonly type: "array";
26
+ readonly items: {
27
+ readonly type: "string";
28
28
  };
29
29
  };
30
30
  };
31
- additionalProperties: false;
31
+ readonly additionalProperties: false;
32
32
  }[];
33
33
  };
34
34
  create(context: Context): Visitor;
@@ -2,6 +2,7 @@
2
2
  * @file Rule: module-level `const` identifiers must be `SCREAMING_SNAKE_CASE`.
3
3
  */
4
4
  import { asVariableDeclaration, unwrapExport } from "../shared/ast.js";
5
+ import { EXCEPTION_SCHEMA, makeIsExceptedName, parseExceptionOptions, } from "../shared/exceptions.js";
5
6
  /**
6
7
  * Rule identifier, used to prefix option-validation errors.
7
8
  */
@@ -31,56 +32,6 @@ function asIdentifier(node) {
31
32
  function isExemptInitializer(init) {
32
33
  return init !== null && EXEMPT_INIT_TYPES.has(init.type);
33
34
  }
34
- /**
35
- * Whether a value is an array whose every element is a string.
36
- */
37
- function isStringArray(value) {
38
- return (Array.isArray(value) && value.every((item) => typeof item === "string"));
39
- }
40
- /**
41
- * Throws if `pattern` is not a valid regular expression source.
42
- */
43
- function assertValidPattern(pattern) {
44
- try {
45
- new RegExp(pattern);
46
- }
47
- catch (error) {
48
- throw new SyntaxError(`${RULE_ID}: invalid \`exceptionPatterns\` entry ${JSON.stringify(pattern)}: ${error.message}`);
49
- }
50
- }
51
- /**
52
- * Parses the raw first config element into typed options.
53
- *
54
- * The plugin interface types options as arbitrary JSON, so the shape declared by `meta.schema` is not guaranteed at runtime.
55
- */
56
- function parseOptions(raw) {
57
- if (raw === undefined || raw === null)
58
- return {};
59
- if (typeof raw !== "object" || Array.isArray(raw))
60
- throw new TypeError(`${RULE_ID}: options must be an object, received ${Array.isArray(raw) ? "array" : typeof raw}.`);
61
- const { exceptions, exceptionPatterns, ...rest } = raw;
62
- const unknownKeys = Object.keys(rest);
63
- if (unknownKeys.length > 0)
64
- throw new TypeError(`${RULE_ID}: unknown option(s): ${unknownKeys.join(", ")}.`);
65
- if (exceptions !== undefined && !isStringArray(exceptions))
66
- throw new TypeError(`${RULE_ID}: \`exceptions\` must be an array of strings.`);
67
- if (exceptionPatterns !== undefined && !isStringArray(exceptionPatterns))
68
- throw new TypeError(`${RULE_ID}: \`exceptionPatterns\` must be an array of strings.`);
69
- for (const pattern of exceptionPatterns ?? [])
70
- assertValidPattern(pattern);
71
- return { exceptions, exceptionPatterns };
72
- }
73
- /**
74
- * Builds a predicate that reports whether a name is exempt by literal name or matching pattern.
75
- */
76
- function makeIsExceptedName(options) {
77
- const exceptions = new Set(options.exceptions ?? []);
78
- const patterns = (options.exceptionPatterns ?? []).map((pattern) => new RegExp(pattern));
79
- return function isExceptedName(name) {
80
- return (exceptions.has(name) ||
81
- patterns.some((pattern) => pattern.test(name)));
82
- };
83
- }
84
35
  /**
85
36
  * Reports any module-level `const` identifier that is neither exempt nor `SCREAMING_SNAKE_CASE`.
86
37
  */
@@ -117,22 +68,10 @@ export default {
117
68
  messages: {
118
69
  notScreamingSnake: "Module-level constant `{{name}}` must be SCREAMING_SNAKE_CASE.",
119
70
  },
120
- schema: [
121
- {
122
- type: "object",
123
- properties: {
124
- exceptions: { type: "array", items: { type: "string" } },
125
- exceptionPatterns: {
126
- type: "array",
127
- items: { type: "string" },
128
- },
129
- },
130
- additionalProperties: false,
131
- },
132
- ],
71
+ schema: [EXCEPTION_SCHEMA],
133
72
  },
134
73
  create(context) {
135
- const options = parseOptions(context.options[0]);
74
+ const options = parseExceptionOptions(context.options[0], RULE_ID);
136
75
  const isExceptedName = makeIsExceptedName(options);
137
76
  return {
138
77
  Program(node) {
@@ -12,6 +12,24 @@ declare const _default: {
12
12
  messages: {
13
13
  outOfOrder: string;
14
14
  };
15
+ schema: {
16
+ readonly type: "object";
17
+ readonly properties: {
18
+ readonly exceptions: {
19
+ readonly type: "array";
20
+ readonly items: {
21
+ readonly type: "string";
22
+ };
23
+ };
24
+ readonly exceptionPatterns: {
25
+ readonly type: "array";
26
+ readonly items: {
27
+ readonly type: "string";
28
+ };
29
+ };
30
+ };
31
+ readonly additionalProperties: false;
32
+ }[];
15
33
  };
16
34
  create(context: Context): Visitor;
17
35
  };
@@ -2,6 +2,11 @@
2
2
  * @file Rule: enforce top-level declaration order.
3
3
  */
4
4
  import { asVariableDeclaration, isExportStatement, unwrapExport, } from "../shared/ast.js";
5
+ import { EXCEPTION_SCHEMA, makeIsExceptedName, parseExceptionOptions, } from "../shared/exceptions.js";
6
+ /**
7
+ * Rule identifier, used to prefix option-validation errors.
8
+ */
9
+ const RULE_ID = "module-structure-order";
5
10
  /**
6
11
  * Human-readable labels for each declaration rank, keyed by descending priority.
7
12
  */
@@ -52,12 +57,39 @@ function rankOf(node) {
52
57
  }
53
58
  return rankOfDeclaration(node);
54
59
  }
60
+ /**
61
+ * Returns the identifier names a top-level statement binds, unwrapping any export.
62
+ *
63
+ * Returns an empty array for statements with no simple bound name, including any variable statement that binds via destructuring (those bindings cannot be matched).
64
+ */
65
+ function declaredNames(node) {
66
+ const inner = unwrapExport(node);
67
+ if (inner.type === "FunctionDeclaration" ||
68
+ inner.type === "TSTypeAliasDeclaration" ||
69
+ inner.type === "TSInterfaceDeclaration")
70
+ return inner.id === null ? [] : [inner.id.name];
71
+ const declaration = asVariableDeclaration(inner);
72
+ if (declaration === null)
73
+ return [];
74
+ const names = [];
75
+ for (const declarator of declaration.declarations) {
76
+ if (declarator.id.type !== "Identifier")
77
+ return [];
78
+ names.push(declarator.id.name);
79
+ }
80
+ return names;
81
+ }
55
82
  /**
56
83
  * Walks the program body and reports any statement whose rank falls below a rank already seen.
84
+ *
85
+ * Statements whose every bound name is excepted are skipped entirely.
57
86
  */
58
- function checkProgram(context, node) {
87
+ function checkProgram(context, node, isExceptedName) {
59
88
  let maxRank = 0;
60
89
  for (const statement of node.body) {
90
+ const names = declaredNames(statement);
91
+ if (names.length > 0 && names.every(isExceptedName))
92
+ continue;
61
93
  const rank = rankOf(statement);
62
94
  if (rank === null)
63
95
  continue;
@@ -85,11 +117,14 @@ export default {
85
117
  messages: {
86
118
  outOfOrder: "{{kind}} are out of order. Expected: imports, types, interfaces, const, let, functions, exports.",
87
119
  },
120
+ schema: [EXCEPTION_SCHEMA],
88
121
  },
89
122
  create(context) {
123
+ const options = parseExceptionOptions(context.options[0], RULE_ID);
124
+ const isExceptedName = makeIsExceptedName(options);
90
125
  return {
91
126
  Program(node) {
92
- checkProgram(context, node);
127
+ checkProgram(context, node, isExceptedName);
93
128
  },
94
129
  };
95
130
  },
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @file Shared exception-list options: literal and pattern name matching reused across rules.
3
+ */
4
+ /**
5
+ * Options for an exception list, passed as the first config element.
6
+ */
7
+ export interface ExceptionOptions {
8
+ /** Exceptions matched by literal value. */
9
+ exceptions?: string[];
10
+ /** Exceptions matched with a regex pattern. */
11
+ exceptionPatterns?: string[];
12
+ }
13
+ /**
14
+ * JSON Schema for the exception-list options object.
15
+ */
16
+ export declare const EXCEPTION_SCHEMA: {
17
+ readonly type: "object";
18
+ readonly properties: {
19
+ readonly exceptions: {
20
+ readonly type: "array";
21
+ readonly items: {
22
+ readonly type: "string";
23
+ };
24
+ };
25
+ readonly exceptionPatterns: {
26
+ readonly type: "array";
27
+ readonly items: {
28
+ readonly type: "string";
29
+ };
30
+ };
31
+ };
32
+ readonly additionalProperties: false;
33
+ };
34
+ /**
35
+ * Parses the raw first config element into typed exception options.
36
+ *
37
+ * The plugin interface types options as arbitrary JSON, so the shape declared by `meta.schema` is not guaranteed at runtime.
38
+ */
39
+ export declare function parseExceptionOptions(raw: unknown, ruleId: string): ExceptionOptions;
40
+ /**
41
+ * Builds a predicate that reports whether a name is exempt by literal name or matching pattern.
42
+ */
43
+ export declare function makeIsExceptedName(options: ExceptionOptions): (name: string) => boolean;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @file Shared exception-list options: literal and pattern name matching reused across rules.
3
+ */
4
+ /**
5
+ * JSON Schema for the exception-list options object.
6
+ */
7
+ export const EXCEPTION_SCHEMA = {
8
+ type: "object",
9
+ properties: {
10
+ exceptions: { type: "array", items: { type: "string" } },
11
+ exceptionPatterns: { type: "array", items: { type: "string" } },
12
+ },
13
+ additionalProperties: false,
14
+ };
15
+ /**
16
+ * Whether a value is an array whose every element is a string.
17
+ */
18
+ function isStringArray(value) {
19
+ return (Array.isArray(value) && value.every((item) => typeof item === "string"));
20
+ }
21
+ /**
22
+ * Throws if `pattern` is not a valid regular expression source.
23
+ */
24
+ function assertValidPattern(pattern, ruleId) {
25
+ try {
26
+ new RegExp(pattern);
27
+ }
28
+ catch (error) {
29
+ throw new SyntaxError(`${ruleId}: invalid \`exceptionPatterns\` entry ${JSON.stringify(pattern)}: ${error.message}`);
30
+ }
31
+ }
32
+ /**
33
+ * Parses the raw first config element into typed exception options.
34
+ *
35
+ * The plugin interface types options as arbitrary JSON, so the shape declared by `meta.schema` is not guaranteed at runtime.
36
+ */
37
+ export function parseExceptionOptions(raw, ruleId) {
38
+ if (raw === undefined || raw === null)
39
+ return {};
40
+ if (typeof raw !== "object" || Array.isArray(raw))
41
+ throw new TypeError(`${ruleId}: options must be an object, received ${Array.isArray(raw) ? "array" : typeof raw}.`);
42
+ const { exceptions, exceptionPatterns, ...rest } = raw;
43
+ const unknownKeys = Object.keys(rest);
44
+ if (unknownKeys.length > 0)
45
+ throw new TypeError(`${ruleId}: unknown option(s): ${unknownKeys.join(", ")}.`);
46
+ if (exceptions !== undefined && !isStringArray(exceptions))
47
+ throw new TypeError(`${ruleId}: \`exceptions\` must be an array of strings.`);
48
+ if (exceptionPatterns !== undefined && !isStringArray(exceptionPatterns))
49
+ throw new TypeError(`${ruleId}: \`exceptionPatterns\` must be an array of strings.`);
50
+ for (const pattern of exceptionPatterns ?? [])
51
+ assertValidPattern(pattern, ruleId);
52
+ return { exceptions, exceptionPatterns };
53
+ }
54
+ /**
55
+ * Builds a predicate that reports whether a name is exempt by literal name or matching pattern.
56
+ */
57
+ export function makeIsExceptedName(options) {
58
+ const exceptions = new Set(options.exceptions ?? []);
59
+ const patterns = (options.exceptionPatterns ?? []).map((pattern) => new RegExp(pattern));
60
+ return function isExceptedName(name) {
61
+ return (exceptions.has(name) ||
62
+ patterns.some((pattern) => pattern.test(name)));
63
+ };
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-frontier-style",
3
- "version": "1.0.0",
3
+ "version": "1.1.2",
4
4
  "description": "oxlint plugin enforcing the Frontier code style",
5
5
  "keywords": [
6
6
  "oxlint",
@@ -34,6 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@oxlint/plugins": "^1.48.0",
36
36
  "@types/node": "^25.9.1",
37
+ "@typescript/native-preview": "^7.0.0-dev.20260616.1",
37
38
  "oxfmt": "^0.54.0",
38
39
  "oxlint": "^1.48.0",
39
40
  "oxlint-tsgolint": "^0.23.0"