@sarj/eslint-plugin 7.1.0 → 8.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.cjs CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  default: () => index_default,
34
34
  recommendedRules: () => recommendedRules,
35
35
  renamedRules: () => renamedRules,
36
+ retiredRules: () => retiredRules,
36
37
  rules: () => rules,
37
38
  strictRules: () => strictRules
38
39
  });
@@ -52,25 +53,41 @@ var createRule = import_utils.ESLintUtils.RuleCreator(evidenceUrl);
52
53
  // src/rules/_paths.ts
53
54
  var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
54
55
  var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
55
- var STORY_DIR_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
56
- 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$)/;
57
- var GENERATED_MARKER_RE = /(?:@generated\b|generated (?:with|by)|generated (?:graphql )?types|do not edit(?: directly| manually)?)/i;
58
- function isTestFile(filename) {
56
+ var STORY_TREE_RE = /(^|\/)stories(?:[_-][^/]*)?\//i;
57
+ 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$)/;
58
+ var EXTERNAL_TREE_RE = /[\\/]external[\\/]/;
59
+ 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;
60
+ var TEST_BASENAME_RE = /[.\-_](test|spec|e2e)\.[cm]?[jt]sx?$/;
61
+ var TEST_INTEGRATION_BASENAME_RE = /\.integration\.[cm]?[jt]sx?$/;
62
+ var TEST_DIR_RE = /(^|\/)(tests?|__tests__|__mocks__|fixtures|__fixtures__|__testfixtures__|e2e|integration)\//;
63
+ var FIXTURE_TREE_RE = /(^|\/)fixture\//;
64
+ function isTestFile(filename, gates = []) {
59
65
  const normalized = filename.replaceAll("\\", "/");
60
66
  const base = normalized.slice(normalized.lastIndexOf("/") + 1);
61
- if (/[.\-_](test|spec|e2e)\.[cm]?[jt]sx?$/.test(base) || /\.integration\.[cm]?[jt]sx?$/.test(base)) {
67
+ if (TEST_BASENAME_RE.test(base) || TEST_INTEGRATION_BASENAME_RE.test(base)) {
62
68
  return true;
63
69
  }
64
- return /(^|\/)(tests?|__tests__|__mocks__|__fixtures__|__testfixtures__|fixtures?|e2e|integration)\//.test(
65
- normalized
66
- );
70
+ if (TEST_DIR_RE.test(normalized)) {
71
+ return true;
72
+ }
73
+ return gates.includes("fixtureTree") && FIXTURE_TREE_RE.test(normalized);
67
74
  }
