@sarj/eslint-plugin 18.1.4 → 18.1.5

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
@@ -204,8 +204,15 @@ function createNativeRule(config, url) {
204
204
  const defaults = metadataDefaults ?? legacyDefaults;
205
205
  if (defaults === void 0) throw new TypeError("Declared defaults disappeared while creating the rule.");
206
206
  const create = config.create;
207
+ const applied = /* @__PURE__ */ new WeakMap();
207
208
  return { ...base, create(context) {
208
- return create(context, import_utils2.ESLintUtils.applyDefault(defaults, context.options));
209
+ const options = context.options;
210
+ let merged = applied.get(options);
211
+ if (merged === void 0) {
212
+ merged = import_utils2.ESLintUtils.applyDefault(defaults, options);
213
+ applied.set(options, merged);
214
+ }
215
+ return create(context, merged);
209
216
  } };
210
217
  }
211
218
  function hasNoDefaultOptions(_config, metaDefaults, legacyDefaults) {
@@ -612,12 +619,17 @@ function isGeneratedFile(filename, sourceText = "", gates = []) {
612
619
  if (gates.includes("externalTree") && EXTERNAL_TREE_RE.test(normalized)) {
613
620
  return true;
614
621
  }
622
+ return generatedHeader(sourceText);
623
+ }
624
+ var lastHeaderText;
625
+ var lastHeaderGenerated = false;
626
+ function generatedHeader(sourceText) {
627
+ if (sourceText === lastHeaderText) return lastHeaderGenerated;
615
628
  const headerBodies = leadingCommentBodies(sourceText.slice(0, 2048));
616
629
  const nonAi = headerBodies.filter((body3) => !AI_ATTRIBUTION_RE.test(body3));
617
- if (nonAi.some((body3) => GENERATED_MARKER_RE.test(body3))) {
618
- return true;
619
- }
620
- return !headerBodies.some((body3) => AI_ATTRIBUTION_RE.test(body3)) && headerBodies.some((body3) => DO_NOT_EDIT_RE.test(body3));
630
+ lastHeaderGenerated = nonAi.some((body3) => GENERATED_MARKER_RE.test(body3)) || !headerBodies.some((body3) => AI_ATTRIBUTION_RE.test(body3)) && headerBodies.some((body3) => DO_NOT_EDIT_RE.test(body3));
631
+ lastHeaderText = sourceText;
632
+ return lastHeaderGenerated;
621
633
  }
622
634
  function leadingCommentBodies(sourceText) {
623
635
  const bodies = [];
@@ -658,6 +670,16 @@ function isScriptFile(filename) {
658
670
  }
659
671
  function scopedFilename(input) {
660
672
  if (typeof input === "string") return input.replaceAll("\\", "/");
673
+ if (input.filename === lastScopedFilename && input.cwd === lastScopedCwd) return lastScoped;
674
+ lastScoped = computeScopedFilename(input);
675
+ lastScopedFilename = input.filename;
676
+ lastScopedCwd = input.cwd;
677
+ return lastScoped;
678
+ }
679
+ var lastScopedFilename;
680
+ var lastScopedCwd;
681
+ var lastScoped = "";
682
+ function computeScopedFilename(input) {
661
683
  const filename = input.filename.replaceAll("\\", "/");
662
684
  const cwd = input.cwd.replaceAll("\\", "/");
663
685
  const fileWindows = windowsAbsolute(filename);
@@ -2299,8 +2321,18 @@ var FRONTEND_IMPORT = /^(?:react(?:-dom)?|next|preact|vue|svelte|@angular\/core|
2299
2321
  function isFrontendSource(context) {
2300
2322
  return /\.[jt]sx$/u.test(context.filename) || frontendModule(context.sourceCode.ast) || frontendPackage(context.filename);
2301
2323
  }
2324
+ var FRONTEND_PACKAGE_BY_DIRECTORY = /* @__PURE__ */ new Map();
2302
2325
  function frontendPackage(filename) {
2303
- let directory = (0, import_node_path2.dirname)((0, import_node_path2.resolve)(filename));
2326
+ const directory = (0, import_node_path2.dirname)((0, import_node_path2.resolve)(filename));
2327
+ let cached = FRONTEND_PACKAGE_BY_DIRECTORY.get(directory);
2328
+ if (cached === void 0) {
2329
+ cached = nearestManifestDeclaresFrontend(directory);
2330
+ FRONTEND_PACKAGE_BY_DIRECTORY.set(directory, cached);
2331
+ }
2332
+ return cached;
2333
+ }
2334
+ function nearestManifestDeclaresFrontend(start) {
2335
+ let directory = start;
2304
2336
  for (; ; ) {
2305
2337
  const manifestPath = (0, import_node_path2.join)(directory, "package.json");
2306
2338
  if ((0, import_node_fs.existsSync)(manifestPath)) {
@@ -3247,6 +3279,21 @@ function isUnshadowedTestMockGlobal(source, identifier) {
3247
3279
  return binding3 === null || binding3.defs.length === 0 && !binding3.references.some((reference) => reference.isWrite());
3248
3280
  }
3249
3281
  var TEST_FRAMEWORK_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
3282
+ function acceptedTestFrameworkName(source, identifier, kind, accepted) {
3283
+ if (!accepted.has(identifier.name) && !importedLocalNames(source.ast).has(identifier.name)) return null;
3284
+ const name = runtimeTestFrameworkName(source, identifier, kind);
3285
+ return name !== null && accepted.has(name) ? name : null;
3286
+ }
3287
+ var EXPECT_ASSERTION = /* @__PURE__ */ new Set(["expect"]);
3288
+ var IMPORTED_LOCAL_NAMES_BY_PROGRAM = /* @__PURE__ */ new WeakMap();
3289
+ function importedLocalNames(program) {
3290
+ let names = IMPORTED_LOCAL_NAMES_BY_PROGRAM.get(program);
3291
+ if (names === void 0) {
3292
+ names = new Set(program.body.flatMap((statement) => statement.type === import_utils26.AST_NODE_TYPES.ImportDeclaration ? statement.specifiers.map((specifier) => specifier.local.name) : []));
3293
+ IMPORTED_LOCAL_NAMES_BY_PROGRAM.set(program, names);
3294
+ }
3295
+ return names;
3296
+ }
3250
3297
  function runtimeTestFrameworkName(source, identifier, kind) {
3251
3298
  const binding3 = import_utils26.ASTUtils.findVariable(source.getScope(identifier), identifier.name);
3252
3299
  if (binding3 === null || binding3.defs.length === 0) {
@@ -6126,7 +6173,7 @@ function isSearchParamReceiver(call, services) {
6126
6173
  // package.json
6127
6174
  var package_default = {
6128
6175
  name: "@sarj/eslint-plugin",
6129
- version: "18.1.4",
6176
+ version: "18.1.5",
6130
6177
  packageManager: "npm@12.2.1",
6131
6178
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6132
6179
  type: "module",
@@ -11783,6 +11830,7 @@ var enforce_file_structure_default = createRule({
11783
11830
  // src/rules/duplicate-test-body.ts
11784
11831
  var import_utils76 = require("@typescript-eslint/utils");
11785
11832
  var TEST_CALLERS = /* @__PURE__ */ new Set(["it", "test"]);
11833
+ var ASSERTION_NAMES = /* @__PURE__ */ new Set(["assert", "expect"]);
11786
11834
  var TEST_MODIFIERS2 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
11787
11835
  var FUNCTION_TYPES = /* @__PURE__ */ new Set([
11788
11836
  import_utils76.AST_NODE_TYPES.FunctionExpression,
@@ -11894,7 +11942,7 @@ function constAssertionLiteral(node) {
11894
11942
  function isAssertionCall(node, sourceCode) {
11895
11943
  const unwrappedNodeCallee = unwrapExpression(node.callee);
11896
11944
  const root = rootIdentifier(unwrappedNodeCallee);
11897
- return root !== null && ["assert", "expect"].includes(runtimeTestFrameworkName(sourceCode, root, "assertion") ?? "");
11945
+ return root !== null && acceptedTestFrameworkName(sourceCode, root, "assertion", ASSERTION_NAMES) !== null;
11898
11946
  }
11899
11947
  function rootIdentifier(callee) {
11900
11948
  callee = unwrapExpression(callee);
@@ -11928,7 +11976,16 @@ function normalizedLiteral(node) {
11928
11976
  function normalizedStringLiteral(value) {
11929
11977
  return value.includes("\n") || value.length > MAX_NORMALIZED_STRING_LENGTH ? ["Literal", "string", value] : ["Literal", "string"];
11930
11978
  }
11979
+ var CANDIDATE_BY_CALL = /* @__PURE__ */ new WeakMap();
11931
11980
  function duplicateTestBodyCandidate(call, sourceCode) {
11981
+ let candidate2 = CANDIDATE_BY_CALL.get(call);
11982
+ if (candidate2 === void 0) {
11983
+ candidate2 = computeDuplicateTestBodyCandidate(call, sourceCode);
11984
+ CANDIDATE_BY_CALL.set(call, candidate2);
11985
+ }
11986
+ return candidate2;
11987
+ }
11988
+ function computeDuplicateTestBodyCandidate(call, sourceCode) {
11932
11989
  if (call.parent?.type !== import_utils76.AST_NODE_TYPES.ExpressionStatement) return null;
11933
11990
  const container = call.parent.parent;
11934
11991
  if (container?.type !== import_utils76.AST_NODE_TYPES.Program && container?.type !== import_utils76.AST_NODE_TYPES.BlockStatement) return null;
@@ -11970,8 +12027,7 @@ function testBody(call, sourceCode) {
11970
12027
  function testCallerSignature(callee, sourceCode) {
11971
12028
  callee = unwrapExpression(callee);
11972
12029
  if (callee.type === import_utils76.AST_NODE_TYPES.Identifier) {
11973
- const name = runtimeTestFrameworkName(sourceCode, callee, "test");
11974
- return name !== null && TEST_CALLERS.has(name) ? name : null;
12030
+ return acceptedTestFrameworkName(sourceCode, callee, "test", TEST_CALLERS);
11975
12031
  }
11976
12032
  if (callee.type === import_utils76.AST_NODE_TYPES.MemberExpression) {
11977
12033
  const base = testCallerSignature(callee.object, sourceCode);
@@ -16230,8 +16286,14 @@ var no_exception_text_in_5xx_response_default = createRule({
16230
16286
  const candidates = [];
16231
16287
  const changedErrors = /* @__PURE__ */ new Set();
16232
16288
  const stableBindings = /* @__PURE__ */ new WeakMap();
16233
- const referenceIdentifiers = new WeakSet(sourceCode.scopeManager?.scopes.flatMap((scope) => scope.references.filter((reference) => reference.isValueReference !== false).map((reference) => reference.identifier)) ?? []);
16289
+ let referenceIdentifiers;
16234
16290
  let uncertainSerialization = false;
16291
+ const catchNames = /* @__PURE__ */ new Set();
16292
+ const responseNames = /* @__PURE__ */ new Set(["Response", ...nextResponseLocals(sourceCode.ast)]);
16293
+ function valueReference(node) {
16294
+ referenceIdentifiers ??= new WeakSet(sourceCode.scopeManager?.scopes.flatMap((scope) => scope.references.filter((reference) => reference.isValueReference !== false).map((reference) => reference.identifier)) ?? []);
16295
+ return referenceIdentifiers.has(node);
16296
+ }
16235
16297
  function binding3(identifier) {
16236
16298
  return import_utils98.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
16237
16299
  }
@@ -16250,14 +16312,14 @@ var no_exception_text_in_5xx_response_default = createRule({
16250
16312
  }
16251
16313
  function caughtValue(node) {
16252
16314
  const value = unwrapExpression(node);
16253
- if (value.type !== import_utils98.AST_NODE_TYPES.Identifier) return null;
16315
+ if (value.type !== import_utils98.AST_NODE_TYPES.Identifier || !catchNames.has(value.name)) return null;
16254
16316
  const variable = binding3(value);
16255
16317
  const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
16256
16318
  return definition?.type === "CatchClause" && definition.node.param?.type === import_utils98.AST_NODE_TYPES.Identifier && variable !== null && stable2(variable) ? variable : null;
16257
16319
  }
16258
16320
  function responseFactory(node) {
16259
16321
  const value = unwrapExpression(node);
16260
- if (value.type !== import_utils98.AST_NODE_TYPES.Identifier) return false;
16322
+ if (value.type !== import_utils98.AST_NODE_TYPES.Identifier || !responseNames.has(value.name)) return false;
16261
16323
  const variable = binding3(value);
16262
16324
  if (value.name === "Response" && (variable === null || variable.defs.length === 0)) {
16263
16325
  return variable === null || stable2(variable);
@@ -16340,8 +16402,11 @@ var no_exception_text_in_5xx_response_default = createRule({
16340
16402
  if (error !== null) changedErrors.add(error);
16341
16403
  }
16342
16404
  return {
16405
+ CatchClause(node) {
16406
+ if (node.param?.type === import_utils98.AST_NODE_TYPES.Identifier) catchNames.add(node.param.name);
16407
+ },
16343
16408
  Identifier(node) {
16344
- if (!referenceIdentifiers.has(node)) return;
16409
+ if (!catchNames.has(node.name) && !responseNames.has(node.name) || !valueReference(node)) return;
16345
16410
  let typeName = node;
16346
16411
  while (typeName.parent?.type === import_utils98.AST_NODE_TYPES.TSQualifiedName) typeName = typeName.parent;
16347
16412
  if (typeName.parent?.type === import_utils98.AST_NODE_TYPES.TSTypeQuery) return;
@@ -16397,6 +16462,9 @@ var no_exception_text_in_5xx_response_default = createRule({
16397
16462
  };
16398
16463
  }
16399
16464
  });
16465
+ function nextResponseLocals(program) {
16466
+ return program.body.flatMap((statement) => statement.type === import_utils98.AST_NODE_TYPES.ImportDeclaration && statement.source.value === "next/server" ? statement.specifiers.flatMap((specifier) => specifier.type === import_utils98.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils98.AST_NODE_TYPES.Identifier && specifier.imported.name === "NextResponse" ? [specifier.local.name] : []) : []);
16467
+ }
16400
16468
 
16401
16469
  // src/rules/no-json-stringify-error.ts
16402
16470
  var import_utils99 = require("@typescript-eslint/utils");
@@ -18490,7 +18558,7 @@ var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
18490
18558
  var TEST_NAMES = /* @__PURE__ */ new Set(["it", "test"]);
18491
18559
  var TEST_MODIFIERS3 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
18492
18560
  var TEST_BUILDERS = /* @__PURE__ */ new Set(["each", "for", "runIf", "skipIf"]);
18493
- var ASSERTION_NAMES = /* @__PURE__ */ new Set(["assert", "assertType", "expect", "expectTypeOf"]);
18561
+ var ASSERTION_NAMES2 = /* @__PURE__ */ new Set(["assert", "assertType", "expect", "expectTypeOf"]);
18494
18562
  var FUNCTION_TYPES3 = /* @__PURE__ */ new Set([import_utils111.AST_NODE_TYPES.ArrowFunctionExpression, import_utils111.AST_NODE_TYPES.FunctionExpression, import_utils111.AST_NODE_TYPES.FunctionDeclaration]);
18495
18563
  function staticMemberName3(node) {
18496
18564
  if (!node.computed && node.property.type === import_utils111.AST_NODE_TYPES.Identifier) return node.property.name;
@@ -18508,7 +18576,7 @@ function isDirectTestCallback(node, context) {
18508
18576
  const call = directArgumentCall(node);
18509
18577
  if (call === null) return false;
18510
18578
  const root = testRoot(call.callee);
18511
- return root !== null && TEST_NAMES.has(runtimeTestFrameworkName(context.sourceCode, root, "test") ?? "");
18579
+ return root !== null && acceptedTestFrameworkName(context.sourceCode, root, "test", TEST_NAMES) !== null;
18512
18580
  }
18513
18581
  function testRoot(callee) {
18514
18582
  callee = unwrapExpression(callee);
@@ -18534,13 +18602,13 @@ function walkOwnScope(node, predicate) {
18534
18602
  function isAssertion(node, context) {
18535
18603
  if (node.type !== import_utils111.AST_NODE_TYPES.CallExpression) return false;
18536
18604
  const root = rootIdentifier2(node.callee);
18537
- return root !== null && ASSERTION_NAMES.has(runtimeTestFrameworkName(context.sourceCode, root, "assertion") ?? "");
18605
+ return root !== null && acceptedTestFrameworkName(context.sourceCode, root, "assertion", ASSERTION_NAMES2) !== null;
18538
18606
  }
18539
18607
  function isExplicitSkip(node, context) {
18540
18608
  const unwrappedNodeCallee = node.type === "CallExpression" || node.type === "NewExpression" ? unwrapExpression(node.callee) : null;
18541
18609
  if (node.type !== import_utils111.AST_NODE_TYPES.CallExpression || unwrappedNodeCallee?.type !== import_utils111.AST_NODE_TYPES.MemberExpression || staticMemberName3(unwrappedNodeCallee) !== "skip") return false;
18542
18610
  const root = rootIdentifier2(unwrappedNodeCallee.object);
18543
- return root !== null && TEST_NAMES.has(runtimeTestFrameworkName(context.sourceCode, root, "test") ?? "");
18611
+ return root !== null && acceptedTestFrameworkName(context.sourceCode, root, "test", TEST_NAMES) !== null;
18544
18612
  }
18545
18613
  function isUnconditionalContextSkip(node, owner, context) {
18546
18614
  if (node.type !== import_utils111.AST_NODE_TYPES.CallExpression || node.optional) return false;
@@ -21693,6 +21761,7 @@ var no_server_file_uploads_default = createRule({
21693
21761
  defaultOptions: [],
21694
21762
  create(context) {
21695
21763
  const sourceCode = context.sourceCode;
21764
+ const mayReferenceFile = sourceCode.text.includes("File") || sourceCode.text.includes("\\u");
21696
21765
  const program = sourceCode.ast;
21697
21766
  const moduleServer = !hasDirective(program.body, "use client") && (hasDirective(program.body, "use server") || program.body.some(serverImport));
21698
21767
  const fileNames = /* @__PURE__ */ new Set(["File"]);
@@ -21727,6 +21796,7 @@ var no_server_file_uploads_default = createRule({
21727
21796
  }
21728
21797
  return {
21729
21798
  "Program:exit"() {
21799
+ if (!mayReferenceFile) return;
21730
21800
  for (const scope of sourceCode.scopeManager?.scopes ?? []) {
21731
21801
  for (const reference of scope.references) {
21732
21802
  const identifier = reference.identifier;
@@ -21746,9 +21816,9 @@ var no_server_file_uploads_default = createRule({
21746
21816
  }
21747
21817
  },
21748
21818
  VariableDeclarator(node) {
21749
- if (node.id.type !== import_utils128.AST_NODE_TYPES.ObjectPattern || node.init?.type !== import_utils128.AST_NODE_TYPES.Identifier || !nativeNamespace2(node.init) || !inServer(node)) return;
21819
+ if (node.id.type !== import_utils128.AST_NODE_TYPES.ObjectPattern || node.init?.type !== import_utils128.AST_NODE_TYPES.Identifier) return;
21750
21820
  for (const property of node.id.properties) {
21751
- if (property.type === import_utils128.AST_NODE_TYPES.Property && staticPropertyName(property, sourceCode) === "File") {
21821
+ if (property.type === import_utils128.AST_NODE_TYPES.Property && staticPropertyName(property, sourceCode) === "File" && nativeNamespace2(node.init) && inServer(node)) {
21752
21822
  context.report({ node: property, messageId: "serverFile" });
21753
21823
  }
21754
21824
  }
@@ -23779,7 +23849,7 @@ function expectOperand(callee, sourceCode) {
23779
23849
  const negated = receiver.type === import_utils138.AST_NODE_TYPES.MemberExpression && import_utils138.ASTUtils.getPropertyName(receiver) === "not";
23780
23850
  if (negated && receiver.type === import_utils138.AST_NODE_TYPES.MemberExpression) receiver = unwrapExpression(receiver.object);
23781
23851
  const expectCallee = receiver.type === import_utils138.AST_NODE_TYPES.CallExpression ? unwrapExpression(receiver.callee) : null;
23782
- if (receiver.type !== import_utils138.AST_NODE_TYPES.CallExpression || expectCallee?.type !== import_utils138.AST_NODE_TYPES.Identifier || runtimeTestFrameworkName(sourceCode, expectCallee, "assertion") !== "expect" || receiver.arguments.length !== 1 && !hasStaticMessage(receiver, expectCallee, sourceCode)) {
23852
+ if (receiver.type !== import_utils138.AST_NODE_TYPES.CallExpression || expectCallee?.type !== import_utils138.AST_NODE_TYPES.Identifier || acceptedTestFrameworkName(sourceCode, expectCallee, "assertion", EXPECT_ASSERTION) === null || receiver.arguments.length !== 1 && !hasStaticMessage(receiver, expectCallee, sourceCode)) {
23783
23853
  return null;
23784
23854
  }
23785
23855
  const operand = receiver.arguments[0];
@@ -23807,6 +23877,7 @@ function isExceptionContract(node, source) {
23807
23877
  }
23808
23878
  function customizedMatchers(source) {
23809
23879
  const customized = /* @__PURE__ */ new Set();
23880
+ if (!source.text.includes("extend") && !source.text.includes("addEqualityTesters") && !source.text.includes("\\u")) return customized;
23810
23881
  const visit = (node) => {
23811
23882
  if (node.type === import_utils138.AST_NODE_TYPES.CallExpression) collectCustomization(node, source, customized);
23812
23883
  for (const key of source.visitorKeys[node.type] ?? []) {
@@ -23823,7 +23894,7 @@ function collectCustomization(node, source, customized) {
23823
23894
  const member3 = unwrapExpression(node.callee);
23824
23895
  if (member3.type !== import_utils138.AST_NODE_TYPES.MemberExpression) return;
23825
23896
  const receiver = unwrapExpression(member3.object);
23826
- if (receiver.type !== import_utils138.AST_NODE_TYPES.Identifier || runtimeTestFrameworkName(source, receiver, "assertion") !== "expect") return;
23897
+ if (receiver.type !== import_utils138.AST_NODE_TYPES.Identifier || acceptedTestFrameworkName(source, receiver, "assertion", EXPECT_ASSERTION) === null) return;
23827
23898
  const method = import_utils138.ASTUtils.getPropertyName(member3);
23828
23899
  const argument = node.arguments[0] === void 0 ? null : unwrapExpression(node.arguments[0]);
23829
23900
  if (method === "addEqualityTesters" && !(argument?.type === import_utils138.AST_NODE_TYPES.ArrayExpression && argument.elements.length === 0)) {
@@ -25936,8 +26007,8 @@ var test_loops_over_literal_cases_default = createRule({
25936
26007
  }
25937
26008
  return identifiers.has(identifier);
25938
26009
  };
25939
- const isFrameworkTest = (identifier) => TEST_CALLERS3.has(runtimeTestFrameworkName(context.sourceCode, identifier, "test") ?? "");
25940
- const isFrameworkAssertion = (identifier) => ASSERTION_ROOTS.has(runtimeTestFrameworkName(context.sourceCode, identifier, "assertion") ?? "");
26010
+ const isFrameworkTest = (identifier) => acceptedTestFrameworkName(context.sourceCode, identifier, "test", TEST_CALLERS3) !== null;
26011
+ const isFrameworkAssertion = (identifier) => acceptedTestFrameworkName(context.sourceCode, identifier, "assertion", ASSERTION_ROOTS) !== null;
25941
26012
  return {
25942
26013
  ForOfStatement(node) {
25943
26014
  const enclosing = nearestEnclosingFunction2(node);
@@ -33161,7 +33232,7 @@ var prefer_whole_object_assertion_default = createRule({
33161
33232
  return assertionExpectation(statement, call, receiver, key, matcher);
33162
33233
  }
33163
33234
  function isTestExpect(callee) {
33164
- return runtimeTestFrameworkName(sourceCode, callee, "assertion") === "expect";
33235
+ return acceptedTestFrameworkName(sourceCode, callee, "assertion", EXPECT_ASSERTION) !== null;
33165
33236
  }
33166
33237
  function isExactLengthAssertion(statement, receiver) {
33167
33238
  if (statement?.type !== import_utils181.AST_NODE_TYPES.ExpressionStatement || statement.expression.type !== import_utils181.AST_NODE_TYPES.CallExpression) return false;
@@ -37365,6 +37436,7 @@ var stepdown_default = createRule({
37365
37436
  "Program:exit": (program) => {
37366
37437
  declarationScope(context, program);
37367
37438
  for (const body3 of functionBodies) declarationScope(context, body3);
37439
+ if (classes.length === 0) return;
37368
37440
  const computedReferences = [];
37369
37441
  walk3(program, context.sourceCode.visitorKeys, (node) => {
37370
37442
  if (node.type === import_utils197.AST_NODE_TYPES.MemberExpression && node.computed)
package/dist/index.js CHANGED
@@ -156,8 +156,15 @@ function createNativeRule(config, url) {
156
156
  const defaults = metadataDefaults ?? legacyDefaults;
157
157
  if (defaults === void 0) throw new TypeError("Declared defaults disappeared while creating the rule.");
158
158
  const create = config.create;
159
+ const applied = /* @__PURE__ */ new WeakMap();
159
160
  return { ...base, create(context) {
160
- return create(context, ESLintUtils.applyDefault(defaults, context.options));
161
+ const options = context.options;
162
+ let merged = applied.get(options);
163
+ if (merged === void 0) {
164
+ merged = ESLintUtils.applyDefault(defaults, options);
165
+ applied.set(options, merged);
166
+ }
167
+ return create(context, merged);
161
168
  } };
162
169
  }
163
170
  function hasNoDefaultOptions(_config, metaDefaults, legacyDefaults) {
@@ -564,12 +571,17 @@ function isGeneratedFile(filename, sourceText = "", gates = []) {
564
571
  if (gates.includes("externalTree") && EXTERNAL_TREE_RE.test(normalized)) {
565
572
  return true;
566
573
  }
574
+ return generatedHeader(sourceText);
575
+ }
576
+ var lastHeaderText;
577
+ var lastHeaderGenerated = false;
578
+ function generatedHeader(sourceText) {
579
+ if (sourceText === lastHeaderText) return lastHeaderGenerated;
567
580
  const headerBodies = leadingCommentBodies(sourceText.slice(0, 2048));
568
581
  const nonAi = headerBodies.filter((body3) => !AI_ATTRIBUTION_RE.test(body3));
569
- if (nonAi.some((body3) => GENERATED_MARKER_RE.test(body3))) {
570
- return true;
571
- }
572
- return !headerBodies.some((body3) => AI_ATTRIBUTION_RE.test(body3)) && headerBodies.some((body3) => DO_NOT_EDIT_RE.test(body3));
582
+ lastHeaderGenerated = nonAi.some((body3) => GENERATED_MARKER_RE.test(body3)) || !headerBodies.some((body3) => AI_ATTRIBUTION_RE.test(body3)) && headerBodies.some((body3) => DO_NOT_EDIT_RE.test(body3));
583
+ lastHeaderText = sourceText;
584
+ return lastHeaderGenerated;
573
585
  }
574
586
  function leadingCommentBodies(sourceText) {
575
587
  const bodies = [];
@@ -610,6 +622,16 @@ function isScriptFile(filename) {
610
622
  }
611
623
  function scopedFilename(input) {
612
624
  if (typeof input === "string") return input.replaceAll("\\", "/");
625
+ if (input.filename === lastScopedFilename && input.cwd === lastScopedCwd) return lastScoped;
626
+ lastScoped = computeScopedFilename(input);
627
+ lastScopedFilename = input.filename;
628
+ lastScopedCwd = input.cwd;
629
+ return lastScoped;
630
+ }
631
+ var lastScopedFilename;
632
+ var lastScopedCwd;
633
+ var lastScoped = "";
634
+ function computeScopedFilename(input) {
613
635
  const filename = input.filename.replaceAll("\\", "/");
614
636
  const cwd = input.cwd.replaceAll("\\", "/");
615
637
  const fileWindows = windowsAbsolute(filename);
@@ -2257,8 +2279,18 @@ var FRONTEND_IMPORT = /^(?:react(?:-dom)?|next|preact|vue|svelte|@angular\/core|
2257
2279
  function isFrontendSource(context) {
2258
2280
  return /\.[jt]sx$/u.test(context.filename) || frontendModule(context.sourceCode.ast) || frontendPackage(context.filename);
2259
2281
  }
2282
+ var FRONTEND_PACKAGE_BY_DIRECTORY = /* @__PURE__ */ new Map();
2260
2283
  function frontendPackage(filename) {
2261
- let directory = dirname(resolve(filename));
2284
+ const directory = dirname(resolve(filename));
2285
+ let cached = FRONTEND_PACKAGE_BY_DIRECTORY.get(directory);
2286
+ if (cached === void 0) {
2287
+ cached = nearestManifestDeclaresFrontend(directory);
2288
+ FRONTEND_PACKAGE_BY_DIRECTORY.set(directory, cached);
2289
+ }
2290
+ return cached;
2291
+ }
2292
+ function nearestManifestDeclaresFrontend(start) {
2293
+ let directory = start;
2262
2294
  for (; ; ) {
2263
2295
  const manifestPath = join(directory, "package.json");
2264
2296
  if (existsSync(manifestPath)) {
@@ -3205,6 +3237,21 @@ function isUnshadowedTestMockGlobal(source, identifier) {
3205
3237
  return binding3 === null || binding3.defs.length === 0 && !binding3.references.some((reference) => reference.isWrite());
3206
3238
  }
3207
3239
  var TEST_FRAMEWORK_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
3240
+ function acceptedTestFrameworkName(source, identifier, kind, accepted) {
3241
+ if (!accepted.has(identifier.name) && !importedLocalNames(source.ast).has(identifier.name)) return null;
3242
+ const name = runtimeTestFrameworkName(source, identifier, kind);
3243
+ return name !== null && accepted.has(name) ? name : null;
3244
+ }
3245
+ var EXPECT_ASSERTION = /* @__PURE__ */ new Set(["expect"]);
3246
+ var IMPORTED_LOCAL_NAMES_BY_PROGRAM = /* @__PURE__ */ new WeakMap();
3247
+ function importedLocalNames(program) {
3248
+ let names = IMPORTED_LOCAL_NAMES_BY_PROGRAM.get(program);
3249
+ if (names === void 0) {
3250
+ names = new Set(program.body.flatMap((statement) => statement.type === AST_NODE_TYPES24.ImportDeclaration ? statement.specifiers.map((specifier) => specifier.local.name) : []));
3251
+ IMPORTED_LOCAL_NAMES_BY_PROGRAM.set(program, names);
3252
+ }
3253
+ return names;
3254
+ }
3208
3255
  function runtimeTestFrameworkName(source, identifier, kind) {
3209
3256
  const binding3 = ASTUtils12.findVariable(source.getScope(identifier), identifier.name);
3210
3257
  if (binding3 === null || binding3.defs.length === 0) {
@@ -6087,7 +6134,7 @@ function isSearchParamReceiver(call, services) {
6087
6134
  // package.json
6088
6135
  var package_default = {
6089
6136
  name: "@sarj/eslint-plugin",
6090
- version: "18.1.4",
6137
+ version: "18.1.5",
6091
6138
  packageManager: "npm@12.2.1",
6092
6139
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6093
6140
  type: "module",
@@ -11747,6 +11794,7 @@ var enforce_file_structure_default = createRule({
11747
11794
  // src/rules/duplicate-test-body.ts
11748
11795
  import { AST_NODE_TYPES as AST_NODE_TYPES73 } from "@typescript-eslint/utils";
11749
11796
  var TEST_CALLERS = /* @__PURE__ */ new Set(["it", "test"]);
11797
+ var ASSERTION_NAMES = /* @__PURE__ */ new Set(["assert", "expect"]);
11750
11798
  var TEST_MODIFIERS2 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
11751
11799
  var FUNCTION_TYPES = /* @__PURE__ */ new Set([
11752
11800
  AST_NODE_TYPES73.FunctionExpression,
@@ -11858,7 +11906,7 @@ function constAssertionLiteral(node) {
11858
11906
  function isAssertionCall(node, sourceCode) {
11859
11907
  const unwrappedNodeCallee = unwrapExpression(node.callee);
11860
11908
  const root = rootIdentifier(unwrappedNodeCallee);
11861
- return root !== null && ["assert", "expect"].includes(runtimeTestFrameworkName(sourceCode, root, "assertion") ?? "");
11909
+ return root !== null && acceptedTestFrameworkName(sourceCode, root, "assertion", ASSERTION_NAMES) !== null;
11862
11910
  }
11863
11911
  function rootIdentifier(callee) {
11864
11912
  callee = unwrapExpression(callee);
@@ -11892,7 +11940,16 @@ function normalizedLiteral(node) {
11892
11940
  function normalizedStringLiteral(value) {
11893
11941
  return value.includes("\n") || value.length > MAX_NORMALIZED_STRING_LENGTH ? ["Literal", "string", value] : ["Literal", "string"];
11894
11942
  }
11943
+ var CANDIDATE_BY_CALL = /* @__PURE__ */ new WeakMap();
11895
11944
  function duplicateTestBodyCandidate(call, sourceCode) {
11945
+ let candidate2 = CANDIDATE_BY_CALL.get(call);
11946
+ if (candidate2 === void 0) {
11947
+ candidate2 = computeDuplicateTestBodyCandidate(call, sourceCode);
11948
+ CANDIDATE_BY_CALL.set(call, candidate2);
11949
+ }
11950
+ return candidate2;
11951
+ }
11952
+ function computeDuplicateTestBodyCandidate(call, sourceCode) {
11896
11953
  if (call.parent?.type !== AST_NODE_TYPES73.ExpressionStatement) return null;
11897
11954
  const container = call.parent.parent;
11898
11955
  if (container?.type !== AST_NODE_TYPES73.Program && container?.type !== AST_NODE_TYPES73.BlockStatement) return null;
@@ -11934,8 +11991,7 @@ function testBody(call, sourceCode) {
11934
11991
  function testCallerSignature(callee, sourceCode) {
11935
11992
  callee = unwrapExpression(callee);
11936
11993
  if (callee.type === AST_NODE_TYPES73.Identifier) {
11937
- const name = runtimeTestFrameworkName(sourceCode, callee, "test");
11938
- return name !== null && TEST_CALLERS.has(name) ? name : null;
11994
+ return acceptedTestFrameworkName(sourceCode, callee, "test", TEST_CALLERS);
11939
11995
  }
11940
11996
  if (callee.type === AST_NODE_TYPES73.MemberExpression) {
11941
11997
  const base = testCallerSignature(callee.object, sourceCode);
@@ -16197,8 +16253,14 @@ var no_exception_text_in_5xx_response_default = createRule({
16197
16253
  const candidates = [];
16198
16254
  const changedErrors = /* @__PURE__ */ new Set();
16199
16255
  const stableBindings = /* @__PURE__ */ new WeakMap();
16200
- const referenceIdentifiers = new WeakSet(sourceCode.scopeManager?.scopes.flatMap((scope) => scope.references.filter((reference) => reference.isValueReference !== false).map((reference) => reference.identifier)) ?? []);
16256
+ let referenceIdentifiers;
16201
16257
  let uncertainSerialization = false;
16258
+ const catchNames = /* @__PURE__ */ new Set();
16259
+ const responseNames = /* @__PURE__ */ new Set(["Response", ...nextResponseLocals(sourceCode.ast)]);
16260
+ function valueReference(node) {
16261
+ referenceIdentifiers ??= new WeakSet(sourceCode.scopeManager?.scopes.flatMap((scope) => scope.references.filter((reference) => reference.isValueReference !== false).map((reference) => reference.identifier)) ?? []);
16262
+ return referenceIdentifiers.has(node);
16263
+ }
16202
16264
  function binding3(identifier) {
16203
16265
  return ASTUtils55.findVariable(sourceCode.getScope(identifier), identifier.name);
16204
16266
  }
@@ -16217,14 +16279,14 @@ var no_exception_text_in_5xx_response_default = createRule({
16217
16279
  }
16218
16280
  function caughtValue(node) {
16219
16281
  const value = unwrapExpression(node);
16220
- if (value.type !== AST_NODE_TYPES91.Identifier) return null;
16282
+ if (value.type !== AST_NODE_TYPES91.Identifier || !catchNames.has(value.name)) return null;
16221
16283
  const variable = binding3(value);
16222
16284
  const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
16223
16285
  return definition?.type === "CatchClause" && definition.node.param?.type === AST_NODE_TYPES91.Identifier && variable !== null && stable2(variable) ? variable : null;
16224
16286
  }
16225
16287
  function responseFactory(node) {
16226
16288
  const value = unwrapExpression(node);
16227
- if (value.type !== AST_NODE_TYPES91.Identifier) return false;
16289
+ if (value.type !== AST_NODE_TYPES91.Identifier || !responseNames.has(value.name)) return false;
16228
16290
  const variable = binding3(value);
16229
16291
  if (value.name === "Response" && (variable === null || variable.defs.length === 0)) {
16230
16292
  return variable === null || stable2(variable);
@@ -16307,8 +16369,11 @@ var no_exception_text_in_5xx_response_default = createRule({
16307
16369
  if (error !== null) changedErrors.add(error);
16308
16370
  }
16309
16371
  return {
16372
+ CatchClause(node) {
16373
+ if (node.param?.type === AST_NODE_TYPES91.Identifier) catchNames.add(node.param.name);
16374
+ },
16310
16375
  Identifier(node) {
16311
- if (!referenceIdentifiers.has(node)) return;
16376
+ if (!catchNames.has(node.name) && !responseNames.has(node.name) || !valueReference(node)) return;
16312
16377
  let typeName = node;
16313
16378
  while (typeName.parent?.type === AST_NODE_TYPES91.TSQualifiedName) typeName = typeName.parent;
16314
16379
  if (typeName.parent?.type === AST_NODE_TYPES91.TSTypeQuery) return;
@@ -16364,6 +16429,9 @@ var no_exception_text_in_5xx_response_default = createRule({
16364
16429
  };
16365
16430
  }
16366
16431
  });
16432
+ function nextResponseLocals(program) {
16433
+ return program.body.flatMap((statement) => statement.type === AST_NODE_TYPES91.ImportDeclaration && statement.source.value === "next/server" ? statement.specifiers.flatMap((specifier) => specifier.type === AST_NODE_TYPES91.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES91.Identifier && specifier.imported.name === "NextResponse" ? [specifier.local.name] : []) : []);
16434
+ }
16367
16435
 
16368
16436
  // src/rules/no-json-stringify-error.ts
16369
16437
  import { ASTUtils as ASTUtils56 } from "@typescript-eslint/utils";
@@ -18463,7 +18531,7 @@ var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
18463
18531
  var TEST_NAMES = /* @__PURE__ */ new Set(["it", "test"]);
18464
18532
  var TEST_MODIFIERS3 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
18465
18533
  var TEST_BUILDERS = /* @__PURE__ */ new Set(["each", "for", "runIf", "skipIf"]);
18466
- var ASSERTION_NAMES = /* @__PURE__ */ new Set(["assert", "assertType", "expect", "expectTypeOf"]);
18534
+ var ASSERTION_NAMES2 = /* @__PURE__ */ new Set(["assert", "assertType", "expect", "expectTypeOf"]);
18467
18535
  var FUNCTION_TYPES3 = /* @__PURE__ */ new Set([AST_NODE_TYPES102.ArrowFunctionExpression, AST_NODE_TYPES102.FunctionExpression, AST_NODE_TYPES102.FunctionDeclaration]);
18468
18536
  function staticMemberName3(node) {
18469
18537
  if (!node.computed && node.property.type === AST_NODE_TYPES102.Identifier) return node.property.name;
@@ -18481,7 +18549,7 @@ function isDirectTestCallback(node, context) {
18481
18549
  const call = directArgumentCall(node);
18482
18550
  if (call === null) return false;
18483
18551
  const root = testRoot(call.callee);
18484
- return root !== null && TEST_NAMES.has(runtimeTestFrameworkName(context.sourceCode, root, "test") ?? "");
18552
+ return root !== null && acceptedTestFrameworkName(context.sourceCode, root, "test", TEST_NAMES) !== null;
18485
18553
  }
18486
18554
  function testRoot(callee) {
18487
18555
  callee = unwrapExpression(callee);
@@ -18507,13 +18575,13 @@ function walkOwnScope(node, predicate) {
18507
18575
  function isAssertion(node, context) {
18508
18576
  if (node.type !== AST_NODE_TYPES102.CallExpression) return false;
18509
18577
  const root = rootIdentifier2(node.callee);
18510
- return root !== null && ASSERTION_NAMES.has(runtimeTestFrameworkName(context.sourceCode, root, "assertion") ?? "");
18578
+ return root !== null && acceptedTestFrameworkName(context.sourceCode, root, "assertion", ASSERTION_NAMES2) !== null;
18511
18579
  }
18512
18580
  function isExplicitSkip(node, context) {
18513
18581
  const unwrappedNodeCallee = node.type === "CallExpression" || node.type === "NewExpression" ? unwrapExpression(node.callee) : null;
18514
18582
  if (node.type !== AST_NODE_TYPES102.CallExpression || unwrappedNodeCallee?.type !== AST_NODE_TYPES102.MemberExpression || staticMemberName3(unwrappedNodeCallee) !== "skip") return false;
18515
18583
  const root = rootIdentifier2(unwrappedNodeCallee.object);
18516
- return root !== null && TEST_NAMES.has(runtimeTestFrameworkName(context.sourceCode, root, "test") ?? "");
18584
+ return root !== null && acceptedTestFrameworkName(context.sourceCode, root, "test", TEST_NAMES) !== null;
18517
18585
  }
18518
18586
  function isUnconditionalContextSkip(node, owner, context) {
18519
18587
  if (node.type !== AST_NODE_TYPES102.CallExpression || node.optional) return false;
@@ -21669,6 +21737,7 @@ var no_server_file_uploads_default = createRule({
21669
21737
  defaultOptions: [],
21670
21738
  create(context) {
21671
21739
  const sourceCode = context.sourceCode;
21740
+ const mayReferenceFile = sourceCode.text.includes("File") || sourceCode.text.includes("\\u");
21672
21741
  const program = sourceCode.ast;
21673
21742
  const moduleServer = !hasDirective(program.body, "use client") && (hasDirective(program.body, "use server") || program.body.some(serverImport));
21674
21743
  const fileNames = /* @__PURE__ */ new Set(["File"]);
@@ -21703,6 +21772,7 @@ var no_server_file_uploads_default = createRule({
21703
21772
  }
21704
21773
  return {
21705
21774
  "Program:exit"() {
21775
+ if (!mayReferenceFile) return;
21706
21776
  for (const scope of sourceCode.scopeManager?.scopes ?? []) {
21707
21777
  for (const reference of scope.references) {
21708
21778
  const identifier = reference.identifier;
@@ -21722,9 +21792,9 @@ var no_server_file_uploads_default = createRule({
21722
21792
  }
21723
21793
  },
21724
21794
  VariableDeclarator(node) {
21725
- if (node.id.type !== AST_NODE_TYPES116.ObjectPattern || node.init?.type !== AST_NODE_TYPES116.Identifier || !nativeNamespace2(node.init) || !inServer(node)) return;
21795
+ if (node.id.type !== AST_NODE_TYPES116.ObjectPattern || node.init?.type !== AST_NODE_TYPES116.Identifier) return;
21726
21796
  for (const property of node.id.properties) {
21727
- if (property.type === AST_NODE_TYPES116.Property && staticPropertyName(property, sourceCode) === "File") {
21797
+ if (property.type === AST_NODE_TYPES116.Property && staticPropertyName(property, sourceCode) === "File" && nativeNamespace2(node.init) && inServer(node)) {
21728
21798
  context.report({ node: property, messageId: "serverFile" });
21729
21799
  }
21730
21800
  }
@@ -23755,7 +23825,7 @@ function expectOperand(callee, sourceCode) {
23755
23825
  const negated = receiver.type === AST_NODE_TYPES124.MemberExpression && ASTUtils82.getPropertyName(receiver) === "not";
23756
23826
  if (negated && receiver.type === AST_NODE_TYPES124.MemberExpression) receiver = unwrapExpression(receiver.object);
23757
23827
  const expectCallee = receiver.type === AST_NODE_TYPES124.CallExpression ? unwrapExpression(receiver.callee) : null;
23758
- if (receiver.type !== AST_NODE_TYPES124.CallExpression || expectCallee?.type !== AST_NODE_TYPES124.Identifier || runtimeTestFrameworkName(sourceCode, expectCallee, "assertion") !== "expect" || receiver.arguments.length !== 1 && !hasStaticMessage(receiver, expectCallee, sourceCode)) {
23828
+ if (receiver.type !== AST_NODE_TYPES124.CallExpression || expectCallee?.type !== AST_NODE_TYPES124.Identifier || acceptedTestFrameworkName(sourceCode, expectCallee, "assertion", EXPECT_ASSERTION) === null || receiver.arguments.length !== 1 && !hasStaticMessage(receiver, expectCallee, sourceCode)) {
23759
23829
  return null;
23760
23830
  }
23761
23831
  const operand = receiver.arguments[0];
@@ -23783,6 +23853,7 @@ function isExceptionContract(node, source) {
23783
23853
  }
23784
23854
  function customizedMatchers(source) {
23785
23855
  const customized = /* @__PURE__ */ new Set();
23856
+ if (!source.text.includes("extend") && !source.text.includes("addEqualityTesters") && !source.text.includes("\\u")) return customized;
23786
23857
  const visit = (node) => {
23787
23858
  if (node.type === AST_NODE_TYPES124.CallExpression) collectCustomization(node, source, customized);
23788
23859
  for (const key of source.visitorKeys[node.type] ?? []) {
@@ -23799,7 +23870,7 @@ function collectCustomization(node, source, customized) {
23799
23870
  const member3 = unwrapExpression(node.callee);
23800
23871
  if (member3.type !== AST_NODE_TYPES124.MemberExpression) return;
23801
23872
  const receiver = unwrapExpression(member3.object);
23802
- if (receiver.type !== AST_NODE_TYPES124.Identifier || runtimeTestFrameworkName(source, receiver, "assertion") !== "expect") return;
23873
+ if (receiver.type !== AST_NODE_TYPES124.Identifier || acceptedTestFrameworkName(source, receiver, "assertion", EXPECT_ASSERTION) === null) return;
23803
23874
  const method = ASTUtils82.getPropertyName(member3);
23804
23875
  const argument = node.arguments[0] === void 0 ? null : unwrapExpression(node.arguments[0]);
23805
23876
  if (method === "addEqualityTesters" && !(argument?.type === AST_NODE_TYPES124.ArrayExpression && argument.elements.length === 0)) {
@@ -25914,8 +25985,8 @@ var test_loops_over_literal_cases_default = createRule({
25914
25985
  }
25915
25986
  return identifiers.has(identifier);
25916
25987
  };
25917
- const isFrameworkTest = (identifier) => TEST_CALLERS3.has(runtimeTestFrameworkName(context.sourceCode, identifier, "test") ?? "");
25918
- const isFrameworkAssertion = (identifier) => ASSERTION_ROOTS.has(runtimeTestFrameworkName(context.sourceCode, identifier, "assertion") ?? "");
25988
+ const isFrameworkTest = (identifier) => acceptedTestFrameworkName(context.sourceCode, identifier, "test", TEST_CALLERS3) !== null;
25989
+ const isFrameworkAssertion = (identifier) => acceptedTestFrameworkName(context.sourceCode, identifier, "assertion", ASSERTION_ROOTS) !== null;
25919
25990
  return {
25920
25991
  ForOfStatement(node) {
25921
25992
  const enclosing = nearestEnclosingFunction2(node);
@@ -33156,7 +33227,7 @@ var prefer_whole_object_assertion_default = createRule({
33156
33227
  return assertionExpectation(statement, call, receiver, key, matcher);
33157
33228
  }
33158
33229
  function isTestExpect(callee) {
33159
- return runtimeTestFrameworkName(sourceCode, callee, "assertion") === "expect";
33230
+ return acceptedTestFrameworkName(sourceCode, callee, "assertion", EXPECT_ASSERTION) !== null;
33160
33231
  }
33161
33232
  function isExactLengthAssertion(statement, receiver) {
33162
33233
  if (statement?.type !== AST_NODE_TYPES164.ExpressionStatement || statement.expression.type !== AST_NODE_TYPES164.CallExpression) return false;
@@ -37366,6 +37437,7 @@ var stepdown_default = createRule({
37366
37437
  "Program:exit": (program) => {
37367
37438
  declarationScope(context, program);
37368
37439
  for (const body3 of functionBodies) declarationScope(context, body3);
37440
+ if (classes.length === 0) return;
37369
37441
  const computedReferences = [];
37370
37442
  walk3(program, context.sourceCode.visitorKeys, (node) => {
37371
37443
  if (node.type === AST_NODE_TYPES178.MemberExpression && node.computed)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "18.1.4",
3
+ "version": "18.1.5",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",