oxlint-plugin-arch 0.3.1 → 0.3.4

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - Options that took one regex string now also take a non-empty list, where any pattern may match: `pattern`, `allowPattern`, `allowNamePattern`, `denyTypePattern`, `hookPattern`, `rootPattern`, `factoryPattern`, `forbid`, and `require`. Single strings keep their behavior.
6
+ - `no-type-declarations` `allowPattern` entries starting with `=` or `^=` match a type alias's value (`= ` plus its source text) instead of its name, so `'^= ReturnType<typeof '` keeps factory return types next to their factory. A name pattern that starts with `=` or `^=` is now read as a value pattern.
7
+ - Fix `declaration-name` `pattern` with `g` or `y` flags rejecting every name after the first match.
8
+
9
+ ## 0.3.3
10
+
11
+ Version 0.3.2 was tagged but never published to npm; 0.3.3 contains its fixes.
12
+
13
+ - Fix `chain-newline` reporting chains that already have one call per line when the root is parenthesized (`(db as TestDb)\n .insertInto()`), the root spans several lines, or a comment sits between two links.
14
+ - Autofix `call-array-multiline` arrays with parenthesized elements, like `Promise.all([(a), (b)])`.
15
+
3
16
  ## 0.3.1
4
17
 
5
18
  - Fix `key-value-same-line` reporting `key: (` values whose parenthesized expression starts on the next line.
