@sarj/eslint-plugin 7.1.0 → 9.0.0

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/dist/index.js CHANGED
@@ -12,25 +12,41 @@ var createRule = ESLintUtils.RuleCreator(evidenceUrl);
12
12
  // src/rules/_paths.ts
13
13
  var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
14
14
  var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
15
- var STORY_DIR_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
16
- var GENERATED_FILE_RE = /([\\/](?:generated|openapi-gen|graphql[\\/]types|vendor|vendored|external|third[-_]?party)[\\/])|(\.gen\.[cm]?[jt]sx?$)|(\.generated\.[cm]?[jt]sx?$)|(\.d\.[cm]?ts$)|(\.types\.[cm]?ts$)/;
17
- var GENERATED_MARKER_RE = /(?:@generated\b|generated (?:with|by)|generated (?:graphql )?types|do not edit(?: directly| manually)?)/i;
18
- function isTestFile(filename) {
15
+ var STORY_TREE_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
16
+ var GENERATED_FILE_RE = /([\\/](?:generated|openapi-gen|graphql[\\/]types|vendor|vendored|third[-_]?party)[\\/])|(\.gen\.[cm]?[jt]sx?$)|(\.generated\.[cm]?[jt]sx?$)|(\.d\.[cm]?ts$)|(\.types\.[cm]?ts$)/;
17
+ var EXTERNAL_TREE_RE = /[\\/]external[\\/]/;
18
+ var GENERATED_MARKER_RE = /(?:@generated\b|this file (?:is|was|has been)[\w\s,'-]{0,40}?generated|auto-?generated file\b|generated (?:with|by)|generated (?:graphql )?types|do not edit(?: directly| manually)?|do not (?:modify|change) this file)/i;
19
+ var TEST_BASENAME_RE = /[.\-_](test|spec|e2e)\.[cm]?[jt]sx?$/;
20
+ var TEST_INTEGRATION_BASENAME_RE = /\.integration\.[cm]?[jt]sx?$/;
21
+ var TEST_DIR_RE = /(^|\/)(tests?|__tests__|__mocks__|fixtures|__fixtures__|__testfixtures__|e2e|integration)\//;
22
+ var FIXTURE_TREE_RE = /(^|\/)fixture\//;
23
+ function isTestFile(filename, gates = []) {
19
24
  const normalized = filename.replaceAll("\\", "/");
20
25
  const base = normalized.slice(normalized.lastIndexOf("/") + 1);
21
- if (/[.\-_](test|spec|e2e)\.[cm]?[jt]sx?$/.test(base) || /\.integration\.[cm]?[jt]sx?$/.test(base)) {
26
+ if (TEST_BASENAME_RE.test(base) || TEST_INTEGRATION_BASENAME_RE.test(base)) {
22
27
  return true;
23
28
  }
24
- return /(^|\/)(tests?|__tests__|__mocks__|__fixtures__|__testfixtures__|fixtures?|e2e|integration)\//.test(
25
- normalized
26
- );
29
+ if (TEST_DIR_RE.test(normalized)) {
30
+ return true;
31
+ }
32
+ return gates.includes("fixtureTree") && FIXTURE_TREE_RE.test(normalized);
27
33
  }
28
- function isStoryFile(filename) {
34
+ function isStoryFile(filename, gates = []) {
29
35
  const normalized = filename.replaceAll("\\", "/");
30
- return STORY_FILE_RE.test(normalized) || STORY_DIR_RE.test(normalized);
36
+ if (STORY_FILE_RE.test(normalized)) {
37
+ return true;
38
+ }
39
+ return gates.includes("storyTree") && STORY_TREE_RE.test(normalized);
31
40
  }
32
- function isGeneratedFile(filename, sourceText = "") {
33
- return GENERATED_FILE_RE.test(filename.replaceAll("\\", "/")) || GENERATED_MARKER_RE.test(sourceText.slice(0, 2048));
41
+ function isGeneratedFile(filename, sourceText = "", gates = []) {
42
+ const normalized = filename.replaceAll("\\", "/");
43
+ if (GENERATED_FILE_RE.test(normalized)) {
44
+ return true;
45
+ }
46
+ if (gates.includes("externalTree") && EXTERNAL_TREE_RE.test(normalized)) {
47
+ return true;
48
+ }
49
+ return GENERATED_MARKER_RE.test(sourceText.slice(0, 2048));
34
50
  }
35
51
  function isScriptFile(filename) {
36
52
  return SCRIPT_FILE_RE.test(filename);
@@ -115,6 +131,10 @@ var enforce_file_structure_default = createRule({
115
131
 
116
132
  // src/rules/no-async-callback-in-wait-for.ts
117
133
  import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
134
+ var isWaitForCallee = (callee) => {
135
+ if (callee.type === AST_NODE_TYPES2.Identifier) return callee.name === "waitFor";
136
+ return callee.type === AST_NODE_TYPES2.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES2.Identifier && callee.property.name === "waitFor";
137
+ };
118
138
  var no_async_callback_in_wait_for_default = createRule({
119
139
  name: "no-async-callback-in-wait-for",
120
140
  meta: {
@@ -134,14 +154,13 @@ var no_async_callback_in_wait_for_default = createRule({
134
154
  }
135
155
  return {
136
156
  CallExpression(node) {
137
- if (node.callee.type === AST_NODE_TYPES2.Identifier && node.callee.name === "waitFor") {
138
- const callback = node.arguments[0];
139
- if (callback && (callback.type === AST_NODE_TYPES2.ArrowFunctionExpression || callback.type === AST_NODE_TYPES2.FunctionExpression) && callback.async) {
140
- context.report({
141
- node: callback,
142
- messageId: "noAsyncCallbackInWaitFor"
143
- });
144
- }
157
+ if (!isWaitForCallee(node.callee)) return;
158
+ const callback = node.arguments[0];
159
+ if (callback && (callback.type === AST_NODE_TYPES2.ArrowFunctionExpression || callback.type === AST_NODE_TYPES2.FunctionExpression) && callback.async) {
160
+ context.report({
161
+ node: callback,
162
+ messageId: "noAsyncCallbackInWaitFor"
163
+ });
145
164
  }
146
165
  }
147
166
  };
@@ -769,6 +788,12 @@ var no_comment_cruft_default = createRule({
769
788
  // src/rules/no-conditional-in-test.ts
770
789
  import { AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/utils";
771
790
  var TEST_CALLERS = /* @__PURE__ */ new Set(["it", "test"]);
791
+ var HOOK_MEMBERS = /* @__PURE__ */ new Set([
792
+ "afterAll",
793
+ "afterEach",
794
+ "beforeAll",
795
+ "beforeEach"
796
+ ]);
772
797
  var FUNCTION_TYPES = /* @__PURE__ */ new Set([
773
798
  AST_NODE_TYPES6.FunctionDeclaration,
774
799
  AST_NODE_TYPES6.FunctionExpression,
@@ -812,6 +837,9 @@ function isTestBody(fn) {
812
837
  if (call?.type !== AST_NODE_TYPES6.CallExpression || !call.arguments.some((argument) => argument === fn)) {
813
838
  return false;
814
839
  }
840
+ if (call.callee.type === AST_NODE_TYPES6.MemberExpression && !call.callee.computed && call.callee.property.type === AST_NODE_TYPES6.Identifier && HOOK_MEMBERS.has(call.callee.property.name)) {
841
+ return false;
842
+ }
815
843
  const name = testCallerName(call.callee);
816
844
  return name !== null && TEST_CALLERS.has(name);
817
845
  }
@@ -966,8 +994,26 @@ function isTypeLevelNarrowing(node) {
966
994
  return isTypeAssertionBranch(node.consequent) && (node.alternate === null || isTypeAssertionBranch(node.alternate));
967
995
  }
968
996
  var isInertBranch = (branch) => !containsAssertion(branch) && !containsEscape(branch) && !containsSkipCall(branch);
997
+ function isNormalizationStatement(statement) {
998
+ if (statement.type === AST_NODE_TYPES6.VariableDeclaration) {
999
+ return true;
1000
+ }
1001
+ if (statement.type === AST_NODE_TYPES6.BlockStatement) {
1002
+ return statement.body.every(isNormalizationStatement);
1003
+ }
1004
+ if (statement.type !== AST_NODE_TYPES6.ExpressionStatement) {
1005
+ return false;
1006
+ }
1007
+ const { expression } = statement;
1008
+ return expression.type === AST_NODE_TYPES6.AssignmentExpression || expression.type === AST_NODE_TYPES6.UpdateExpression || expression.type === AST_NODE_TYPES6.UnaryExpression && expression.operator === "delete";
1009
+ }
969
1010
  function isInertNormalization(node) {
970
- return isInertBranch(node.consequent) && (node.alternate === null || isInertBranch(node.alternate));
1011
+ const branches = [node.consequent, node.alternate].filter(
1012
+ (branch) => branch !== null
1013
+ );
1014
+ return branches.every(
1015
+ (branch) => isInertBranch(branch) && isNormalizationStatement(branch)
1016
+ );
971
1017
  }
972
1018
  function isExemptIfStatement(node) {
973
1019
  return isPinnedNarrowingGuard(node) || isThrowingGuard(node) || isTypeLevelNarrowing(node) || isInertNormalization(node);
@@ -1575,7 +1621,6 @@ var PURE_METHODS = /* @__PURE__ */ new Set([
1575
1621
  "forEach",
1576
1622
  "reduce",
1577
1623
  "reduceRight",
1578
- "find",
1579
1624
  "findIndex",
1580
1625
  "findLast",
1581
1626
  "findLastIndex",
@@ -1598,15 +1643,6 @@ var PURE_METHODS = /* @__PURE__ */ new Set([
1598
1643
  "indexOf",
1599
1644
  "lastIndexOf",
1600
1645
  "at",
1601
- "keys",
1602
- "values",
1603
- "entries",
1604
- "has",
1605
- "get",
1606
- "set",
1607
- "add",
1608
- "delete",
1609
- "clear",
1610
1646
  "toString",
1611
1647
  "toLocaleString",
1612
1648
  "valueOf",
@@ -1637,6 +1673,7 @@ var PURE_NAMESPACES = /* @__PURE__ */ new Set([
1637
1673
  "Boolean",
1638
1674
  "console"
1639
1675
  ]);
1676
+ var IMPURE_NAMESPACE_METHODS = /* @__PURE__ */ new Set(["JSON.parse"]);
1640
1677
  var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
1641
1678
  "Map",
1642
1679
  "Set",
@@ -1673,7 +1710,7 @@ function isPureCall(node) {
1673
1710
  return false;
1674
1711
  }
1675
1712
  if (callee.object.type === AST_NODE_TYPES9.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
1676
- return true;
1713
+ return !IMPURE_NAMESPACE_METHODS.has(`${callee.object.name}.${property.name}`);
1677
1714
  }
1678
1715
  return PURE_METHODS.has(property.name);
1679
1716
  }
@@ -5247,7 +5284,7 @@ var no_type_member_comment_wall_default = createRule({
5247
5284
  create(context, [provided]) {
5248
5285
  const options = { ...WALL_DEFAULTS, ...provided };
5249
5286
  const sourceCode = context.sourceCode;
5250
- if (isGeneratedFile(context.filename, sourceCode.text) || isTestFile(context.filename) || isStoryFile(context.filename)) {
5287
+ if (isGeneratedFile(context.filename, sourceCode.text, ["externalTree"]) || isTestFile(context.filename, ["fixtureTree"]) || isStoryFile(context.filename, ["storyTree"])) {
5251
5288
  return {};
5252
5289
  }
5253
5290
  const endingOn = /* @__PURE__ */ new Map();
@@ -6230,7 +6267,7 @@ var DEFAULT_FACTORIES = [
6230
6267
  "tuple",
6231
6268
  "union"
6232
6269
  ];
6233
- var DEFAULT_MIN_PROPERTIES = 1;
6270
+ var DEFAULT_MIN_PROPERTIES = 2;
6234
6271
  var MEMO_CALLEES = /* @__PURE__ */ new Set([
6235
6272
  "lazy",
6236
6273
  "memo",
@@ -6246,6 +6283,34 @@ var TERMINAL_METHODS = /* @__PURE__ */ new Set([
6246
6283
  "safeParseAsync",
6247
6284
  "spa"
6248
6285
  ]);
6286
+ var ZOD_COMBINATOR_METHODS = /* @__PURE__ */ new Set([
6287
+ "and",
6288
+ "array",
6289
+ "catch",
6290
+ "catchall",
6291
+ "default",
6292
+ "extend",
6293
+ "merge",
6294
+ "or",
6295
+ "pipe",
6296
+ "refine",
6297
+ "superRefine",
6298
+ "transform"
6299
+ ]);
6300
+ var I18N_CALLEE_NAMES = /* @__PURE__ */ new Set([
6301
+ "$t",
6302
+ "defineMessage",
6303
+ "gettext",
6304
+ "msg",
6305
+ "ngettext",
6306
+ "t",
6307
+ "translate"
6308
+ ]);
6309
+ var I18N_RECEIVER_NAMES = /* @__PURE__ */ new Set([
6310
+ "$i18n",
6311
+ "i18n",
6312
+ "intl"
6313
+ ]);
6249
6314
  var FUNCTION_TYPES6 = /* @__PURE__ */ new Set([
6250
6315
  AST_NODE_TYPES31.ArrowFunctionExpression,
6251
6316
  AST_NODE_TYPES31.FunctionDeclaration,
@@ -6279,7 +6344,7 @@ function outermostEnclosingFunction(node) {
6279
6344
  }
6280
6345
  return outermost;
6281
6346
  }
6282
- function readsReceiver(node) {
6347
+ function subtreeSome(root, predicate) {
6283
6348
  let found = false;
6284
6349
  const visit = (value) => {
6285
6350
  if (found || value === null || typeof value !== "object") {
@@ -6295,7 +6360,7 @@ function readsReceiver(node) {
6295
6360
  if (typeof candidate.type !== "string") {
6296
6361
  return;
6297
6362
  }
6298
- if (candidate.type === AST_NODE_TYPES31.ThisExpression || candidate.type === AST_NODE_TYPES31.Super || candidate.type === AST_NODE_TYPES31.Identifier && candidate.name === "arguments") {
6363
+ if (predicate(candidate)) {
6299
6364
  found = true;
6300
6365
  return;
6301
6366
  }
@@ -6306,9 +6371,30 @@ function readsReceiver(node) {
6306
6371
  visit(candidate[key]);
6307
6372
  }
6308
6373
  };
6309
- visit(node);
6374
+ visit(root);
6310
6375
  return found;
6311
6376
  }
6377
+ function readsReceiver(node) {
6378
+ return subtreeSome(
6379
+ node,
6380
+ (inner) => inner.type === AST_NODE_TYPES31.ThisExpression || inner.type === AST_NODE_TYPES31.Super || inner.type === AST_NODE_TYPES31.Identifier && inner.name === "arguments"
6381
+ );
6382
+ }
6383
+ function buildsLocalizedText(node) {
6384
+ return subtreeSome(node, (inner) => {
6385
+ if (inner.type === AST_NODE_TYPES31.TaggedTemplateExpression) {
6386
+ return true;
6387
+ }
6388
+ if (inner.type !== AST_NODE_TYPES31.CallExpression) {
6389
+ return false;
6390
+ }
6391
+ const { callee } = inner;
6392
+ if (callee.type === AST_NODE_TYPES31.Identifier) {
6393
+ return I18N_CALLEE_NAMES.has(callee.name);
6394
+ }
6395
+ return callee.type === AST_NODE_TYPES31.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES31.Identifier && I18N_RECEIVER_NAMES.has(callee.object.name);
6396
+ });
6397
+ }
6312
6398
  function collectReferences(scope, out) {
6313
6399
  out.push(...scope.references);
6314
6400
  for (const child of scope.childScopes) {
@@ -6378,6 +6464,31 @@ var prefer_module_level_schema_default = createRule({
6378
6464
  }
6379
6465
  return false;
6380
6466
  }
6467
+ function outermostSchemaExpression(expression) {
6468
+ let confirmed = expression;
6469
+ let current = expression;
6470
+ for (; ; ) {
6471
+ const parent = current.parent ?? void 0;
6472
+ if (parent === void 0) {
6473
+ return confirmed;
6474
+ }
6475
+ if (parent.type === AST_NODE_TYPES31.Property && parent.value === current || parent.type === AST_NODE_TYPES31.ObjectExpression || parent.type === AST_NODE_TYPES31.ArrayExpression) {
6476
+ current = parent;
6477
+ continue;
6478
+ }
6479
+ if (parent.type === AST_NODE_TYPES31.CallExpression && parent.arguments.includes(current) && isSchemaComposition(parent)) {
6480
+ current = schemaExpression(parent);
6481
+ confirmed = current;
6482
+ continue;
6483
+ }
6484
+ return confirmed;
6485
+ }
6486
+ }
6487
+ function isSchemaComposition(node) {
6488
+ const { callee } = node;
6489
+ const isCombinator = callee.type === AST_NODE_TYPES31.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES31.Identifier && ZOD_COMBINATOR_METHODS.has(callee.property.name);
6490
+ return isCombinator || isZodCall(node);
6491
+ }
6381
6492
  function closesOverNothing(node, enclosing) {
6382
6493
  const references = [];
6383
6494
  collectReferences(sourceCode.getScope(node), references);
@@ -6397,6 +6508,9 @@ var prefer_module_level_schema_default = createRule({
6397
6508
  continue;
6398
6509
  }
6399
6510
  const [defStart, defEnd] = definition.node.range;
6511
+ if (defStart >= schemaStart && defEnd <= schemaEnd) {
6512
+ continue;
6513
+ }
6400
6514
  if (defStart >= functionStart && defEnd <= functionEnd) {
6401
6515
  return false;
6402
6516
  }
@@ -6455,6 +6569,13 @@ var prefer_module_level_schema_default = createRule({
6455
6569
  if (readsReceiver(expression)) {
6456
6570
  return;
6457
6571
  }
6572
+ if (buildsLocalizedText(expression)) {
6573
+ return;
6574
+ }
6575
+ const outermost = outermostSchemaExpression(expression);
6576
+ if (outermost !== expression && (readsReceiver(outermost) || buildsLocalizedText(outermost) || !closesOverNothing(outermost, enclosing))) {
6577
+ return;
6578
+ }
6458
6579
  if (!closesOverNothing(expression, enclosing)) {
6459
6580
  return;
6460
6581
  }
@@ -6813,7 +6934,7 @@ var prefer_schema_for_api_payload_default = createRule({
6813
6934
 
6814
6935
  // src/rules/prefer-semantic-colors.ts
6815
6936
  import { AST_NODE_TYPES as AST_NODE_TYPES34 } from "@typescript-eslint/utils";
6816
- import { existsSync, readFileSync } from "fs";
6937
+ import { existsSync, readdirSync, readFileSync } from "fs";
6817
6938
  import { dirname, join, parse } from "path";
6818
6939
 
6819
6940
  // src/rules/_tailwind.ts
@@ -6854,7 +6975,14 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
6854
6975
  var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
6855
6976
  var CSS_VAR_REFERENCE_RE = /var\(\s*--/;
6856
6977
  var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
6857
- var SEMANTIC_TOKEN_RE = /--(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b|(?:bg|text|border)-(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b/;
6978
+ var SEMANTIC_TOKEN_RE = /@theme\b|--(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b|(?:bg|text|border)-(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b/;
6979
+ var CONFIG_IS_SUFFICIENT = /* @__PURE__ */ new Set([
6980
+ "components.json",
6981
+ "tailwind.config.cjs",
6982
+ "tailwind.config.js",
6983
+ "tailwind.config.mjs",
6984
+ "tailwind.config.ts"
6985
+ ]);
6858
6986
  var DETECTION_FILES = [
6859
6987
  "components.json",
6860
6988
  "tailwind.config.js",
@@ -6867,8 +6995,17 @@ var DETECTION_FILES = [
6867
6995
  "src/styles/globals.css",
6868
6996
  "styles/globals.css"
6869
6997
  ];
6870
- var MAX_UPWARD_DEPTH = 8;
6871
- var semanticTokenCache = /* @__PURE__ */ new Map();
6998
+ var WORKSPACE_ROOT_FILES = [
6999
+ "pnpm-workspace.yaml",
7000
+ "pnpm-workspace.yml",
7001
+ "turbo.json",
7002
+ "lerna.json"
7003
+ ];
7004
+ var DEFAULT_WORKSPACE_GLOBS = ["packages/*", "apps/*"];
7005
+ var MAX_WORKSPACE_PACKAGES = 512;
7006
+ var ancestryCache = /* @__PURE__ */ new Map();
7007
+ var workspaceScanCache = /* @__PURE__ */ new Map();
7008
+ var workspaceRootCache = /* @__PURE__ */ new Map();
6872
7009
  var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
6873
7010
  "mask",
6874
7011
  "clipPath",
@@ -6934,48 +7071,134 @@ var isInsideIconFactoryPath = (node) => {
6934
7071
  }
6935
7072
  return false;
6936
7073
  };
6937
- var hasSemanticTokenSystem = (filename) => {
6938
- let dir = dirname(filename);
7074
+ var hasMarkerAt = (dir) => {
7075
+ for (const rel of DETECTION_FILES) {
7076
+ const candidate = join(dir, rel);
7077
+ if (!existsSync(candidate)) continue;
7078
+ if (CONFIG_IS_SUFFICIENT.has(rel)) return true;
7079
+ try {
7080
+ if (SEMANTIC_TOKEN_RE.test(readFileSync(candidate, "utf8"))) return true;
7081
+ } catch {
7082
+ }
7083
+ }
7084
+ return false;
7085
+ };
7086
+ var hasMarkerAtOrAbove = (startDir) => {
7087
+ let dir = startDir;
6939
7088
  const root = parse(dir).root;
6940
7089
  const visited = [];
6941
7090
  let answer;
6942
- for (let depth = 0; depth < MAX_UPWARD_DEPTH; depth += 1) {
6943
- const cached = semanticTokenCache.get(dir);
7091
+ for (; ; ) {
7092
+ const cached = ancestryCache.get(dir);
6944
7093
  if (cached !== void 0) {
6945
7094
  answer = cached;
6946
7095
  break;
6947
7096
  }
6948
7097
  visited.push(dir);
6949
- let found = false;
6950
- for (const rel of DETECTION_FILES) {
6951
- const candidate = join(dir, rel);
6952
- if (!existsSync(candidate)) continue;
6953
- if (rel === "components.json") {
6954
- found = true;
6955
- break;
6956
- }
6957
- try {
6958
- if (SEMANTIC_TOKEN_RE.test(readFileSync(candidate, "utf8"))) {
6959
- found = true;
6960
- break;
6961
- }
6962
- } catch {
6963
- }
6964
- }
6965
- if (found) {
7098
+ if (hasMarkerAt(dir)) {
6966
7099
  answer = true;
6967
7100
  break;
6968
7101
  }
6969
- if (dir === root) {
7102
+ const parent = dirname(dir);
7103
+ if (dir === root || parent === dir) {
6970
7104
  answer = false;
6971
7105
  break;
6972
7106
  }
6973
- dir = dirname(dir);
7107
+ dir = parent;
6974
7108
  }
6975
- if (answer === void 0) return false;
6976
- for (const seen of visited) semanticTokenCache.set(seen, answer);
7109
+ for (const seen of visited) ancestryCache.set(seen, answer);
6977
7110
  return answer;
6978
7111
  };
7112
+ var readWorkspaceGlobs = (dir) => {
7113
+ const globs = [];
7114
+ const packageJson = join(dir, "package.json");
7115
+ if (existsSync(packageJson)) {
7116
+ try {
7117
+ const parsed = JSON.parse(readFileSync(packageJson, "utf8"));
7118
+ const declared = typeof parsed === "object" && parsed !== null && "workspaces" in parsed ? parsed.workspaces : void 0;
7119
+ const list = Array.isArray(declared) ? declared : typeof declared === "object" && declared !== null && Array.isArray(declared.packages) ? declared.packages : [];
7120
+ for (const entry of list) if (typeof entry === "string") globs.push(entry);
7121
+ } catch {
7122
+ }
7123
+ }
7124
+ for (const name of ["pnpm-workspace.yaml", "pnpm-workspace.yml"]) {
7125
+ const yaml = join(dir, name);
7126
+ if (!existsSync(yaml)) continue;
7127
+ try {
7128
+ for (const line of readFileSync(yaml, "utf8").split("\n")) {
7129
+ const match = /^\s*-\s*["']?([^"'#\s]+)["']?\s*$/u.exec(line);
7130
+ if (match?.[1] !== void 0) globs.push(match[1]);
7131
+ }
7132
+ } catch {
7133
+ }
7134
+ }
7135
+ return globs;
7136
+ };
7137
+ var expandWorkspaceGlob = (root, glob) => {
7138
+ const star = glob.indexOf("*");
7139
+ if (star === -1) return [join(root, glob)];
7140
+ const prefix = glob.slice(0, star).replace(/\/$/u, "");
7141
+ const parent = prefix === "" ? root : join(root, prefix);
7142
+ try {
7143
+ return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => join(parent, entry.name));
7144
+ } catch {
7145
+ return [];
7146
+ }
7147
+ };
7148
+ var findWorkspaceRoot = (startDir) => {
7149
+ let dir = startDir;
7150
+ const root = parse(dir).root;
7151
+ const visited = [];
7152
+ let answer;
7153
+ for (; ; ) {
7154
+ const cached = workspaceRootCache.get(dir);
7155
+ if (cached !== void 0) {
7156
+ answer = cached;
7157
+ break;
7158
+ }
7159
+ visited.push(dir);
7160
+ if (WORKSPACE_ROOT_FILES.some((name) => existsSync(join(dir, name))) || readWorkspaceGlobs(dir).length > 0) {
7161
+ answer = dir;
7162
+ break;
7163
+ }
7164
+ const parent = dirname(dir);
7165
+ if (dir === root || parent === dir) {
7166
+ answer = null;
7167
+ break;
7168
+ }
7169
+ dir = parent;
7170
+ }
7171
+ for (const seen of visited) workspaceRootCache.set(seen, answer);
7172
+ return answer;
7173
+ };
7174
+ var workspaceHasMarker = (root) => {
7175
+ const cached = workspaceScanCache.get(root);
7176
+ if (cached !== void 0) return cached;
7177
+ const globs = readWorkspaceGlobs(root);
7178
+ const candidates = /* @__PURE__ */ new Set();
7179
+ for (const glob of globs.length > 0 ? globs : DEFAULT_WORKSPACE_GLOBS) {
7180
+ for (const dir of expandWorkspaceGlob(root, glob)) {
7181
+ candidates.add(dir);
7182
+ if (candidates.size >= MAX_WORKSPACE_PACKAGES) break;
7183
+ }
7184
+ if (candidates.size >= MAX_WORKSPACE_PACKAGES) break;
7185
+ }
7186
+ let found = false;
7187
+ for (const dir of candidates) {
7188
+ if (hasMarkerAt(dir)) {
7189
+ found = true;
7190
+ break;
7191
+ }
7192
+ }
7193
+ workspaceScanCache.set(root, found);
7194
+ return found;
7195
+ };
7196
+ var hasSemanticTokenSystem = (filename) => {
7197
+ const dir = dirname(filename);
7198
+ if (hasMarkerAtOrAbove(dir)) return true;
7199
+ const root = findWorkspaceRoot(dir);
7200
+ return root !== null && workspaceHasMarker(root);
7201
+ };
6979
7202
  var propName = (key) => {
6980
7203
  if (key.type === AST_NODE_TYPES34.Identifier) return key.name;
6981
7204
  if (key.type === AST_NODE_TYPES34.Literal && typeof key.value === "string") return key.value;
@@ -7615,6 +7838,7 @@ import { AST_NODE_TYPES as AST_NODE_TYPES36 } from "@typescript-eslint/utils";
7615
7838
  var MERGEABLE_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
7616
7839
  var ARRAY_MATCHERS = /* @__PURE__ */ new Set(["toEqual", "toStrictEqual"]);
7617
7840
  var COLLECTION_PROPERTIES = /* @__PURE__ */ new Set(["length", "size"]);
7841
+ var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
7618
7842
  var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
7619
7843
  var MIN_RUN_LENGTH = 2;
7620
7844
  function literalText(node, getText) {
@@ -7704,7 +7928,7 @@ var prefer_whole_object_assertion_default = createRule({
7704
7928
  }
7705
7929
  key = { kind: "index", index };
7706
7930
  } else {
7707
- if (actual.property.type !== AST_NODE_TYPES36.Identifier || COLLECTION_PROPERTIES.has(actual.property.name)) {
7931
+ if (actual.property.type !== AST_NODE_TYPES36.Identifier || COLLECTION_PROPERTIES.has(actual.property.name) || LITERAL_KEY_HAZARDS.has(actual.property.name)) {
7708
7932
  return null;
7709
7933
  }
7710
7934
  key = { kind: "property", name: actual.property.name };
@@ -7729,6 +7953,11 @@ var prefer_whole_object_assertion_default = createRule({
7729
7953
  expectedIsLiteral: literal !== null
7730
7954
  };
7731
7955
  }
7956
+ function hasInterveningComment(run) {
7957
+ return run.some(
7958
+ (assertion, index) => sourceCode.getCommentsInside(assertion.statement).length > 0 || index > 0 && sourceCode.getCommentsBefore(assertion.statement).length > 0
7959
+ );
7960
+ }
7732
7961
  function reportPropertyRun(run) {
7733
7962
  const names = /* @__PURE__ */ new Set();
7734
7963
  for (const assertion of run) {
@@ -7755,7 +7984,7 @@ var prefer_whole_object_assertion_default = createRule({
7755
7984
  node: first.statement,
7756
7985
  messageId: "combineAssertions",
7757
7986
  data: { count: String(run.length), receiver: receiverText },
7758
- fix: (fixer) => [
7987
+ fix: hasInterveningComment(run) ? null : (fixer) => [
7759
7988
  fixer.replaceText(first.statement, `expect(${receiverText}).toMatchObject({ ${properties} });`),
7760
7989
  ...run.slice(1).map((assertion) => fixer.remove(assertion.statement))
7761
7990
  ]
@@ -8920,6 +9149,54 @@ var renamedRules = {
8920
9149
  "trailing-value-narration": "no-trailing-value-narration"
8921
9150
  };
8922
9151
 
9152
+ // src/rules/_retired.ts
9153
+ var retiredRules = {
9154
+ "ban-loose-type-guards-in-tests": {
9155
+ removedIn: "5.0.0",
9156
+ reason: "Read at 39 findings with 0 true positives in the #183 corpus audit. Delete the config entry; no replacement."
9157
+ },
9158
+ "no-implicit-attribute-access": {
9159
+ removedIn: "5.0.0",
9160
+ reason: "Read at 50 findings with 0 true positives in the #183 corpus audit. Delete the config entry; no replacement."
9161
+ },
9162
+ "no-sequential-await": {
9163
+ removedIn: "3.0.0",
9164
+ reason: "218 findings, 100% range-contained in core `no-await-in-loop`, which the shipped config already enables. Delete the entry; `no-await-in-loop` covers it."
9165
+ },
9166
+ "no-template-literal-in-log": {
9167
+ removedIn: "2.3.1",
9168
+ reason: "Withdrawn. Delete the config entry; no replacement."
9169
+ },
9170
+ "no-unsafe-cast": {
9171
+ removedIn: "3.0.0",
9172
+ reason: '1,089 findings, matching `@typescript-eslint/consistent-type-assertions` ("never") at the identical line and column with zero residue. Delete the entry; keep that rule enabled.'
9173
+ },
9174
+ "prefer-setup-file-mocks": {
9175
+ removedIn: "5.0.0",
9176
+ reason: "Read at 50 findings with 0 true positives in the #183 corpus audit. Delete the config entry; no replacement."
9177
+ },
9178
+ "prefer-shadcn": {
9179
+ removedIn: "3.0.0",
9180
+ reason: "645 findings, a subset of `react/forbid-elements`; its 24-position residue was all design-system primitives being told not to be the design system. Delete the entry."
9181
+ },
9182
+ "primary-export-file-name": {
9183
+ removedIn: "4.0.0",
9184
+ reason: "Renamed files after one of their exports \u2014 316 findings over 1,966 files sampled 11 harmful / 15 useless / 4 valuable, including telling `next.config.ts` to become `next-config.ts`. Delete the config entry."
9185
+ },
9186
+ "require-parameterized-tests": {
9187
+ removedIn: "4.0.0",
9188
+ reason: "Landed in #153 and never wired up: absent from the `rules` record, every preset, and eslint.strict.mjs. Nothing to migrate."
9189
+ },
9190
+ "require-schema-validate-search": {
9191
+ removedIn: "3.0.0",
9192
+ reason: "14 findings, all matched line-and-column by `@typescript-eslint/consistent-type-assertions`. Delete the entry."
9193
+ },
9194
+ "single-public-export": {
9195
+ removedIn: "3.0.0",
9196
+ reason: "3 findings, all also reported by the then-live `primary-export-file-name` (itself withdrawn in 4.0.0). Delete the entry."
9197
+ }
9198
+ };
9199
+
8923
9200
  // src/index.ts
8924
9201
  var rules = {
8925
9202
  "enforce-file-structure": enforce_file_structure_default,
@@ -8977,29 +9254,8 @@ var rules = {
8977
9254
  };
8978
9255
  var meta = {
8979
9256
  name: "@sarj/eslint-plugin",
8980
- version: "7.1.0"
9257
+ version: "9.0.0"
8981
9258
  };
8982
- var renames = renamedRules;
8983
- var deprecatedAliases = Object.fromEntries(
8984
- Object.entries(renames).map(([from, to]) => {
8985
- const rule = rules[to];
8986
- return [
8987
- from,
8988
- {
8989
- ...rule,
8990
- meta: {
8991
- ...rule.meta,
8992
- deprecated: {
8993
- message: `Renamed to \`@sarj/${to}\`.`,
8994
- replacedBy: [{ rule: { name: `@sarj/${to}` } }],
8995
- deprecatedSince: meta.version
8996
- }
8997
- }
8998
- }
8999
- ];
9000
- })
9001
- );
9002
- var allRules = { ...rules, ...deprecatedAliases };
9003
9259
  var recommendedRules = {
9004
9260
  "@sarj/enforce-file-structure": "warn",
9005
9261
  "@sarj/no-async-callback-in-wait-for": "warn",
@@ -9106,7 +9362,10 @@ var strictRules = {
9106
9362
  };
9107
9363
  var plugin = {
9108
9364
  meta,
9109
- rules: allRules,
9365
+ rules,
9366
+ // Withdrawn names travel WITH the plugin so a consumer's migration script and
9367
+ // this repo's gates read one map, not two. See src/rules/_retired.ts.
9368
+ retiredRules,
9110
9369
  configs: {
9111
9370
  recommended: {},
9112
9371
  strict: {}
@@ -9127,6 +9386,7 @@ export {
9127
9386
  index_default as default,
9128
9387
  recommendedRules,
9129
9388
  renamedRules,
9389
+ retiredRules,
9130
9390
  rules,
9131
9391
  strictRules
9132
9392
  };