oxlint-plugin-arch 0.12.6 → 0.13.1

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.
@@ -139,7 +139,7 @@ function optionsPatternsTest(patterns, value) {
139
139
  function optionsGlob(pattern) {
140
140
  const negated = [];
141
141
  const source = pattern.replaceAll(/!\(([^()]*)\)/g, (_, options) => {
142
- negated.push(options.split("|").map((option) => option.replaceAll(/[.+^$()|[\]\\*?{}]/g, "\\$&")).join("|"));
142
+ negated.push(options.split("|").map((option) => option.replaceAll(/[.+^$()|[\]\\?{}]/g, "\\$&").replaceAll("*", "[^/]*")).join("|"));
143
143
  return `\x02${negated.length - 1}\x03`;
144
144
  }).replaceAll(/[.+^$()|[\]\\]/g, "\\$&").replaceAll("?", "[^/]").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*").replaceAll(/\{([^}]*)\}/g, (_, options) => `(?:${options.split(",").join("|")})`);
145
145
  return new RegExp(`^${negated.reduce((text, names, index) => text.replace(`\x02${index}\x03`, `(?!(?:${names})(?:/|$))[^/]*`), source)}$`);
@@ -152,6 +152,9 @@ function optionsAssertMockingApis(patterns) {
152
152
  throw new OptionsError("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
153
153
  }
154
154
  }
155
+ function optionsEscape(value) {
156
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
157
+ }
155
158
 
156
159
  // src/utils/ast.utils.ts
157
160
  var astSkippedKeys = new Set(["parent", "type", "start", "end", "range", "loc", "tokens", "comments"]);
@@ -339,9 +342,6 @@ function astIsInlineFunction(node) {
339
342
  function astIsCall(node) {
340
343
  return node.type === "CallExpression" || node.type === "NewExpression";
341
344
  }
342
- function astIsPipe(node) {
343
- return node.callee.type === "MemberExpression" && astMemberName(node.callee) === "pipe";
344
- }
345
345
  function astChildren(node) {
346
346
  return Object.entries(node).filter(([key]) => !astSkippedKeys.has(key)).map(([, child]) => child);
347
347
  }
@@ -351,17 +351,20 @@ function astSome(value, test, enter = () => true) {
351
351
  }
352
352
  return astIsNode(value) && (test(value) || enter(value) && astChildren(value).some((child) => astSome(child, test, enter)));
353
353
  }
354
- function astCallDepth(value) {
354
+ function astCallDepth(value, callbacks = false) {
355
355
  if (Array.isArray(value)) {
356
- return Math.max(0, ...value.map(astCallDepth));
356
+ return Math.max(0, ...value.map((item) => astCallDepth(item, callbacks)));
357
357
  }
358
- if (!astIsNode(value) || astIsInlineFunction(value)) {
358
+ if (!astIsNode(value)) {
359
359
  return 0;
360
360
  }
361
+ if (astIsInlineFunction(value)) {
362
+ return callbacks ? astCallDepth(value.body, callbacks) : 0;
363
+ }
361
364
  if (astIsCall(value)) {
362
- return Math.max(value.arguments.length === 0 ? 0 : 1 + astCallDepth(value.arguments), astCallDepth(value.callee));
365
+ return Math.max(value.arguments.length === 0 ? 0 : 1 + astCallDepth(value.arguments, callbacks), astCallDepth(value.callee, callbacks));
363
366
  }
364
- return astCallDepth(astChildren(value));
367
+ return astCallDepth(astChildren(value), callbacks);
365
368
  }
366
369
 
367
370
  // src/vendor/dillon-anti-slop/shared/type-reference-name.ts
@@ -1247,7 +1250,6 @@ var noRuntimeTypeofRule = defineRule8({
1247
1250
 
1248
1251
  // src/vendor/dillon-anti-slop/rules/no-shape-in-symbol-names.ts
1249
1252
  import { defineRule as defineRule9 } from "@oxlint/plugins";
1250
- var FORBIDDEN_SYMBOL_NAME = "shape";
1251
1253
  function namesForeignApi(node) {
1252
1254
  const parent = node.parent;
1253
1255
  if (!parent) {
@@ -1279,22 +1281,29 @@ var noForbiddenTermInSymbolNamesRule = defineRule9({
1279
1281
  docs: {
1280
1282
  description: 'Disallow the case-insensitive substring "shape" in JavaScript, TypeScript, private, and JSX symbol names.'
1281
1283
  },
1284
+ schema: [{ type: "object", additionalProperties: false, properties: { terms: { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 } } }],
1282
1285
  messages: {
1283
- forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role; "shape" describes structure rather than ownership.'
1286
+ forbiddenSymbolName: 'Rename symbol "{{name}}" for its domain role; "{{term}}" describes structure rather than ownership.'
1284
1287
  }
1285
1288
  },
1286
1289
  createOnce(context) {
1290
+ let terms = [];
1287
1291
  const reportForbiddenSymbolName = (node) => {
1288
- if (!node.name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME) || namesForeignApi(node)) {
1292
+ const lower = node.name.toLowerCase();
1293
+ const term = terms.find((candidate) => lower.includes(candidate));
1294
+ if (term === undefined || namesForeignApi(node)) {
1289
1295
  return;
1290
1296
  }
1291
1297
  context.report({
1292
1298
  node,
1293
1299
  messageId: "forbiddenSymbolName",
1294
- data: { name: node.name }
1300
+ data: { name: node.name, term }
1295
1301
  });
1296
1302
  };
1297
1303
  return {
1304
+ before() {
1305
+ terms = (optionsFirst(context, {}).terms ?? ["shape"]).map((term) => term.toLowerCase());
1306
+ },
1298
1307
  Identifier: reportForbiddenSymbolName,
1299
1308
  PrivateIdentifier: reportForbiddenSymbolName,
1300
1309
  JSXIdentifier: reportForbiddenSymbolName
@@ -1750,18 +1759,22 @@ var requireSafetyCommentForTypeAssertionRule = defineRule15({
1750
1759
  docs: {
1751
1760
  description: "Require a nearby SAFETY comment for every TypeScript type assertion except const assertions."
1752
1761
  },
1762
+ schema: [{ type: "object", additionalProperties: false, properties: { comment: { type: "string", minLength: 1 } } }],
1753
1763
  messages: {
1754
- missingSafetyComment: "This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement."
1764
+ missingSafetyComment: "This type assertion has no `{{comment}}` justification. State the checked invariant immediately before the assertion or its containing statement."
1755
1765
  }
1756
1766
  },
1757
1767
  createOnce(context) {
1768
+ let comment = "SAFETY:";
1769
+ let marker = /(?<![\p{L}\p{N}_])SAFETY\s*:/u;
1758
1770
  const checkAssertion = (node) => {
1759
1771
  if (expressionsIsConstAssertion(node)) {
1760
1772
  return;
1761
1773
  }
1762
1774
  let current = node;
1775
+ const justified = (candidate) => candidate.end <= node.start && marker.test(candidate.value);
1763
1776
  for (;; ) {
1764
- if (context.sourceCode.getCommentsBefore(current).some((comment) => comment.end <= node.start && /\bSAFETY\s*:/u.test(comment.value))) {
1777
+ if (context.sourceCode.getCommentsBefore(current).some(justified)) {
1765
1778
  return;
1766
1779
  }
1767
1780
  const parent = current.parent;
@@ -1770,9 +1783,18 @@ var requireSafetyCommentForTypeAssertionRule = defineRule15({
1770
1783
  }
1771
1784
  current = parent;
1772
1785
  }
1773
- context.report({ node, messageId: "missingSafetyComment" });
1786
+ context.report({
1787
+ node,
1788
+ messageId: "missingSafetyComment",
1789
+ data: { comment }
1790
+ });
1774
1791
  };
1775
1792
  return {
1793
+ before() {
1794
+ comment = optionsFirst(context, {}).comment ?? "SAFETY:";
1795
+ const word = optionsEscape(comment.replace(/:\s*$/, ""));
1796
+ marker = new RegExp(`(?<![\\p{L}\\p{N}_])${word}\\s*:`, "u");
1797
+ },
1776
1798
  TSAsExpression: checkAssertion,
1777
1799
  TSTypeAssertion: checkAssertion
1778
1800
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-arch",
3
- "version": "0.12.6",
3
+ "version": "0.13.1",
4
4
  "description": "Configurable architecture rules for Oxlint JavaScript plugins.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,8 +57,11 @@
57
57
  "@effect/tsgo": ">=0.67.1",
58
58
  "@opsydyn/oxlint-effect": ">=2.0.1",
59
59
  "@shadcn/lint": ">=0.2.3",
60
+ "eslint-plugin-de-morgan": ">=2.3.0",
60
61
  "eslint-plugin-react": ">=7.37.5",
61
62
  "eslint-plugin-react-you-might-not-need-an-effect": ">=1.0.2",
63
+ "eslint-plugin-regexp": ">=3.3.3",
64
+ "eslint-plugin-sonarjs": ">=4.2.2",
62
65
  "eslint-plugin-svg-jsx": ">=1.3.0",
63
66
  "oxlint": ">=1.85.0",
64
67
  "oxlint-tailwindcss": ">=1.16.0",
@@ -70,8 +73,10 @@
70
73
  "@shadcn/lint": "0.2.3",
71
74
  "@types/node": "26.6.5",
72
75
  "bumpp": "12.3.0",
76
+ "eslint-plugin-de-morgan": "2.3.0",
73
77
  "eslint-plugin-react": "7.37.5",
74
78
  "eslint-plugin-react-you-might-not-need-an-effect": "1.0.2",
79
+ "eslint-plugin-regexp": "3.3.3",
75
80
  "eslint-plugin-svg-jsx": "1.3.0",
76
81
  "fallow": "3.36.0",
77
82
  "oxlint": "1.89.0",
@@ -111,12 +116,21 @@
111
116
  "@shadcn/lint": {
112
117
  "optional": true
113
118
  },
119
+ "eslint-plugin-de-morgan": {
120
+ "optional": true
121
+ },
114
122
  "eslint-plugin-react": {
115
123
  "optional": true
116
124
  },
117
125
  "eslint-plugin-react-you-might-not-need-an-effect": {
118
126
  "optional": true
119
127
  },
128
+ "eslint-plugin-regexp": {
129
+ "optional": true
130
+ },
131
+ "eslint-plugin-sonarjs": {
132
+ "optional": true
133
+ },
120
134
  "eslint-plugin-svg-jsx": {
121
135
  "optional": true
122
136
  },
@@ -14,6 +14,9 @@ The TanStack preset selects the enabled rules; this directory implements them
14
14
  and is not a separate preset. Upstream intentionally distributes source for
15
15
  vendoring and does not publish an official npm package.
16
16
 
17
+ Local options: `terms` on `no-shape-in-symbol-names` and `comment` on
18
+ `require-safety-comment-for-type-assertion`; their defaults keep upstream behavior.
19
+
17
20
  Local behavior fixes recognize `SAFETY:` comments on exported declarations and
18
21
  export-default assertions, while retaining statement boundaries for other
19
22
  assertions.