68
- function isStoryFile(filename) {
75
+ function isStoryFile(filename, gates = []) {
69
76
  const normalized = filename.replaceAll("\\", "/");
70
- return STORY_FILE_RE.test(normalized) || STORY_DIR_RE.test(normalized);
77
+ if (STORY_FILE_RE.test(normalized)) {
78
+ return true;
79
+ }
80
+ return gates.includes("storyTree") && STORY_TREE_RE.test(normalized);
71
81
  }
72
- function isGeneratedFile(filename, sourceText = "") {
73
- return GENERATED_FILE_RE.test(filename.replaceAll("\\", "/")) || GENERATED_MARKER_RE.test(sourceText.slice(0, 2048));
82
+ function isGeneratedFile(filename, sourceText = "", gates = []) {
83
+ const normalized = filename.replaceAll("\\", "/");
84
+ if (GENERATED_FILE_RE.test(normalized)) {
85
+ return true;
86
+ }
87
+ if (gates.includes("externalTree") && EXTERNAL_TREE_RE.test(normalized)) {
88
+ return true;
89
+ }
90
+ return GENERATED_MARKER_RE.test(sourceText.slice(0, 2048));
74
91
  }
75
92
  function isScriptFile(filename) {
76
93
  return SCRIPT_FILE_RE.test(filename);
@@ -155,6 +172,10 @@ var enforce_file_structure_default = createRule({
155
172
 
156
173
  // src/rules/no-async-callback-in-wait-for.ts
157
174
  var import_utils3 = require("@typescript-eslint/utils");
175
+ var isWaitForCallee = (callee) => {
176
+ if (callee.type === import_utils3.AST_NODE_TYPES.Identifier) return callee.name === "waitFor";
177
+ return callee.type === import_utils3.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils3.AST_NODE_TYPES.Identifier && callee.property.name === "waitFor";
178
+ };
158
179
  var no_async_callback_in_wait_for_default = createRule({
159
180
  name: "no-async-callback-in-wait-for",
160
181
  meta: {
@@ -174,14 +195,13 @@ var no_async_callback_in_wait_for_default = createRule({
174
195
  }
175
196
  return {
176
197
  CallExpression(node) {
177
- if (node.callee.type === import_utils3.AST_NODE_TYPES.Identifier && node.callee.name === "waitFor") {
178
- const callback = node.arguments[0];
179
- if (callback && (callback.type === import_utils3.AST_NODE_TYPES.ArrowFunctionExpression || callback.type === import_utils3.AST_NODE_TYPES.FunctionExpression) && callback.async) {
180
- context.report({
181
- node: callback,
182
- messageId: "noAsyncCallbackInWaitFor"
183
- });
184
- }
198
+ if (!isWaitForCallee(node.callee)) return;
199
+ const callback = node.arguments[0];
200
+ if (callback && (callback.type === import_utils3.AST_NODE_TYPES.ArrowFunctionExpression || callback.type === import_utils3.AST_NODE_TYPES.FunctionExpression) && callback.async) {
201
+ context.report({
202
+ node: callback,
203
+ messageId: "noAsyncCallbackInWaitFor"
204
+ });
185
205
  }
186
206
  }
187
207
  };
@@ -809,6 +829,12 @@ var no_comment_cruft_default = createRule({
809
829
  // src/rules/no-conditional-in-test.ts
810
830
  var import_utils7 = require("@typescript-eslint/utils");
811
831
  var TEST_CALLERS = /* @__PURE__ */ new Set(["it", "test"]);
832
+ var HOOK_MEMBERS = /* @__PURE__ */ new Set([
833
+ "afterAll",
834
+ "afterEach",
835
+ "beforeAll",
836
+ "beforeEach"
837
+ ]);
812
838
  var FUNCTION_TYPES = /* @__PURE__ */ new Set([
813
839
  import_utils7.AST_NODE_TYPES.FunctionDeclaration,
814
840
  import_utils7.AST_NODE_TYPES.FunctionExpression,
@@ -852,6 +878,9 @@ function isTestBody(fn) {
852
878
  if (call?.type !== import_utils7.AST_NODE_TYPES.CallExpression || !call.arguments.some((argument) => argument === fn)) {
853
879
  return false;
854
880
  }
881
+ if (call.callee.type === import_utils7.AST_NODE_TYPES.MemberExpression && !call.callee.computed && call.callee.property.type === import_utils7.AST_NODE_TYPES.Identifier && HOOK_MEMBERS.has(call.callee.property.name)) {
882
+ return false;
883
+ }
855
884
  const name = testCallerName(call.callee);
856
885
  return name !== null && TEST_CALLERS.has(name);
857
886
  }
@@ -1006,8 +1035,26 @@ function isTypeLevelNarrowing(node) {
1006
1035
  return isTypeAssertionBranch(node.consequent) && (node.alternate === null || isTypeAssertionBranch(node.alternate));
1007
1036
  }
1008
1037
  var isInertBranch = (branch) => !containsAssertion(branch) && !containsEscape(branch) && !containsSkipCall(branch);
1038
+ function isNormalizationStatement(statement) {
1039
+ if (statement.type === import_utils7.AST_NODE_TYPES.VariableDeclaration) {
1040
+ return true;
1041
+ }
1042
+ if (statement.type === import_utils7.AST_NODE_TYPES.BlockStatement) {
1043
+ return statement.body.every(isNormalizationStatement);
1044
+ }
1045
+ if (statement.type !== import_utils7.AST_NODE_TYPES.ExpressionStatement) {
1046
+ return false;
1047
+ }
1048
+ const { expression } = statement;
1049
+ return expression.type === import_utils7.AST_NODE_TYPES.AssignmentExpression || expression.type === import_utils7.AST_NODE_TYPES.UpdateExpression || expression.type === import_utils7.AST_NODE_TYPES.UnaryExpression && expression.operator === "delete";
1050
+ }
1009
1051
  function isInertNormalization(node) {
1010
- return isInertBranch(node.consequent) && (node.alternate === null || isInertBranch(node.alternate));
1052
+ const branches = [node.consequent, node.alternate].filter(
1053
+ (branch) => branch !== null
1054
+ );
1055
+ return branches.every(
1056
+ (branch) => isInertBranch(branch) && isNormalizationStatement(branch)
1057
+ );
1011
1058
  }
1012
1059
  function isExemptIfStatement(node) {
1013
1060
  return isPinnedNarrowingGuard(node) || isThrowingGuard(node) || isTypeLevelNarrowing(node) || isInertNormalization(node);
@@ -1615,7 +1662,6 @@ var PURE_METHODS = /* @__PURE__ */ new Set([
1615
1662
  "forEach",
1616
1663
  "reduce",
1617
1664
  "reduceRight",
1618
- "find",
1619
1665
  "findIndex",
1620
1666
  "findLast",
1621
1667
  "findLastIndex",
@@ -1638,15 +1684,6 @@ var PURE_METHODS = /* @__PURE__ */ new Set([
1638
1684
  "indexOf",
1639
1685
  "lastIndexOf",
1640
1686
  "at",
1641
- "keys",
1642
- "values",
1643
- "entries",
1644
- "has",
1645
- "get",
1646
- "set",
1647
- "add",
1648
- "delete",
1649
- "clear",
1650
1687
  "toString",
1651
1688
  "toLocaleString",
1652
1689
  "valueOf",
@@ -1677,6 +1714,7 @@ var PURE_NAMESPACES = /* @__PURE__ */ new Set([
1677
1714
  "Boolean",
1678
1715
  "console"
1679
1716
  ]);
1717
+ var IMPURE_NAMESPACE_METHODS = /* @__PURE__ */ new Set(["JSON.parse"]);
1680
1718
  var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
1681
1719
  "Map",
1682
1720
  "Set",
@@ -1713,7 +1751,7 @@ function isPureCall(node) {
1713
1751
  return false;
1714
1752
  }
1715
1753
  if (callee.object.type === import_utils12.AST_NODE_TYPES.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
1716
- return true;
1754
+ return !IMPURE_NAMESPACE_METHODS.has(`${callee.object.name}.${property.name}`);
1717
1755
  }
1718
1756
  return PURE_METHODS.has(property.name);
1719
1757
  }
@@ -5287,7 +5325,7 @@ var no_type_member_comment_wall_default = createRule({
5287
5325
  create(context, [provided]) {
5288
5326
  const options = { ...WALL_DEFAULTS, ...provided };
5289
5327
  const sourceCode = context.sourceCode;
5290
- if (isGeneratedFile(context.filename, sourceCode.text) || isTestFile(context.filename) || isStoryFile(context.filename)) {
5328
+ if (isGeneratedFile(context.filename, sourceCode.text, ["externalTree"]) || isTestFile(context.filename, ["fixtureTree"]) || isStoryFile(context.filename, ["storyTree"])) {
5291
5329
  return {};
5292
5330
  }
5293
5331
  const endingOn = /* @__PURE__ */ new Map();
@@ -6267,7 +6305,7 @@ var DEFAULT_FACTORIES = [
6267
6305
  "tuple",
6268
6306
  "union"
6269
6307
  ];
6270
- var DEFAULT_MIN_PROPERTIES = 1;
6308
+ var DEFAULT_MIN_PROPERTIES = 2;
6271
6309
  var MEMO_CALLEES = /* @__PURE__ */ new Set([
6272
6310
  "lazy",
6273
6311
  "memo",
@@ -6283,6 +6321,34 @@ var TERMINAL_METHODS = /* @__PURE__ */ new Set([
6283
6321
  "safeParseAsync",
6284
6322
  "spa"
6285
6323
  ]);
6324
+ var ZOD_COMBINATOR_METHODS = /* @__PURE__ */ new Set([
6325
+ "and",
6326
+ "array",
6327
+ "catch",
6328
+ "catchall",
6329
+ "default",
6330
+ "extend",
6331
+ "merge",
6332
+ "or",
6333
+ "pipe",
6334
+ "refine",
6335
+ "superRefine",
6336
+ "transform"
6337
+ ]);
6338
+ var I18N_CALLEE_NAMES = /* @__PURE__ */ new Set([
6339
+ "$t",
6340
+ "defineMessage",
6341
+ "gettext",
6342
+ "msg",
6343
+ "ngettext",
6344
+ "t",
6345
+ "translate"
6346
+ ]);
6347
+ var I18N_RECEIVER_NAMES = /* @__PURE__ */ new Set([
6348
+ "$i18n",
6349
+ "i18n",
6350
+ "intl"
6351
+ ]);
6286
6352
  var FUNCTION_TYPES6 = /* @__PURE__ */ new Set([
6287
6353
  import_utils45.AST_NODE_TYPES.ArrowFunctionExpression,
6288
6354
  import_utils45.AST_NODE_TYPES.FunctionDeclaration,
@@ -6316,7 +6382,7 @@ function outermostEnclosingFunction(node) {
6316
6382
  }
6317
6383
  return outermost;
6318
6384
  }
6319
- function readsReceiver(node) {
6385
+ function subtreeSome(root, predicate) {
6320
6386
  let found = false;
6321
6387
  const visit = (value) => {
6322
6388
  if (found || value === null || typeof value !== "object") {
@@ -6332,7 +6398,7 @@ function readsReceiver(node) {
6332
6398
  if (typeof candidate.type !== "string") {
6333
6399
  return;
6334
6400
  }
6335
- if (candidate.type === import_utils45.AST_NODE_TYPES.ThisExpression || candidate.type === import_utils45.AST_NODE_TYPES.Super || candidate.type === import_utils45.AST_NODE_TYPES.Identifier && candidate.name === "arguments") {
6401
+ if (predicate(candidate)) {
6336
6402
  found = true;
6337
6403
  return;
6338
6404
  }
@@ -6343,9 +6409,30 @@ function readsReceiver(node) {
6343
6409
  visit(candidate[key]);
6344
6410
  }
6345
6411
  };
6346
- visit(node);
6412
+ visit(root);
6347
6413
  return found;
6348
6414
  }
6415
+ function readsReceiver(node) {
6416
+ return subtreeSome(
6417
+ node,
6418
+ (inner) => inner.type === import_utils45.AST_NODE_TYPES.ThisExpression || inner.type === import_utils45.AST_NODE_TYPES.Super || inner.type === import_utils45.AST_NODE_TYPES.Identifier && inner.name === "arguments"
6419
+ );
6420
+ }
6421
+ function buildsLocalizedText(node) {
6422
+ return subtreeSome(node, (inner) => {
6423
+ if (inner.type === import_utils45.AST_NODE_TYPES.TaggedTemplateExpression) {
6424
+ return true;
6425
+ }
6426
+ if (inner.type !== import_utils45.AST_NODE_TYPES.CallExpression) {
6427
+ return false;
6428
+ }
6429
+ const { callee } = inner;
6430
+ if (callee.type === import_utils45.AST_NODE_TYPES.Identifier) {
6431
+ return I18N_CALLEE_NAMES.has(callee.name);
6432
+ }
6433
+ return callee.type === import_utils45.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.object.type === import_utils45.AST_NODE_TYPES.Identifier && I18N_RECEIVER_NAMES.has(callee.object.name);
6434
+ });
6435
+ }
6349
6436
  function collectReferences(scope, out) {
6350
6437
  out.push(...scope.references);
6351
6438
  for (const child of scope.childScopes) {
@@ -6415,6 +6502,31 @@ var prefer_module_level_schema_default = createRule({
6415
6502
  }
6416
6503
  return false;
6417
6504
  }
6505
+ function outermostSchemaExpression(expression) {
6506
+ let confirmed = expression;
6507
+ let current = expression;
6508
+ for (; ; ) {
6509
+ const parent = current.parent ?? void 0;
6510
+ if (parent === void 0) {
6511
+ return confirmed;
6512
+ }
6513
+ if (parent.type === import_utils45.AST_NODE_TYPES.Property && parent.value === current || parent.type === import_utils45.AST_NODE_TYPES.ObjectExpression || parent.type === import_utils45.AST_NODE_TYPES.ArrayExpression) {
6514
+ current = parent;
6515
+ continue;
6516
+ }
6517
+ if (parent.type === import_utils45.AST_NODE_TYPES.CallExpression && parent.arguments.includes(current) && isSchemaComposition(parent)) {
6518
+ current = schemaExpression(parent);
6519
+ confirmed = current;
6520
+ continue;
6521
+ }
6522
+ return confirmed;
6523
+ }
6524
+ }
6525
+ function isSchemaComposition(node) {
6526
+ const { callee } = node;
6527
+ const isCombinator = callee.type === import_utils45.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils45.AST_NODE_TYPES.Identifier && ZOD_COMBINATOR_METHODS.has(callee.property.name);
6528
+ return isCombinator || isZodCall(node);
6529
+ }
6418
6530
  function closesOverNothing(node, enclosing) {
6419
6531
  const references = [];
6420
6532
  collectReferences(sourceCode.getScope(node), references);
@@ -6434,6 +6546,9 @@ var prefer_module_level_schema_default = createRule({
6434
6546
  continue;
6435
6547
  }
6436
6548
  const [defStart, defEnd] = definition.node.range;
6549
+ if (defStart >= schemaStart && defEnd <= schemaEnd) {
6550
+ continue;
6551
+ }
6437
6552
  if (defStart >= functionStart && defEnd <= functionEnd) {
6438
6553
  return false;
6439
6554
  }
@@ -6492,6 +6607,13 @@ var prefer_module_level_schema_default = createRule({
6492
6607
  if (readsReceiver(expression)) {
6493
6608
  return;
6494
6609
  }
6610
+ if (buildsLocalizedText(expression)) {
6611
+ return;
6612
+ }
6613
+ const outermost = outermostSchemaExpression(expression);
6614
+ if (outermost !== expression && (readsReceiver(outermost) || buildsLocalizedText(outermost) || !closesOverNothing(outermost, enclosing))) {
6615
+ return;
6616
+ }
6495
6617
  if (!closesOverNothing(expression, enclosing)) {
6496
6618
  return;
6497
6619
  }
@@ -6891,7 +7013,14 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
6891
7013
  var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
6892
7014
  var CSS_VAR_REFERENCE_RE = /var\(\s*--/;
6893
7015
  var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
6894
- 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/;
7016
+ 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/;
7017
+ var CONFIG_IS_SUFFICIENT = /* @__PURE__ */ new Set([
7018
+ "components.json",
7019
+ "tailwind.config.cjs",
7020
+ "tailwind.config.js",
7021
+ "tailwind.config.mjs",
7022
+ "tailwind.config.ts"
7023
+ ]);
6895
7024
  var DETECTION_FILES = [
6896
7025
  "components.json",
6897
7026
  "tailwind.config.js",
@@ -6904,8 +7033,17 @@ var DETECTION_FILES = [
6904
7033
  "src/styles/globals.css",
6905
7034
  "styles/globals.css"
6906
7035
  ];
6907
- var MAX_UPWARD_DEPTH = 8;
6908
- var semanticTokenCache = /* @__PURE__ */ new Map();
7036
+ var WORKSPACE_ROOT_FILES = [
7037
+ "pnpm-workspace.yaml",
7038
+ "pnpm-workspace.yml",
7039
+ "turbo.json",
7040
+ "lerna.json"
7041
+ ];
7042
+ var DEFAULT_WORKSPACE_GLOBS = ["packages/*", "apps/*"];
7043
+ var MAX_WORKSPACE_PACKAGES = 512;
7044
+ var ancestryCache = /* @__PURE__ */ new Map();
7045
+ var workspaceScanCache = /* @__PURE__ */ new Map();
7046
+ var workspaceRootCache = /* @__PURE__ */ new Map();
6909
7047
  var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
6910
7048
  "mask",
6911
7049
  "clipPath",
@@ -6971,48 +7109,134 @@ var isInsideIconFactoryPath = (node) => {
6971
7109
  }
6972
7110
  return false;
6973
7111
  };
6974
- var hasSemanticTokenSystem = (filename) => {
6975
- let dir = (0, import_path.dirname)(filename);
7112
+ var hasMarkerAt = (dir) => {
7113
+ for (const rel of DETECTION_FILES) {
7114
+ const candidate = (0, import_path.join)(dir, rel);
7115
+ if (!(0, import_fs.existsSync)(candidate)) continue;
7116
+ if (CONFIG_IS_SUFFICIENT.has(rel)) return true;
7117
+ try {
7118
+ if (SEMANTIC_TOKEN_RE.test((0, import_fs.readFileSync)(candidate, "utf8"))) return true;
7119
+ } catch {
7120
+ }
7121
+ }
7122
+ return false;
7123
+ };
7124
+ var hasMarkerAtOrAbove = (startDir) => {
7125
+ let dir = startDir;
6976
7126
  const root = (0, import_path.parse)(dir).root;
6977
7127
  const visited = [];
6978
7128
  let answer;
6979
- for (let depth = 0; depth < MAX_UPWARD_DEPTH; depth += 1) {
6980
- const cached = semanticTokenCache.get(dir);
7129
+ for (; ; ) {
7130
+ const cached = ancestryCache.get(dir);
6981
7131
  if (cached !== void 0) {
6982
7132
  answer = cached;
6983
7133
  break;
6984
7134
  }
6985
7135
  visited.push(dir);
6986
- let found = false;
6987
- for (const rel of DETECTION_FILES) {
6988
- const candidate = (0, import_path.join)(dir, rel);
6989
- if (!(0, import_fs.existsSync)(candidate)) continue;
6990
- if (rel === "components.json") {
6991
- found = true;
6992
- break;
6993
- }
6994
- try {
6995
- if (SEMANTIC_TOKEN_RE.test((0, import_fs.readFileSync)(candidate, "utf8"))) {
6996
- found = true;
6997
- break;
6998
- }
6999
- } catch {
7000
- }
7001
- }
7002
- if (found) {
7136
+ if (hasMarkerAt(dir)) {
7003
7137
  answer = true;
7004
7138
  break;
7005
7139
  }
7006
- if (dir === root) {
7140
+ const parent = (0, import_path.dirname)(dir);
7141
+ if (dir === root || parent === dir) {
7007
7142
  answer = false;
7008
7143
  break;
7009
7144
  }
7010
- dir = (0, import_path.dirname)(dir);
7145
+ dir = parent;
7146
+ }
7147
+ for (const seen of visited) ancestryCache.set(seen, answer);
7148
+ return answer;
7149
+ };
7150
+ var readWorkspaceGlobs = (dir) => {
7151
+ const globs = [];
7152
+ const packageJson = (0, import_path.join)(dir, "package.json");
7153
+ if ((0, import_fs.existsSync)(packageJson)) {
7154
+ try {
7155
+ const parsed = JSON.parse((0, import_fs.readFileSync)(packageJson, "utf8"));
7156
+ const declared = typeof parsed === "object" && parsed !== null && "workspaces" in parsed ? parsed.workspaces : void 0;
7157
+ const list = Array.isArray(declared) ? declared : typeof declared === "object" && declared !== null && Array.isArray(declared.packages) ? declared.packages : [];
7158
+ for (const entry of list) if (typeof entry === "string") globs.push(entry);
7159
+ } catch {
7160
+ }
7161
+ }
7162
+ for (const name of ["pnpm-workspace.yaml", "pnpm-workspace.yml"]) {
7163
+ const yaml = (0, import_path.join)(dir, name);
7164
+ if (!(0, import_fs.existsSync)(yaml)) continue;
7165
+ try {
7166
+ for (const line of (0, import_fs.readFileSync)(yaml, "utf8").split("\n")) {
7167
+ const match = /^\s*-\s*["']?([^"'#\s]+)["']?\s*$/u.exec(line);
7168
+ if (match?.[1] !== void 0) globs.push(match[1]);
7169
+ }
7170
+ } catch {
7171
+ }
7172
+ }
7173
+ return globs;
7174
+ };
7175
+ var expandWorkspaceGlob = (root, glob) => {
7176
+ const star = glob.indexOf("*");
7177
+ if (star === -1) return [(0, import_path.join)(root, glob)];
7178
+ const prefix = glob.slice(0, star).replace(/\/$/u, "");
7179
+ const parent = prefix === "" ? root : (0, import_path.join)(root, prefix);
7180
+ try {
7181
+ return (0, import_fs.readdirSync)(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => (0, import_path.join)(parent, entry.name));
7182
+ } catch {
7183
+ return [];
7184
+ }
7185
+ };
7186
+ var findWorkspaceRoot = (startDir) => {
7187
+ let dir = startDir;
7188
+ const root = (0, import_path.parse)(dir).root;
7189
+ const visited = [];
7190
+ let answer;
7191
+ for (; ; ) {
7192
+ const cached = workspaceRootCache.get(dir);
7193
+ if (cached !== void 0) {
7194
+ answer = cached;
7195
+ break;
7196
+ }
7197
+ visited.push(dir);
7198
+ if (WORKSPACE_ROOT_FILES.some((name) => (0, import_fs.existsSync)((0, import_path.join)(dir, name))) || readWorkspaceGlobs(dir).length > 0) {
7199
+ answer = dir;
7200
+ break;
7201
+ }
7202
+ const parent = (0, import_path.dirname)(dir);
7203
+ if (dir === root || parent === dir) {
7204
+ answer = null;
7205
+ break;
7206
+ }
7207
+ dir = parent;
7011
7208
  }
7012
- if (answer === void 0) return false;
7013
- for (const seen of visited) semanticTokenCache.set(seen, answer);
7209
+ for (const seen of visited) workspaceRootCache.set(seen, answer);
7014
7210
  return answer;
7015
7211
  };
7212
+ var workspaceHasMarker = (root) => {
7213
+ const cached = workspaceScanCache.get(root);
7214
+ if (cached !== void 0) return cached;
7215
+ const globs = readWorkspaceGlobs(root);
7216
+ const candidates = /* @__PURE__ */ new Set();
7217
+ for (const glob of globs.length > 0 ? globs : DEFAULT_WORKSPACE_GLOBS) {
7218
+ for (const dir of expandWorkspaceGlob(root, glob)) {
7219
+ candidates.add(dir);
7220
+ if (candidates.size >= MAX_WORKSPACE_PACKAGES) break;
7221
+ }
7222
+ if (candidates.size >= MAX_WORKSPACE_PACKAGES) break;
7223
+ }
7224
+ let found = false;
7225
+ for (const dir of candidates) {
7226
+ if (hasMarkerAt(dir)) {
7227
+ found = true;
7228
+ break;
7229
+ }
7230
+ }
7231
+ workspaceScanCache.set(root, found);
7232
+ return found;
7233
+ };
7234
+ var hasSemanticTokenSystem = (filename) => {
7235
+ const dir = (0, import_path.dirname)(filename);
7236
+ if (hasMarkerAtOrAbove(dir)) return true;
7237
+ const root = findWorkspaceRoot(dir);
7238
+ return root !== null && workspaceHasMarker(root);
7239
+ };
7016
7240
  var propName = (key) => {
7017
7241
  if (key.type === import_utils48.AST_NODE_TYPES.Identifier) return key.name;
7018
7242
  if (key.type === import_utils48.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
@@ -7649,6 +7873,7 @@ var import_utils51 = require("@typescript-eslint/utils");
7649
7873
  var MERGEABLE_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
7650
7874
  var ARRAY_MATCHERS = /* @__PURE__ */ new Set(["toEqual", "toStrictEqual"]);
7651
7875
  var COLLECTION_PROPERTIES = /* @__PURE__ */ new Set(["length", "size"]);
7876
+ var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
7652
7877
  var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
7653
7878
  var MIN_RUN_LENGTH = 2;
7654
7879
  function literalText(node, getText) {
@@ -7738,7 +7963,7 @@ var prefer_whole_object_assertion_default = createRule({
7738
7963
  }
7739
7964
  key = { kind: "index", index };
7740
7965
  } else {
7741
- if (actual.property.type !== import_utils51.AST_NODE_TYPES.Identifier || COLLECTION_PROPERTIES.has(actual.property.name)) {
7966
+ if (actual.property.type !== import_utils51.AST_NODE_TYPES.Identifier || COLLECTION_PROPERTIES.has(actual.property.name) || LITERAL_KEY_HAZARDS.has(actual.property.name)) {
7742
7967
  return null;
7743
7968
  }
7744
7969
  key = { kind: "property", name: actual.property.name };
@@ -7763,6 +7988,11 @@ var prefer_whole_object_assertion_default = createRule({
7763
7988
  expectedIsLiteral: literal !== null
7764
7989
  };
7765
7990
  }
7991
+ function hasInterveningComment(run) {
7992
+ return run.some(
7993
+ (assertion, index) => sourceCode.getCommentsInside(assertion.statement).length > 0 || index > 0 && sourceCode.getCommentsBefore(assertion.statement).length > 0
7994
+ );
7995
+ }
7766
7996
  function reportPropertyRun(run) {
7767
7997
  const names = /* @__PURE__ */ new Set();
7768
7998
  for (const assertion of run) {
@@ -7789,7 +8019,7 @@ var prefer_whole_object_assertion_default = createRule({
7789
8019
  node: first.statement,
7790
8020
  messageId: "combineAssertions",
7791
8021
  data: { count: String(run.length), receiver: receiverText },
7792
- fix: (fixer) => [
8022
+ fix: hasInterveningComment(run) ? null : (fixer) => [
7793
8023
  fixer.replaceText(first.statement, `expect(${receiverText}).toMatchObject({ ${properties} });`),
7794
8024
  ...run.slice(1).map((assertion) => fixer.remove(assertion.statement))
7795
8025
  ]
@@ -8954,6 +9184,54 @@ var renamedRules = {
8954
9184
  "trailing-value-narration": "no-trailing-value-narration"
8955
9185
  };
8956
9186
 
9187
+ // src/rules/_retired.ts
9188
+ var retiredRules = {
9189
+ "ban-loose-type-guards-in-tests": {
9190
+ removedIn: "5.0.0",
9191
+ reason: "Read at 39 findings with 0 true positives in the #183 corpus audit. Delete the config entry; no replacement."
9192
+ },
9193
+ "no-implicit-attribute-access": {
9194
+ removedIn: "5.0.0",
9195
+ reason: "Read at 50 findings with 0 true positives in the #183 corpus audit. Delete the config entry; no replacement."
9196
+ },
9197
+ "no-sequential-await": {
9198
+ removedIn: "3.0.0",
9199
+ 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."
9200
+ },
9201
+ "no-template-literal-in-log": {
9202
+ removedIn: "2.3.1",
9203
+ reason: "Withdrawn. Delete the config entry; no replacement."
9204
+ },
9205
+ "no-unsafe-cast": {
9206
+ removedIn: "3.0.0",
9207
+ 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.'
9208
+ },
9209
+ "prefer-setup-file-mocks": {
9210
+ removedIn: "5.0.0",
9211
+ reason: "Read at 50 findings with 0 true positives in the #183 corpus audit. Delete the config entry; no replacement."
9212
+ },
9213
+ "prefer-shadcn": {
9214
+ removedIn: "3.0.0",
9215
+ 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."
9216
+ },
9217
+ "primary-export-file-name": {
9218
+ removedIn: "4.0.0",
9219
+ 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."
9220
+ },
9221
+ "require-parameterized-tests": {
9222
+ removedIn: "4.0.0",
9223
+ reason: "Landed in #153 and never wired up: absent from the `rules` record, every preset, and eslint.strict.mjs. Nothing to migrate."
9224
+ },
9225
+ "require-schema-validate-search": {
9226
+ removedIn: "3.0.0",
9227
+ reason: "14 findings, all matched line-and-column by `@typescript-eslint/consistent-type-assertions`. Delete the entry."
9228
+ },
9229
+ "single-public-export": {
9230
+ removedIn: "3.0.0",
9231
+ reason: "3 findings, all also reported by the then-live `primary-export-file-name` (itself withdrawn in 4.0.0). Delete the entry."
9232
+ }
9233
+ };
9234
+
8957
9235
  // src/index.ts
8958
9236
  var rules = {
8959
9237
  "enforce-file-structure": enforce_file_structure_default,
@@ -9011,7 +9289,7 @@ var rules = {
9011
9289
  };
9012
9290
  var meta = {
9013
9291
  name: "@sarj/eslint-plugin",
9014
- version: "7.1.0"
9292
+ version: "8.0.0"
9015
9293
  };
9016
9294
  var renames = renamedRules;
9017
9295
  var deprecatedAliases = Object.fromEntries(
@@ -9141,6 +9419,9 @@ var strictRules = {
9141
9419
  var plugin = {
9142
9420
  meta,
9143
9421
  rules: allRules,
9422
+ // Withdrawn names travel WITH the plugin so a consumer's migration script and
9423
+ // this repo's gates read one map, not two. See src/rules/_retired.ts.
9424
+ retiredRules,
9144
9425
  configs: {
9145
9426
  recommended: {},
9146
9427
  strict: {}
@@ -9161,6 +9442,7 @@ var index_default = plugin;
9161
9442
  0 && (module.exports = {
9162
9443
  recommendedRules,
9163
9444
  renamedRules,
9445
+ retiredRules,
9164
9446
  rules,
9165
9447
  strictRules
9166
9448
  });