package/README.md CHANGED
@@ -76,6 +76,8 @@ export default defineConfig({
76
76
 
77
77
  42 rules in 9 groups. 🔧 means the rule can autofix. Each rule file in [`src/rules/`](src/rules) explains its behavior in plain English, and the tests in [`src/tests/`](src/tests) show every option shape.
78
78
 
79
+ Options that take one regex string (`pattern`, `allowPattern`, `forbid`, `require`, and the other `*Pattern` options) also take a non-empty list; any pattern in the list may match. Options that were already lists, such as `allowPatterns` and `allowCallees`, are unchanged.
80
+
79
81
  ### 📁 Files & folders
80
82
 
81
83
  | Rule | What it enforces |
@@ -198,6 +200,21 @@ Use `no-type-declarations` separately to control where named types may live. Wit
198
200
 
199
201
  </details>
200
202
 
203
+ ### `no-type-declarations`
204
+
205
+ Rejects type aliases and interfaces in matched files, so types live in one place.
206
+
207
+ | Option | Default | What it does |
208
+ | --- | --- | --- |
209
+ | `allowPattern` | none | Keep a declaration whose **name** matches. A pattern that starts with `=` or `^=` matches a type alias's **value** instead: `= ` followed by the value's source text, without surrounding parentheses or comments. Interfaces have no value, so only name patterns apply to them. |
210
+
211
+ ```ts
212
+ // allowPattern: ['(Service|Deps)$', '^= ReturnType<typeof ']
213
+ export type AiRetry = ReturnType<typeof makeAiRetry> // ✅ value matches
214
+ export interface OrderDeps { db: Database } // ✅ name matches
215
+ export type OrderCreateParams = { id: string } // ❌ move it to your types file
216
+ ```
217
+
201
218
  ### `only-export-constants`
202
219
 
203
220
  Every runtime export must point to a local `const`. Direct, destructured, renamed, and default exports of local constants all work.
package/dist/index.js CHANGED
@@ -551,6 +551,27 @@ function factoriesObjectPropertyName(property) {
551
551
  function optionsFirst(context, fallback) {
552
552
  return context.options[0] ?? fallback;
553
553
  }
554
+ var optionsPatternSchema = {
555
+ anyOf: [
556
+ { type: "string" },
557
+ { type: "array", items: { type: "string" }, minItems: 1 }
558
+ ]
559
+ };
560
+ function optionsPatterns(pattern, flags) {
561
+ return pattern === undefined ? [] : [pattern].flat().map((source) => new RegExp(source, flags));
562
+ }
563
+ function optionsOptionalPatterns(pattern, flags) {
564
+ return optionsPatterns(pattern === undefined ? undefined : [pattern].flat().filter((source) => source !== ""), flags);
565
+ }
566
+ function optionsPatternLabel(pattern) {
567
+ return pattern === undefined ? "" : [pattern].flat().join(" | ");
568
+ }
569
+ function optionsPatternsTest(patterns, value) {
570
+ return patterns.some((pattern) => {
571
+ pattern.lastIndex = 0;
572
+ return pattern.test(value);
573
+ });
574
+ }
554
575
  // src/utils/helpers/orpc.ts
555
576
  function orpcFindChainCall(expression, method) {
556
577
  if (expression.type === "CallExpression") {
@@ -815,32 +836,43 @@ function callArrayMultilineIsShadowed(context, root) {
815
836
  }
816
837
  return false;
817
838
  }
818
- function callArrayMultilineEdits(array, text, elementIndent, baseIndent, newline) {
839
+ function callArrayMultilineOuterRange(source, node) {
840
+ let range = [node.range[0], node.range[1]];
841
+ let before = source.getTokenBefore(node);
842
+ let after = source.getTokenAfter(node);
843
+ while (before?.value === "(" && after?.value === ")") {
844
+ range = [before.range[0], after.range[1]];
845
+ before = source.getTokenBefore(before);
846
+ after = source.getTokenAfter(after);
847
+ }
848
+ return range;
849
+ }
850
+ function callArrayMultilineEdits(array, source, text, elementIndent, baseIndent, newline) {
819
851
  const elements = array.elements;
820
852
  if (elements.some((element) => element === null))
821
853
  return null;
822
- const present = elements.filter((element) => element !== null);
854
+ const present = elements.filter((element) => element !== null).map((element) => callArrayMultilineOuterRange(source, element));
823
855
  const first = present[0];
824
856
  const last = present.at(-1);
825
857
  if (!first || !last)
826
858
  return null;
827
859
  const edits = [];
828
- const openingRange = [array.range[0] + 1, first.range[0]];
860
+ const openingRange = [array.range[0] + 1, first[0]];
829
861
  if (!/^\s*$/.test(text.slice(...openingRange)))
830
862
  return null;
831
863
  edits.push({ range: openingRange, text: `${newline}${elementIndent}` });
832
864
  for (let index = 1;index < present.length; index++) {
833
865
  const previous = present[index - 1];
834
866
  const current = present[index];
835
- const gap = text.slice(previous.range[1], current.range[0]);
867
+ const gap = text.slice(previous[1], current[0]);
836
868
  if (!/^,\s*$/.test(gap))
837
869
  return null;
838
870
  edits.push({
839
- range: [previous.range[1] + 1, current.range[0]],
871
+ range: [previous[1] + 1, current[0]],
840
872
  text: `${newline}${elementIndent}`
841
873
  });
842
874
  }
843
- const closingRange = [last.range[1], array.range[1] - 1];
875
+ const closingRange = [last[1], array.range[1] - 1];
844
876
  const closingGap = text.slice(...closingRange);
845
877
  if (!/^,?\s*$/.test(closingGap))
846
878
  return null;
@@ -898,7 +930,7 @@ var callArrayMultiline = defineRule({
898
930
  const text = source.text;
899
931
  const baseIndent = layoutLineIndent(text, array.range[0]);
900
932
  const hasComments = source.getCommentsInside(array).length > 0;
901
- const edits = hasComments ? null : callArrayMultilineEdits(array, text, baseIndent + indentUnit, baseIndent, newline);
933
+ const edits = hasComments ? null : callArrayMultilineEdits(array, source, text, baseIndent + indentUnit, baseIndent, newline);
902
934
  context.report({
903
935
  node: array,
904
936
  messageId: "multiline",
@@ -936,7 +968,7 @@ function chainNewlineRootIdentifier(node) {
936
968
  function chainNewlineMatches(root, links, group) {
937
969
  if (links.length < group.minDepth)
938
970
  return false;
939
- const hasCriteria = group.methods !== undefined || group.onlyMethods !== undefined || group.rootPattern !== undefined;
971
+ const hasCriteria = group.methods !== undefined || group.onlyMethods !== undefined || group.rootPatterns.length > 0;
940
972
  if (!hasCriteria)
941
973
  return true;
942
974
  if (group.methods && links.some((link) => link.methodName !== null && group.methods?.has(link.methodName))) {
@@ -945,7 +977,7 @@ function chainNewlineMatches(root, links, group) {
945
977
  if (group.onlyMethods && links.every((link) => link.methodName !== null && group.onlyMethods?.has(link.methodName)))
946
978
  return true;
947
979
  const rootName = chainNewlineRootIdentifier(root);
948
- return rootName !== null && group.rootPattern !== undefined && group.rootPattern.test(rootName);
980
+ return rootName !== null && optionsPatternsTest(group.rootPatterns, rootName);
949
981
  }
950
982
  var chainNewline = defineRule2({
951
983
  meta: {
@@ -964,7 +996,7 @@ var chainNewline = defineRule2({
964
996
  minDepth: { type: "integer", minimum: 1 },
965
997
  methods: { type: "array", items: { type: "string" } },
966
998
  onlyMethods: { type: "array", items: { type: "string" } },
967
- rootPattern: { type: "string" }
999
+ rootPattern: optionsPatternSchema
968
1000
  },
969
1001
  required: ["minDepth"]
970
1002
  }
@@ -990,7 +1022,7 @@ var chainNewline = defineRule2({
990
1022
  minDepth: group.minDepth,
991
1023
  methods: group.methods ? new Set(group.methods) : undefined,
992
1024
  onlyMethods: group.onlyMethods ? new Set(group.onlyMethods) : undefined,
993
- rootPattern: group.rootPattern === undefined ? undefined : new RegExp(group.rootPattern)
1025
+ rootPatterns: optionsPatterns(group.rootPattern)
994
1026
  }));
995
1027
  indentUnit = layoutIndentUnit(options.indent);
996
1028
  newline = layoutNewline(context.sourceCode.text);
@@ -1011,7 +1043,7 @@ var chainNewline = defineRule2({
1011
1043
  return;
1012
1044
  const source = context.sourceCode;
1013
1045
  const text = source.text;
1014
- const chainIndent = layoutLineIndent(text, root.range[0]) + indentUnit;
1046
+ const chainIndent = layoutLineIndent(text, node.range[0]) + indentUnit;
1015
1047
  const replacements = [];
1016
1048
  let fixable = true;
1017
1049
  let needsFix = false;
@@ -1021,14 +1053,19 @@ var chainNewline = defineRule2({
1021
1053
  needsFix = true;
1022
1054
  continue;
1023
1055
  }
1024
- const range = [link.member.object.range[1], link.member.property.range[0]];
1056
+ const operatorToken = source.getTokenBefore(link.member.property);
1057
+ const objectEnd = (operatorToken && source.getTokenBefore(operatorToken)) ?? link.member.object;
1058
+ const range = [objectEnd.range[1], link.member.property.range[0]];
1025
1059
  const gap = text.slice(...range);
1026
1060
  const operator = link.member.optional ? "?." : ".";
1027
1061
  const expected = `${newline}${chainIndent}${operator}`;
1028
- if (source.commentsExistBetween(link.member.object, link.member.property) || gap.trim() !== operator) {
1062
+ const hasComments = source.commentsExistBetween(objectEnd, link.member.property);
1063
+ if (hasComments || gap.trim() !== operator) {
1029
1064
  fixable = false;
1030
1065
  }
1031
- if (gap !== expected) {
1066
+ const lastComment = hasComments && operatorToken ? source.getTokenBefore(operatorToken, { includeComments: true }) : null;
1067
+ const alignedAfterComment = lastComment !== null && text.slice(lastComment.range[1], link.member.property.range[0]) === expected;
1068
+ if (gap !== expected && !alignedAfterComment) {
1032
1069
  needsFix = true;
1033
1070
  replacements.push({ range, text: expected });
1034
1071
  }
@@ -1058,14 +1095,14 @@ var declarationName = defineRule3({
1058
1095
  type: "array",
1059
1096
  items: { type: "string", enum: [...declarationsKinds] }
1060
1097
  },
1061
- pattern: { type: "string" },
1098
+ pattern: optionsPatternSchema,
1062
1099
  flags: { type: "string" },
1063
1100
  stem: { type: "string", enum: [...namingStemModes] },
1064
1101
  trailingRoles: { type: "array", items: { type: "string" } },
1065
1102
  roleSeparators: { type: "array", items: { type: "string" } },
1066
1103
  normalize: { type: "string", enum: ["remove-separators", "none"] },
1067
1104
  singularize: { type: "string", enum: ["none", "trailing-s"] },
1068
- allowPattern: { type: "string" }
1105
+ allowPattern: optionsPatternSchema
1069
1106
  }
1070
1107
  }],
1071
1108
  messages: {
@@ -1076,23 +1113,23 @@ var declarationName = defineRule3({
1076
1113
  createOnce(context) {
1077
1114
  let options = {};
1078
1115
  let kinds = null;
1079
- let allowed = null;
1080
- let expected = null;
1116
+ let allowed = [];
1117
+ let expected = [];
1081
1118
  let prefixes = [];
1082
1119
  function check(node) {
1083
1120
  const item = declarationsNamedFromNode(node);
1084
- if (!item || kinds && !kinds.has(item.kind) || allowed?.test(item.name)) {
1121
+ if (!item || kinds && !kinds.has(item.kind) || optionsPatternsTest(allowed, item.name)) {
1085
1122
  return;
1086
1123
  }
1087
1124
  const comparableName = options.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
1088
- const matches = expected ? expected.test(item.name) : prefixes.some((prefix) => comparableName.startsWith(prefix));
1125
+ const matches = expected.length > 0 ? optionsPatternsTest(expected, item.name) : prefixes.some((prefix) => comparableName.startsWith(prefix));
1089
1126
  if (matches) {
1090
1127
  return;
1091
1128
  }
1092
1129
  context.report({
1093
1130
  node: item.node,
1094
- messageId: expected ? "pattern" : "prefix",
1095
- data: expected ? { name: item.name, pattern: options.pattern ?? "" } : { name: item.name, prefix: prefixes.join(" or ") }
1131
+ messageId: expected.length > 0 ? "pattern" : "prefix",
1132
+ data: expected.length > 0 ? { name: item.name, pattern: optionsPatternLabel([options.pattern ?? []].flat().filter((source) => source !== "")) } : { name: item.name, prefix: prefixes.join(" or ") }
1096
1133
  });
1097
1134
  }
1098
1135
  return {
@@ -1102,8 +1139,8 @@ var declarationName = defineRule3({
1102
1139
  normalize: "remove-separators"
1103
1140
  });
1104
1141
  kinds = options.kinds && options.kinds.length > 0 ? new Set(options.kinds) : null;
1105
- allowed = options.allowPattern ? new RegExp(options.allowPattern) : null;
1106
- expected = options.pattern ? new RegExp(options.pattern, options.flags) : null;
1142
+ allowed = optionsOptionalPatterns(options.allowPattern);
1143
+ expected = optionsOptionalPatterns(options.pattern, options.flags);
1107
1144
  const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
1108
1145
  const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
1109
1146
  prefixes = namingFilePrefixes(stem, options.normalize ?? "remove-separators", options.singularize ?? "none");
@@ -1133,7 +1170,7 @@ var exportFilePrefix = defineRule4({
1133
1170
  normalize: { type: "string", enum: ["remove-separators", "none"] },
1134
1171
  singularize: { type: "string", enum: ["none", "trailing-s"] },
1135
1172
  allDeclarations: { type: "boolean" },
1136
- allowPattern: { type: "string" }
1173
+ allowPattern: optionsPatternSchema
1137
1174
  }
1138
1175
  }],
1139
1176
  messages: {
@@ -1150,7 +1187,7 @@ var exportFilePrefix = defineRule4({
1150
1187
  const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
1151
1188
  const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
1152
1189
  const prefixes = namingFilePrefixes(stem, options.normalize ?? "remove-separators", options.singularize ?? "none");
1153
- const allowed = options.allowPattern ? new RegExp(options.allowPattern) : null;
1190
+ const allowed = optionsOptionalPatterns(options.allowPattern);
1154
1191
  const names = options.allDeclarations ? declarationsCollectNamed(program) : exportsCollect(program).map((binding) => ({
1155
1192
  name: binding.localName ?? binding.exportedName,
1156
1193
  node: binding.node
@@ -1159,7 +1196,7 @@ var exportFilePrefix = defineRule4({
1159
1196
  for (const item of names) {
1160
1197
  const key = `${item.name}:${item.node.start}:${item.node.end}`;
1161
1198
  const comparableName = options.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
1162
- if (seen.has(key) || allowed?.test(item.name) || prefixes.some((prefix) => comparableName.startsWith(prefix))) {
1199
+ if (seen.has(key) || optionsPatternsTest(allowed, item.name) || prefixes.some((prefix) => comparableName.startsWith(prefix))) {
1163
1200
  continue;
1164
1201
  }
1165
1202
  seen.add(key);
@@ -1183,11 +1220,11 @@ var exportNamePattern = defineRule5({
1183
1220
  type: "object",
1184
1221
  additionalProperties: false,
1185
1222
  properties: {
1186
- pattern: { type: "string" },
1223
+ pattern: optionsPatternSchema,
1187
1224
  flags: { type: "string" },
1188
1225
  ignoreTypeExports: { type: "boolean" },
1189
1226
  allDeclarations: { type: "boolean" },
1190
- allowPattern: { type: "string" }
1227
+ allowPattern: optionsPatternSchema
1191
1228
  },
1192
1229
  required: ["pattern"]
1193
1230
  }],
@@ -1205,19 +1242,20 @@ var exportNamePattern = defineRule5({
1205
1242
  allDeclarations = false,
1206
1243
  allowPattern
1207
1244
  } = optionsFirst(context);
1208
- const allowed = allowPattern ? new RegExp(allowPattern) : null;
1245
+ const allowed = optionsOptionalPatterns(allowPattern);
1246
+ const expected = optionsPatterns(pattern, flags);
1209
1247
  const names = allDeclarations ? declarationsCollectNamed(program) : exportsCollect(program).flatMap((binding) => ignoreTypeExports && binding.typeOnly ? [] : [{ name: binding.localName ?? binding.exportedName, node: binding.node }]);
1210
1248
  const seen = new Set;
1211
1249
  for (const item of names) {
1212
1250
  const key = `${item.name}:${item.node.start}:${item.node.end}`;
1213
- if (seen.has(key) || allowed?.test(item.name) || new RegExp(pattern, flags).test(item.name)) {
1251
+ if (seen.has(key) || optionsPatternsTest(allowed, item.name) || optionsPatternsTest(expected, item.name)) {
1214
1252
  continue;
1215
1253
  }
1216
1254
  seen.add(key);
1217
1255
  context.report({
1218
1256
  node: item.node,
1219
1257
  messageId: "pattern",
1220
- data: { name: item.name, pattern }
1258
+ data: { name: item.name, pattern: optionsPatternLabel(pattern) }
1221
1259
  });
1222
1260
  }
1223
1261
  }
@@ -1296,7 +1334,7 @@ var filenameMatch = defineRule7({
1296
1334
  type: "object",
1297
1335
  additionalProperties: false,
1298
1336
  properties: {
1299
- pattern: { type: "string" },
1337
+ pattern: optionsPatternSchema,
1300
1338
  message: { type: "string" },
1301
1339
  flags: { type: "string" }
1302
1340
  },
@@ -1310,7 +1348,8 @@ var filenameMatch = defineRule7({
1310
1348
  return {
1311
1349
  Program(program) {
1312
1350
  const { pattern, message, flags } = optionsFirst(context);
1313
- if (!new RegExp(pattern, flags).test(namingFileBasename(context.filename))) {
1351
+ const patterns = optionsPatterns(pattern, flags);
1352
+ if (!optionsPatternsTest(patterns, namingFileBasename(context.filename))) {
1314
1353
  context.report({
1315
1354
  node: program,
1316
1355
  messageId: "mismatch",
@@ -1782,7 +1821,7 @@ import { defineRule as defineRule13 } from "@oxlint/plugins";
1782
1821
  function compileFactoryKeys(pattern, keys, requireKeys) {
1783
1822
  const allowedList = keys.concat(requireKeys.filter((key) => !keys.includes(key)));
1784
1823
  return {
1785
- pattern: new RegExp(pattern),
1824
+ patterns: optionsPatterns(pattern),
1786
1825
  allowed: new Set(allowedList),
1787
1826
  allowedList,
1788
1827
  requireKeys
@@ -1796,7 +1835,7 @@ var noExtraFactoryKeys = defineRule13({
1796
1835
  additionalProperties: false,
1797
1836
  properties: {
1798
1837
  keys: { type: "array", items: { type: "string" } },
1799
- factoryPattern: { type: "string" },
1838
+ factoryPattern: optionsPatternSchema,
1800
1839
  requireKeys: { type: "array", items: { type: "string" } },
1801
1840
  keySets: {
1802
1841
  type: "array",
@@ -1804,7 +1843,7 @@ var noExtraFactoryKeys = defineRule13({
1804
1843
  type: "object",
1805
1844
  additionalProperties: false,
1806
1845
  properties: {
1807
- factoryPattern: { type: "string" },
1846
+ factoryPattern: optionsPatternSchema,
1808
1847
  keys: { type: "array", items: { type: "string" } },
1809
1848
  requireKeys: { type: "array", items: { type: "string" } }
1810
1849
  },
@@ -1823,11 +1862,11 @@ var noExtraFactoryKeys = defineRule13({
1823
1862
  let keySets = [];
1824
1863
  let fallback;
1825
1864
  function resolveFactoryKeys(name) {
1826
- const matched = keySets.find((item) => item.pattern.test(name));
1865
+ const matched = keySets.find((item) => optionsPatternsTest(item.patterns, name));
1827
1866
  if (matched) {
1828
1867
  return matched;
1829
1868
  }
1830
- if (fallback?.pattern.test(name)) {
1869
+ if (fallback && optionsPatternsTest(fallback.patterns, name)) {
1831
1870
  return fallback;
1832
1871
  }
1833
1872
  }
@@ -1894,9 +1933,9 @@ var noFileLevelHelpers = defineRule14({
1894
1933
  type: "object",
1895
1934
  additionalProperties: false,
1896
1935
  properties: {
1897
- allowPattern: { type: "string" },
1936
+ allowPattern: optionsPatternSchema,
1898
1937
  detectComponents: { type: "boolean" },
1899
- hookPattern: { type: "string" }
1938
+ hookPattern: optionsPatternSchema
1900
1939
  }
1901
1940
  }],
1902
1941
  messages: {
@@ -1911,10 +1950,10 @@ var noFileLevelHelpers = defineRule14({
1911
1950
  detectComponents = true,
1912
1951
  hookPattern = "^use[A-Z]"
1913
1952
  } = optionsFirst(context, {});
1914
- const allowed = allowPattern ? new RegExp(allowPattern) : null;
1915
- const hooks = new RegExp(hookPattern);
1953
+ const allowed = optionsOptionalPatterns(allowPattern);
1954
+ const hooks = optionsPatterns(hookPattern);
1916
1955
  for (const candidate of declarationsFileLevelFunctionCandidates(program)) {
1917
- if (detectComponents && reactComponentsIsLike(program, candidate) || hooks.test(candidate.name) || allowed?.test(candidate.name)) {
1956
+ if (detectComponents && reactComponentsIsLike(program, candidate) || optionsPatternsTest(hooks, candidate.name) || optionsPatternsTest(allowed, candidate.name)) {
1918
1957
  continue;
1919
1958
  }
1920
1959
  context.report({
@@ -2427,7 +2466,7 @@ var noModuleMutableState = defineRule21({
2427
2466
  type: "array",
2428
2467
  items: { type: "string", enum: ["let", "var"] }
2429
2468
  },
2430
- allowNamePattern: { type: "string" }
2469
+ allowNamePattern: optionsPatternSchema
2431
2470
  }
2432
2471
  }],
2433
2472
  messages: {
@@ -2436,12 +2475,12 @@ var noModuleMutableState = defineRule21({
2436
2475
  },
2437
2476
  createOnce(context) {
2438
2477
  let kindSet = new Set(["let", "var"]);
2439
- let allowed = null;
2478
+ let allowed = [];
2440
2479
  return {
2441
2480
  before() {
2442
2481
  const { kinds = ["let", "var"], allowNamePattern } = optionsFirst(context, {});
2443
2482
  kindSet = new Set(kinds);
2444
- allowed = allowNamePattern ? new RegExp(allowNamePattern) : null;
2483
+ allowed = optionsOptionalPatterns(allowNamePattern);
2445
2484
  },
2446
2485
  VariableDeclaration(node) {
2447
2486
  if (node.declare || !kindSet.has(node.kind)) {
@@ -2452,7 +2491,7 @@ var noModuleMutableState = defineRule21({
2452
2491
  }
2453
2492
  for (const declarator of node.declarations) {
2454
2493
  for (const binding of moduleMutableStateBindings(declarator.id)) {
2455
- if (!allowed?.test(binding.name)) {
2494
+ if (!optionsPatternsTest(allowed, binding.name)) {
2456
2495
  context.report({
2457
2496
  node: binding.node,
2458
2497
  messageId: "moduleState"
@@ -3333,7 +3372,7 @@ var noTopLevelFunctions = defineRule29({
3333
3372
  additionalProperties: false,
3334
3373
  properties: {
3335
3374
  banReExports: { type: "boolean" },
3336
- allowPattern: { type: "string" }
3375
+ allowPattern: optionsPatternSchema
3337
3376
  }
3338
3377
  }],
3339
3378
  defaultOptions: [{ banReExports: true }],
@@ -3346,7 +3385,7 @@ var noTopLevelFunctions = defineRule29({
3346
3385
  return {
3347
3386
  Program(program) {
3348
3387
  const { banReExports, allowPattern } = optionsFirst(context);
3349
- const allowed = allowPattern ? new RegExp(allowPattern) : null;
3388
+ const allowed = optionsOptionalPatterns(allowPattern);
3350
3389
  for (const statement of program.body) {
3351
3390
  if (banReExports && (statement.type === "ExportAllDeclaration" || statement.type === "ExportNamedDeclaration" && statement.source)) {
3352
3391
  context.report({ node: statement, messageId: "noReExport" });
@@ -3354,7 +3393,7 @@ var noTopLevelFunctions = defineRule29({
3354
3393
  }
3355
3394
  const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
3356
3395
  if (declaration?.type === "FunctionDeclaration") {
3357
- if (!allowed?.test(declarationsFunctionName(declaration))) {
3396
+ if (!optionsPatternsTest(allowed, declarationsFunctionName(declaration))) {
3358
3397
  context.report({
3359
3398
  node: declaration,
3360
3399
  messageId: "noFunction",
@@ -3367,7 +3406,7 @@ var noTopLevelFunctions = defineRule29({
3367
3406
  continue;
3368
3407
  }
3369
3408
  for (const item of declaration.declarations) {
3370
- if ((item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") && !allowed?.test(declarationsFunctionName(item))) {
3409
+ if ((item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") && !optionsPatternsTest(allowed, declarationsFunctionName(item))) {
3371
3410
  context.report({
3372
3411
  node: item,
3373
3412
  messageId: "noFunction",
@@ -3416,7 +3455,7 @@ var noTrivialFunctions = defineRule30({
3416
3455
  type: "object",
3417
3456
  additionalProperties: false,
3418
3457
  properties: {
3419
- allowPattern: { type: "string" },
3458
+ allowPattern: optionsPatternSchema,
3420
3459
  allowCallees: { type: "array", items: { type: "string" } },
3421
3460
  allowAsync: { type: "boolean" }
3422
3461
  }
@@ -3426,13 +3465,13 @@ var noTrivialFunctions = defineRule30({
3426
3465
  }
3427
3466
  },
3428
3467
  createOnce(context) {
3429
- let allowed = null;
3468
+ let allowed = [];
3430
3469
  let allowedCallees = [];
3431
3470
  let allowAsync = false;
3432
3471
  return {
3433
3472
  before() {
3434
3473
  const { allowPattern, allowCallees: calleePatterns = [], allowAsync: asyncAllowed = false } = optionsFirst(context, {});
3435
- allowed = allowPattern ? new RegExp(allowPattern) : null;
3474
+ allowed = optionsOptionalPatterns(allowPattern);
3436
3475
  allowedCallees = calleePatterns.map((pattern) => new RegExp(pattern));
3437
3476
  allowAsync = asyncAllowed;
3438
3477
  },
@@ -3440,7 +3479,7 @@ var noTrivialFunctions = defineRule30({
3440
3479
  const seen = new Set;
3441
3480
  for (const item of [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)]) {
3442
3481
  const key = `${item.name}:${item.node.start}:${item.node.end}`;
3443
- if (seen.has(key) || allowed?.test(item.name) || !declarationsIsTrivialFunction(item.node)) {
3482
+ if (seen.has(key) || optionsPatternsTest(allowed, item.name) || !declarationsIsTrivialFunction(item.node)) {
3444
3483
  continue;
3445
3484
  }
3446
3485
  const expression = forwardedExpression(item.node);
@@ -3470,7 +3509,7 @@ var noTypeDeclarations = defineRule31({
3470
3509
  type: "object",
3471
3510
  additionalProperties: false,
3472
3511
  properties: {
3473
- allowPattern: { type: "string" }
3512
+ allowPattern: optionsPatternSchema
3474
3513
  }
3475
3514
  }],
3476
3515
  messages: {
@@ -3478,11 +3517,13 @@ var noTypeDeclarations = defineRule31({
3478
3517
  }
3479
3518
  },
3480
3519
  createOnce(context) {
3481
- let allowed = null;
3520
+ let allowedNames = [];
3521
+ let allowedValues = [];
3482
3522
  function check(node) {
3483
- if (allowed?.test(node.id.name)) {
3523
+ if (optionsPatternsTest(allowedNames, node.id.name))
3524
+ return;
3525
+ if (node.type === "TSTypeAliasDeclaration" && allowedValues.length > 0 && optionsPatternsTest(allowedValues, `= ${context.sourceCode.getText(node.typeAnnotation)}`))
3484
3526
  return;
3485
- }
3486
3527
  context.report({
3487
3528
  node,
3488
3529
  messageId: "typeDeclaration",
@@ -3492,7 +3533,10 @@ var noTypeDeclarations = defineRule31({
3492
3533
  return {
3493
3534
  before() {
3494
3535
  const { allowPattern } = optionsFirst(context, {});
3495
- allowed = allowPattern ? new RegExp(allowPattern) : null;
3536
+ const sources = allowPattern === undefined ? [] : [allowPattern].flat();
3537
+ const isValuePattern = (source) => /^\^?=/.test(source);
3538
+ allowedNames = optionsOptionalPatterns(sources.filter((source) => !isValuePattern(source)));
3539
+ allowedValues = optionsOptionalPatterns(sources.filter(isValuePattern));
3496
3540
  },
3497
3541
  TSTypeAliasDeclaration: check,
3498
3542
  TSInterfaceDeclaration: check
@@ -3655,7 +3699,7 @@ var onlyExportComponents = defineRule34({
3655
3699
  properties: {
3656
3700
  matchFileName: { type: "boolean" },
3657
3701
  allowTypeExports: { type: "boolean" },
3658
- denyTypePattern: { type: "string" }
3702
+ denyTypePattern: optionsPatternSchema
3659
3703
  }
3660
3704
  }],
3661
3705
  messages: {
@@ -3674,7 +3718,7 @@ var onlyExportComponents = defineRule34({
3674
3718
  denyTypePattern
3675
3719
  } = optionsFirst(context, {});
3676
3720
  const expected = reactComponentsPascalFromBasename(namingFileBasename(context.filename));
3677
- const denied = denyTypePattern ? new RegExp(denyTypePattern) : null;
3721
+ const denied = optionsOptionalPatterns(denyTypePattern);
3678
3722
  const components = [];
3679
3723
  for (const candidate of reactComponentsExportCandidates(program)) {
3680
3724
  if (candidate.typeOnly) {
@@ -3684,7 +3728,7 @@ var onlyExportComponents = defineRule34({
3684
3728
  messageId: "typeExport",
3685
3729
  data: { name: candidate.name, expected }
3686
3730
  });
3687
- } else if (denied?.test(candidate.name)) {
3731
+ } else if (optionsPatternsTest(denied, candidate.name)) {
3688
3732
  context.report({
3689
3733
  node: candidate.node,
3690
3734
  messageId: "deniedType",
@@ -4037,7 +4081,7 @@ var requireObjectParams = defineRule38({
4037
4081
  properties: {
4038
4082
  maxParams: { type: "integer", minimum: 1 },
4039
4083
  allDeclarations: { type: "boolean" },
4040
- allowPattern: { type: "string" }
4084
+ allowPattern: optionsPatternSchema
4041
4085
  }
4042
4086
  }],
4043
4087
  messages: {
@@ -4048,12 +4092,12 @@ var requireObjectParams = defineRule38({
4048
4092
  return {
4049
4093
  Program(program) {
4050
4094
  const { maxParams = 1, allDeclarations = false, allowPattern } = optionsFirst(context, {});
4051
- const allowed = allowPattern ? new RegExp(allowPattern) : null;
4095
+ const allowed = optionsOptionalPatterns(allowPattern);
4052
4096
  const seen = new Set;
4053
4097
  const functions = allDeclarations ? [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)] : exportsCollectFunctions(program);
4054
4098
  for (const item of functions) {
4055
4099
  const key = `${item.name}:${item.node.start}:${item.node.end}`;
4056
- if (seen.has(key) || allowed?.test(item.name)) {
4100
+ if (seen.has(key) || optionsPatternsTest(allowed, item.name)) {
4057
4101
  continue;
4058
4102
  }
4059
4103
  seen.add(key);
@@ -4275,6 +4319,9 @@ function regexMatches(regex, value) {
4275
4319
  regex.lastIndex = 0;
4276
4320
  return regex.test(value);
4277
4321
  }
4322
+ function titlePatterns(pattern, flags) {
4323
+ return pattern === undefined ? [] : [pattern].flat().map((source) => ({ regex: new RegExp(source, flags), pattern: source }));
4324
+ }
4278
4325
  var testTitlePattern = defineRule42({
4279
4326
  meta: {
4280
4327
  type: "suggestion",
@@ -4283,8 +4330,8 @@ var testTitlePattern = defineRule42({
4283
4330
  additionalProperties: false,
4284
4331
  properties: {
4285
4332
  callees: { type: "array", items: { type: "string" } },
4286
- forbid: { type: "string" },
4287
- require: { type: "string" },
4333
+ forbid: optionsPatternSchema,
4334
+ require: optionsPatternSchema,
4288
4335
  flags: { type: "string", pattern: "^[dgimsuvy]*$" }
4289
4336
  }
4290
4337
  }],
@@ -4299,16 +4346,18 @@ var testTitlePattern = defineRule42({
4299
4346
  },
4300
4347
  createOnce(context) {
4301
4348
  let callees = [];
4302
- let forbid;
4303
- let required;
4349
+ let forbid = [];
4350
+ let required = [];
4351
+ let requiredLabel = "";
4304
4352
  return {
4305
4353
  before() {
4306
4354
  const options = optionsFirst(context);
4307
4355
  callees = options.callees;
4308
4356
  const flags = options.flags ?? "";
4309
- forbid = options.forbid === undefined ? undefined : { regex: new RegExp(options.forbid, flags), pattern: options.forbid };
4310
- required = options.require === undefined ? undefined : { regex: new RegExp(options.require, flags), pattern: options.require };
4311
- if (forbid === undefined && required === undefined) {
4357
+ forbid = titlePatterns(options.forbid, flags);
4358
+ required = titlePatterns(options.require, flags);
4359
+ requiredLabel = optionsPatternLabel(options.require);
4360
+ if (forbid.length === 0 && required.length === 0) {
4312
4361
  return false;
4313
4362
  }
4314
4363
  },
@@ -4324,18 +4373,19 @@ var testTitlePattern = defineRule42({
4324
4373
  if (text === undefined) {
4325
4374
  return;
4326
4375
  }
4327
- if (forbid && regexMatches(forbid.regex, text)) {
4376
+ const forbidden = forbid.find((item) => regexMatches(item.regex, text));
4377
+ if (forbidden) {
4328
4378
  context.report({
4329
4379
  node: title,
4330
4380
  messageId: "forbidden",
4331
- data: { pattern: forbid.pattern }
4381
+ data: { pattern: forbidden.pattern }
4332
4382
  });
4333
4383
  }
4334
- if (required && !regexMatches(required.regex, text)) {
4384
+ if (required.length > 0 && !required.some((item) => regexMatches(item.regex, text))) {
4335
4385
  context.report({
4336
4386
  node: title,
4337
4387
  messageId: "required",
4338
- data: { pattern: required.pattern }
4388
+ data: { pattern: requiredLabel }
4339
4389
  });
4340
4390
  }
4341
4391
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-arch",
3
- "version": "0.3.1",
3
+ "version": "0.3.4",
4
4
  "description": "Configurable architecture rules for Oxlint JavaScript plugins.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",