@sarj/eslint-plugin 16.30.0 → 16.30.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1580,7 +1580,7 @@ function isSearchParamReceiver(call, services) {
1580
1580
  // package.json
1581
1581
  var package_default = {
1582
1582
  name: "@sarj/eslint-plugin",
1583
- version: "16.30.0",
1583
+ version: "16.30.4",
1584
1584
  packageManager: "npm@12.2.1",
1585
1585
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
1586
1586
  type: "module",
@@ -1636,7 +1636,6 @@ var package_default = {
1636
1636
  dependencies: {
1637
1637
  "@typescript-eslint/typescript-estree": "^8.72.0",
1638
1638
  "@typescript-eslint/utils": "^8.72.0",
1639
- "eslint-plugin-sonarjs": "4.2.2",
1640
1639
  "jsonc-parser": "^3.3.1",
1641
1640
  "ts-api-utils": "2.5.0"
1642
1641
  },
@@ -4857,7 +4856,6 @@ function showWitness(value) {
4857
4856
 
4858
4857
  // src/rules/no-boolean-selector-parameter.ts
4859
4858
  var import_utils32 = require("@typescript-eslint/utils");
4860
- var import_eslint_plugin_sonarjs = require("eslint-plugin-sonarjs");
4861
4859
 
4862
4860
  // src/rules/_boolean-selector-contract.ts
4863
4861
  var import_utils30 = require("@typescript-eslint/utils");
@@ -5182,7 +5180,6 @@ function literalCallers(sourceCode, owner, binding) {
5182
5180
  }
5183
5181
 
5184
5182
  // src/rules/no-boolean-selector-parameter.ts
5185
- var UPSTREAM_SELECTOR_RULE = import_eslint_plugin_sonarjs.rules["no-selector-parameter"];
5186
5183
  var NO_BOOLEAN_SELECTOR_PARAMETER_DOCUMENTATION = {
5187
5184
  summary: "Prefer intention-revealing operations over a boolean parameter selecting between actions.",
5188
5185
  rationale: "A positional boolean hides which operation a caller requests. Splitting independent actions can make that intent explicit, while boolean data, pure mappings and externally required callback or method contracts remain valid.",
@@ -5191,15 +5188,14 @@ var NO_BOOLEAN_SELECTOR_PARAMETER_DOCUMENTATION = {
5191
5188
  defaultLevel: "warning",
5192
5189
  autofix: "safe",
5193
5190
  limitations: [
5194
- "Requires type information. Adapts maintained SonarJS S2301 for single-statement if/else or conditional bodies and adds expression-bodied arrow conditionals. Only a direct broad-boolean parameter or its negation is considered; optional, nullable, literal-only, generic, destructured and indirect predicate conditions are excluded.",
5191
+ "Requires type information. Checks an owned single-statement if/else, returned conditional or conditional expression statement, and expression-bodied arrow conditionals. Only a direct broad-boolean parameter or its negation is considered; optional, nullable, literal-only, generic, destructured and indirect predicate conditions are excluded.",
5195
5192
  "Preserves setters, pure mappings, selected pure data passed to one stable lexical operation or assigned to one simple target, independently authored callable and inherited member contracts, inline callbacks, and statically proven named callback references. Independent helpers remain checked in JSX files; callback identity is established from references rather than handler names.",
5196
- "The upstream block-body detector still exempts literal on[A-Z] object properties; the concise-arrow extension does not infer a callback contract from that name. Multi-statement bodies, short-circuit guards, local parameter aliases and arbitrary control flow are not inferred. Noncomputed property reads count as data without proving getter effects; computed reads remain outside that exemption, including literal keys. Side-effect detection does not prove whether a called function is pure, and a warning requires manual design review. Generated files are excluded; tests remain checked.",
5193
+ "The block-body detector preserves the existing literal on[A-Z] object-property exemption; the concise-arrow extension does not infer a callback contract from that name. Multi-statement bodies, short-circuit guards, local parameter aliases and arbitrary control flow are not inferred. Noncomputed property reads count as data without proving getter effects; computed reads remain outside that exemption, including literal keys. Side-effect detection does not prove whether a called function is pure, and a warning requires manual design review. Generated files are excluded; tests remain checked.",
5197
5194
  "Automatic fixes only reuse two already authored private zero-argument ordinary operations returning primitive string literals. A complete comment-free four-declaration module must contain those operations, one private const broad-boolean arrow selecting their direct calls, and one exported zero-argument ordinary function with an explicit string-array return that calls the selector exclusively with literal true and false. Calls are routed to the existing named operations and the exclusively used selector declaration is removed. All other APIs, callbacks, dynamic arguments, contracts and operation bodies remain manual."
5198
5195
  ],
5199
5196
  references: [
5200
5197
  "https://understandlegacycode.com/blog/what-is-wrong-with-boolean-parameters/",
5201
- "https://martinfowler.com/bliki/FlagArgument.html",
5202
- "https://github.com/SonarSource/SonarJS/tree/master/packages/analysis/src/jsts/rules/S2301"
5198
+ "https://martinfowler.com/bliki/FlagArgument.html"
5203
5199
  ],
5204
5200
  examples: [
5205
5201
  {
@@ -5289,38 +5285,26 @@ var no_boolean_selector_parameter_default = createRule({
5289
5285
  fix
5290
5286
  });
5291
5287
  }
5292
- function translate({ loc }) {
5293
- if (loc === void 0) return;
5294
- const position = "start" in loc ? loc.start : loc;
5295
- const identifier = context.sourceCode.getNodeByRangeIndex(
5296
- context.sourceCode.getIndexFromLoc(position)
5297
- );
5298
- if (identifier?.type === import_utils32.AST_NODE_TYPES.Identifier) report2(identifier);
5299
- }
5300
- const nativeContext = Object.create(context, {
5301
- report: { value: translate }
5302
- });
5303
- const nativeArguments = [nativeContext];
5304
- const listeners = Reflect.apply(
5305
- UPSTREAM_SELECTOR_RULE.create.bind(UPSTREAM_SELECTOR_RULE),
5306
- void 0,
5307
- nativeArguments
5308
- );
5309
- const native = {};
5310
- if (typeof listeners !== "object" || listeners === null) return native;
5311
- for (const selector of Object.keys(listeners)) {
5312
- const listener = Reflect.get(listeners, selector);
5313
- if (typeof listener === "function")
5314
- native[selector] = (...nodes) => {
5315
- Reflect.apply(listener, void 0, nodes);
5316
- };
5288
+ function inspectSingleStatementBody(owner) {
5289
+ if (owner.body?.type !== import_utils32.AST_NODE_TYPES.BlockStatement || owner.body.body.length !== 1 || preservesBlockObjectCallback(owner)) return;
5290
+ const statement = owner.body.body[0];
5291
+ if (statement?.type === import_utils32.AST_NODE_TYPES.IfStatement) {
5292
+ if (statement.alternate === null || !import_utils32.ASTUtils.hasSideEffect(statement.consequent, context.sourceCode) && !import_utils32.ASTUtils.hasSideEffect(statement.alternate, context.sourceCode)) return;
5293
+ const identifier2 = testIdentifier(statement.test);
5294
+ if (identifier2 !== void 0) report2(identifier2);
5295
+ return;
5296
+ }
5297
+ const selected = statement?.type === import_utils32.AST_NODE_TYPES.ReturnStatement ? statement.argument : statement?.type === import_utils32.AST_NODE_TYPES.ExpressionStatement ? statement.expression : void 0;
5298
+ if (selected?.type !== import_utils32.AST_NODE_TYPES.ConditionalExpression || !import_utils32.ASTUtils.hasSideEffect(selected.consequent, context.sourceCode) && !import_utils32.ASTUtils.hasSideEffect(selected.alternate, context.sourceCode)) return;
5299
+ const identifier = testIdentifier(selected.test);
5300
+ if (identifier !== void 0) report2(identifier);
5317
5301
  }
5318
5302
  return {
5319
- ...native,
5303
+ FunctionDeclaration: inspectSingleStatementBody,
5304
+ FunctionExpression: inspectSingleStatementBody,
5305
+ ArrowFunctionExpression: inspectSingleStatementBody,
5320
5306
  ConditionalExpression(node) {
5321
- native.ConditionalExpression?.(node);
5322
- if (node.parent.type !== import_utils32.AST_NODE_TYPES.ArrowFunctionExpression || node.parent.body !== node || !import_utils32.ASTUtils.hasSideEffect(node.consequent, context.sourceCode) && !import_utils32.ASTUtils.hasSideEffect(node.alternate, context.sourceCode))
5323
- return;
5307
+ if (node.parent.type !== import_utils32.AST_NODE_TYPES.ArrowFunctionExpression || node.parent.body !== node || !import_utils32.ASTUtils.hasSideEffect(node.consequent, context.sourceCode) && !import_utils32.ASTUtils.hasSideEffect(node.alternate, context.sourceCode)) return;
5324
5308
  const callable = node.parent;
5325
5309
  if (callable.parent.type === import_utils32.AST_NODE_TYPES.CallExpression && callable.parent.arguments.includes(callable) || callable.parent.type === import_utils32.AST_NODE_TYPES.JSXExpressionContainer)
5326
5310
  return;
@@ -5330,6 +5314,13 @@ var no_boolean_selector_parameter_default = createRule({
5330
5314
  };
5331
5315
  }
5332
5316
  });
5317
+ function preservesBlockObjectCallback(owner) {
5318
+ const property = owner.parent;
5319
+ if (property.type !== import_utils32.AST_NODE_TYPES.Property || property.computed) return false;
5320
+ const key = property.key;
5321
+ const name = key.type === import_utils32.AST_NODE_TYPES.Identifier ? key.name : key.type === import_utils32.AST_NODE_TYPES.Literal && typeof key.value === "string" ? key.value : void 0;
5322
+ return name !== void 0 && /^on[A-Z]/u.test(name);
5323
+ }
5333
5324
  function testIdentifier(test) {
5334
5325
  const value = test.type === import_utils32.AST_NODE_TYPES.UnaryExpression && test.operator === "!" ? test.argument : test;
5335
5326
  return value.type === import_utils32.AST_NODE_TYPES.Identifier ? value : void 0;
@@ -32282,7 +32273,7 @@ var SOURCE_COUPLED_TEST_DOCUMENTATION = {
32282
32273
  "The rule follows stable lexical bindings, static source paths including JSX/TSX, CSS and HTML, awaited reads, and common text operations. Named imports of node:path/path join and resolve are followed when their complete trailing static path proves a source suffix. Reassigned bindings, dynamic trailing segments, namespace path wrappers, unknown path wrappers, iterator pipelines, and interprocedural flows remain unreported.",
32283
32274
  "Assertion roots are scope-resolved for supported test runners and Node assert imports; local or unknown helpers with assertion-like names are not inferred.",
32284
32275
  "Known fixture, golden, snapshot, and generated source paths are representation contracts and remain unreported; dot segments in the known path are normalized first.",
32285
- "Contained static paths beneath a stable native mkdtemp/mkdtempSync result are generated artifacts. Literal replace/replaceAll nonidentity guards comparing the same raw read remain unreported; fixed-string comparisons remain candidates.",
32276
+ "Contained static paths, including stable nested join/resolve descendants, beneath a native mkdtemp/mkdtempSync result are generated artifacts. Absolute or drive-anchored resolve segments and traversal above the proven directory remain unproven. Literal replace/replaceAll nonidentity guards comparing the same raw read remain unreported; fixed-string comparisons remain candidates.",
32286
32277
  "When raw representation is genuinely the contract (for example a golden or compatibility sentinel), use an exact line suppression with the reason."
32287
32278
  ],
32288
32279
  examples: [
@@ -32325,6 +32316,16 @@ function stringValue(node) {
32325
32316
  }
32326
32317
  return null;
32327
32318
  }
32319
+ function relativePathDepth(normalized, initialDepth) {
32320
+ let relativeDepth = initialDepth;
32321
+ for (const part of normalized.split("/")) {
32322
+ if (part === "..") {
32323
+ if (relativeDepth === 0) return null;
32324
+ relativeDepth -= 1;
32325
+ } else if (part !== "" && part !== ".") relativeDepth += 1;
32326
+ }
32327
+ return relativeDepth;
32328
+ }
32328
32329
  function staticPathSuffix(pathArguments, builder) {
32329
32330
  const trailing = [];
32330
32331
  for (const argument of pathArguments.toReversed()) {
@@ -32450,20 +32451,27 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
32450
32451
  }
32451
32452
  if (current.type !== import_utils169.AST_NODE_TYPES.CallExpression) return false;
32452
32453
  const callee = unwrap6(current.callee);
32453
- return callee.type === import_utils169.AST_NODE_TYPES.Identifier && ["mkdtemp", "mkdtempSync"].includes(nativeImportedName(callee, FS_MODULES) ?? "");
32454
+ if (callee.type !== import_utils169.AST_NODE_TYPES.Identifier) return false;
32455
+ if (["mkdtemp", "mkdtempSync"].includes(nativeImportedName(callee, FS_MODULES) ?? "")) return true;
32456
+ const builder = nativePathBuilder(callee);
32457
+ return builder !== null && temporaryDescendant(current, builder, depth + 1);
32454
32458
  };
32455
- const temporaryDescendant = (node, builder) => {
32459
+ const temporaryDescendant = (node, builder, depth = 0) => {
32460
+ if (depth > 8) return false;
32456
32461
  const [directory, ...segments] = node.arguments;
32457
- if (directory === void 0 || directory.type === import_utils169.AST_NODE_TYPES.SpreadElement || !temporaryDirectory(directory)) return false;
32458
- const values = [];
32462
+ if (directory === void 0 || directory.type === import_utils169.AST_NODE_TYPES.SpreadElement || !temporaryDirectory(directory, depth)) return false;
32463
+ let relativeDepth = 0;
32459
32464
  for (const segment of segments) {
32460
32465
  if (segment.type === import_utils169.AST_NODE_TYPES.SpreadElement) return false;
32461
32466
  const value = stringValue(segment);
32462
32467
  if (value === null) return false;
32463
- values.push(value.replaceAll("\\", "/"));
32468
+ const normalized = value.replaceAll("\\", "/");
32469
+ if (builder === "resolve" && (import_node_path3.posix.isAbsolute(normalized) || import_node_path3.win32.parse(normalized).root !== "")) return false;
32470
+ const nextDepth = relativePathDepth(normalized, relativeDepth);
32471
+ if (nextDepth === null) return false;
32472
+ relativeDepth = nextDepth;
32464
32473
  }
32465
- const sentinel = "/__source_oracle_temporary_directory__";
32466
- return import_node_path3.posix[builder](sentinel, ...values).startsWith(`${sentinel}/`);
32474
+ return relativeDepth > 0;
32467
32475
  };
32468
32476
  const sourcePathValue = (value) => {
32469
32477
  const normalized = import_node_path3.posix.normalize(value.replaceAll("\\", "/"));
package/dist/index.d.cts CHANGED
@@ -1162,5 +1162,5 @@ declare const PLUGIN: {
1162
1162
  };
1163
1163
  };
1164
1164
 
1165
- export { ADVISORY_RULES, APPLICATION_ONLY_RULES, RECOMMENDED_RULES, RENAMED_RULES, RETIRED_RULES, RULES, STRICT_RULES, ADVISORY_RULES as advisoryRules, APPLICATION_ONLY_RULES as applicationOnlyRules, PLUGIN as default, RECOMMENDED_RULES as recommendedRules, RENAMED_RULES as renamedRules, RETIRED_RULES as retiredRules, RULES as rules, STRICT_RULES as strictRules };
1165
+ export { ADVISORY_RULES, APPLICATION_ONLY_RULES, DocumentedRule, RECOMMENDED_RULES, RENAMED_RULES, RETIRED_RULES, RULES, STRICT_RULES, ADVISORY_RULES as advisoryRules, APPLICATION_ONLY_RULES as applicationOnlyRules, PLUGIN as default, RECOMMENDED_RULES as recommendedRules, RENAMED_RULES as renamedRules, RETIRED_RULES as retiredRules, RULES as rules, STRICT_RULES as strictRules };
1166
1166
  export type { RetiredRule };
package/dist/index.d.ts CHANGED
@@ -1162,5 +1162,5 @@ declare const PLUGIN: {
1162
1162
  };
1163
1163
  };
1164
1164
 
1165
- export { ADVISORY_RULES, APPLICATION_ONLY_RULES, RECOMMENDED_RULES, RENAMED_RULES, RETIRED_RULES, RULES, STRICT_RULES, ADVISORY_RULES as advisoryRules, APPLICATION_ONLY_RULES as applicationOnlyRules, PLUGIN as default, RECOMMENDED_RULES as recommendedRules, RENAMED_RULES as renamedRules, RETIRED_RULES as retiredRules, RULES as rules, STRICT_RULES as strictRules };
1165
+ export { ADVISORY_RULES, APPLICATION_ONLY_RULES, DocumentedRule, RECOMMENDED_RULES, RENAMED_RULES, RETIRED_RULES, RULES, STRICT_RULES, ADVISORY_RULES as advisoryRules, APPLICATION_ONLY_RULES as applicationOnlyRules, PLUGIN as default, RECOMMENDED_RULES as recommendedRules, RENAMED_RULES as renamedRules, RETIRED_RULES as retiredRules, RULES as rules, STRICT_RULES as strictRules };
1166
1166
  export type { RetiredRule };
package/dist/index.js CHANGED
@@ -1533,7 +1533,7 @@ function isSearchParamReceiver(call, services) {
1533
1533
  // package.json
1534
1534
  var package_default = {
1535
1535
  name: "@sarj/eslint-plugin",
1536
- version: "16.30.0",
1536
+ version: "16.30.4",
1537
1537
  packageManager: "npm@12.2.1",
1538
1538
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
1539
1539
  type: "module",
@@ -1589,7 +1589,6 @@ var package_default = {
1589
1589
  dependencies: {
1590
1590
  "@typescript-eslint/typescript-estree": "^8.72.0",
1591
1591
  "@typescript-eslint/utils": "^8.72.0",
1592
- "eslint-plugin-sonarjs": "4.2.2",
1593
1592
  "jsonc-parser": "^3.3.1",
1594
1593
  "ts-api-utils": "2.5.0"
1595
1594
  },
@@ -4835,12 +4834,7 @@ function showWitness(value) {
4835
4834
  }
4836
4835
 
4837
4836
  // src/rules/no-boolean-selector-parameter.ts
4838
- import {
4839
- AST_NODE_TYPES as AST_NODE_TYPES30,
4840
- ASTUtils as ASTUtils16,
4841
- ESLintUtils as ESLintUtils14
4842
- } from "@typescript-eslint/utils";
4843
- import { rules as sonarRules } from "eslint-plugin-sonarjs";
4837
+ import { AST_NODE_TYPES as AST_NODE_TYPES30, ASTUtils as ASTUtils16, ESLintUtils as ESLintUtils14 } from "@typescript-eslint/utils";
4844
4838
 
4845
4839
  // src/rules/_boolean-selector-contract.ts
4846
4840
  import { AST_NODE_TYPES as AST_NODE_TYPES28, ASTUtils as ASTUtils14 } from "@typescript-eslint/utils";
@@ -5168,7 +5162,6 @@ function literalCallers(sourceCode, owner, binding) {
5168
5162
  }
5169
5163
 
5170
5164
  // src/rules/no-boolean-selector-parameter.ts
5171
- var UPSTREAM_SELECTOR_RULE = sonarRules["no-selector-parameter"];
5172
5165
  var NO_BOOLEAN_SELECTOR_PARAMETER_DOCUMENTATION = {
5173
5166
  summary: "Prefer intention-revealing operations over a boolean parameter selecting between actions.",
5174
5167
  rationale: "A positional boolean hides which operation a caller requests. Splitting independent actions can make that intent explicit, while boolean data, pure mappings and externally required callback or method contracts remain valid.",
@@ -5177,15 +5170,14 @@ var NO_BOOLEAN_SELECTOR_PARAMETER_DOCUMENTATION = {
5177
5170
  defaultLevel: "warning",
5178
5171
  autofix: "safe",
5179
5172
  limitations: [
5180
- "Requires type information. Adapts maintained SonarJS S2301 for single-statement if/else or conditional bodies and adds expression-bodied arrow conditionals. Only a direct broad-boolean parameter or its negation is considered; optional, nullable, literal-only, generic, destructured and indirect predicate conditions are excluded.",
5173
+ "Requires type information. Checks an owned single-statement if/else, returned conditional or conditional expression statement, and expression-bodied arrow conditionals. Only a direct broad-boolean parameter or its negation is considered; optional, nullable, literal-only, generic, destructured and indirect predicate conditions are excluded.",
5181
5174
  "Preserves setters, pure mappings, selected pure data passed to one stable lexical operation or assigned to one simple target, independently authored callable and inherited member contracts, inline callbacks, and statically proven named callback references. Independent helpers remain checked in JSX files; callback identity is established from references rather than handler names.",
5182
- "The upstream block-body detector still exempts literal on[A-Z] object properties; the concise-arrow extension does not infer a callback contract from that name. Multi-statement bodies, short-circuit guards, local parameter aliases and arbitrary control flow are not inferred. Noncomputed property reads count as data without proving getter effects; computed reads remain outside that exemption, including literal keys. Side-effect detection does not prove whether a called function is pure, and a warning requires manual design review. Generated files are excluded; tests remain checked.",
5175
+ "The block-body detector preserves the existing literal on[A-Z] object-property exemption; the concise-arrow extension does not infer a callback contract from that name. Multi-statement bodies, short-circuit guards, local parameter aliases and arbitrary control flow are not inferred. Noncomputed property reads count as data without proving getter effects; computed reads remain outside that exemption, including literal keys. Side-effect detection does not prove whether a called function is pure, and a warning requires manual design review. Generated files are excluded; tests remain checked.",
5183
5176
  "Automatic fixes only reuse two already authored private zero-argument ordinary operations returning primitive string literals. A complete comment-free four-declaration module must contain those operations, one private const broad-boolean arrow selecting their direct calls, and one exported zero-argument ordinary function with an explicit string-array return that calls the selector exclusively with literal true and false. Calls are routed to the existing named operations and the exclusively used selector declaration is removed. All other APIs, callbacks, dynamic arguments, contracts and operation bodies remain manual."
5184
5177
  ],
5185
5178
  references: [
5186
5179
  "https://understandlegacycode.com/blog/what-is-wrong-with-boolean-parameters/",
5187
- "https://martinfowler.com/bliki/FlagArgument.html",
5188
- "https://github.com/SonarSource/SonarJS/tree/master/packages/analysis/src/jsts/rules/S2301"
5180
+ "https://martinfowler.com/bliki/FlagArgument.html"
5189
5181
  ],
5190
5182
  examples: [
5191
5183
  {
@@ -5275,38 +5267,26 @@ var no_boolean_selector_parameter_default = createRule({
5275
5267
  fix
5276
5268
  });
5277
5269
  }
5278
- function translate({ loc }) {
5279
- if (loc === void 0) return;
5280
- const position = "start" in loc ? loc.start : loc;
5281
- const identifier = context.sourceCode.getNodeByRangeIndex(
5282
- context.sourceCode.getIndexFromLoc(position)
5283
- );
5284
- if (identifier?.type === AST_NODE_TYPES30.Identifier) report2(identifier);
5285
- }
5286
- const nativeContext = Object.create(context, {
5287
- report: { value: translate }
5288
- });
5289
- const nativeArguments = [nativeContext];
5290
- const listeners = Reflect.apply(
5291
- UPSTREAM_SELECTOR_RULE.create.bind(UPSTREAM_SELECTOR_RULE),
5292
- void 0,
5293
- nativeArguments
5294
- );
5295
- const native = {};
5296
- if (typeof listeners !== "object" || listeners === null) return native;
5297
- for (const selector of Object.keys(listeners)) {
5298
- const listener = Reflect.get(listeners, selector);
5299
- if (typeof listener === "function")
5300
- native[selector] = (...nodes) => {
5301
- Reflect.apply(listener, void 0, nodes);
5302
- };
5270
+ function inspectSingleStatementBody(owner) {
5271
+ if (owner.body?.type !== AST_NODE_TYPES30.BlockStatement || owner.body.body.length !== 1 || preservesBlockObjectCallback(owner)) return;
5272
+ const statement = owner.body.body[0];
5273
+ if (statement?.type === AST_NODE_TYPES30.IfStatement) {
5274
+ if (statement.alternate === null || !ASTUtils16.hasSideEffect(statement.consequent, context.sourceCode) && !ASTUtils16.hasSideEffect(statement.alternate, context.sourceCode)) return;
5275
+ const identifier2 = testIdentifier(statement.test);
5276
+ if (identifier2 !== void 0) report2(identifier2);
5277
+ return;
5278
+ }
5279
+ const selected = statement?.type === AST_NODE_TYPES30.ReturnStatement ? statement.argument : statement?.type === AST_NODE_TYPES30.ExpressionStatement ? statement.expression : void 0;
5280
+ if (selected?.type !== AST_NODE_TYPES30.ConditionalExpression || !ASTUtils16.hasSideEffect(selected.consequent, context.sourceCode) && !ASTUtils16.hasSideEffect(selected.alternate, context.sourceCode)) return;
5281
+ const identifier = testIdentifier(selected.test);
5282
+ if (identifier !== void 0) report2(identifier);
5303
5283
  }
5304
5284
  return {
5305
- ...native,
5285
+ FunctionDeclaration: inspectSingleStatementBody,
5286
+ FunctionExpression: inspectSingleStatementBody,
5287
+ ArrowFunctionExpression: inspectSingleStatementBody,
5306
5288
  ConditionalExpression(node) {
5307
- native.ConditionalExpression?.(node);
5308
- if (node.parent.type !== AST_NODE_TYPES30.ArrowFunctionExpression || node.parent.body !== node || !ASTUtils16.hasSideEffect(node.consequent, context.sourceCode) && !ASTUtils16.hasSideEffect(node.alternate, context.sourceCode))
5309
- return;
5289
+ if (node.parent.type !== AST_NODE_TYPES30.ArrowFunctionExpression || node.parent.body !== node || !ASTUtils16.hasSideEffect(node.consequent, context.sourceCode) && !ASTUtils16.hasSideEffect(node.alternate, context.sourceCode)) return;
5310
5290
  const callable = node.parent;
5311
5291
  if (callable.parent.type === AST_NODE_TYPES30.CallExpression && callable.parent.arguments.includes(callable) || callable.parent.type === AST_NODE_TYPES30.JSXExpressionContainer)
5312
5292
  return;
@@ -5316,6 +5296,13 @@ var no_boolean_selector_parameter_default = createRule({
5316
5296
  };
5317
5297
  }
5318
5298
  });
5299
+ function preservesBlockObjectCallback(owner) {
5300
+ const property = owner.parent;
5301
+ if (property.type !== AST_NODE_TYPES30.Property || property.computed) return false;
5302
+ const key = property.key;
5303
+ const name = key.type === AST_NODE_TYPES30.Identifier ? key.name : key.type === AST_NODE_TYPES30.Literal && typeof key.value === "string" ? key.value : void 0;
5304
+ return name !== void 0 && /^on[A-Z]/u.test(name);
5305
+ }
5319
5306
  function testIdentifier(test) {
5320
5307
  const value = test.type === AST_NODE_TYPES30.UnaryExpression && test.operator === "!" ? test.argument : test;
5321
5308
  return value.type === AST_NODE_TYPES30.Identifier ? value : void 0;
@@ -32339,7 +32326,7 @@ function classMethodCounts(body3, methods) {
32339
32326
  }
32340
32327
 
32341
32328
  // src/rules/source-coupled-test.ts
32342
- import { posix as posix2 } from "path";
32329
+ import { posix as posix2, win32 as win322 } from "path";
32343
32330
  import { AST_NODE_TYPES as AST_NODE_TYPES150, ASTUtils as ASTUtils98 } from "@typescript-eslint/utils";
32344
32331
  var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc|toml|py|css|html|[cm]?[jt]s|[jt]sx)$|(?<!\.(?:tf|tftest))\.json$/iu;
32345
32332
  var REPRESENTATION_DIR_RE = /(?:^|\/)(?:__fixtures__|fixtures?|goldens?|snapshots?)(?:\/|$)/iu;
@@ -32391,7 +32378,7 @@ var SOURCE_COUPLED_TEST_DOCUMENTATION = {
32391
32378
  "The rule follows stable lexical bindings, static source paths including JSX/TSX, CSS and HTML, awaited reads, and common text operations. Named imports of node:path/path join and resolve are followed when their complete trailing static path proves a source suffix. Reassigned bindings, dynamic trailing segments, namespace path wrappers, unknown path wrappers, iterator pipelines, and interprocedural flows remain unreported.",
32392
32379
  "Assertion roots are scope-resolved for supported test runners and Node assert imports; local or unknown helpers with assertion-like names are not inferred.",
32393
32380
  "Known fixture, golden, snapshot, and generated source paths are representation contracts and remain unreported; dot segments in the known path are normalized first.",
32394
- "Contained static paths beneath a stable native mkdtemp/mkdtempSync result are generated artifacts. Literal replace/replaceAll nonidentity guards comparing the same raw read remain unreported; fixed-string comparisons remain candidates.",
32381
+ "Contained static paths, including stable nested join/resolve descendants, beneath a native mkdtemp/mkdtempSync result are generated artifacts. Absolute or drive-anchored resolve segments and traversal above the proven directory remain unproven. Literal replace/replaceAll nonidentity guards comparing the same raw read remain unreported; fixed-string comparisons remain candidates.",
32395
32382
  "When raw representation is genuinely the contract (for example a golden or compatibility sentinel), use an exact line suppression with the reason."
32396
32383
  ],
32397
32384
  examples: [
@@ -32434,6 +32421,16 @@ function stringValue(node) {
32434
32421
  }
32435
32422
  return null;
32436
32423
  }
32424
+ function relativePathDepth(normalized, initialDepth) {
32425
+ let relativeDepth = initialDepth;
32426
+ for (const part of normalized.split("/")) {
32427
+ if (part === "..") {
32428
+ if (relativeDepth === 0) return null;
32429
+ relativeDepth -= 1;
32430
+ } else if (part !== "" && part !== ".") relativeDepth += 1;
32431
+ }
32432
+ return relativeDepth;
32433
+ }
32437
32434
  function staticPathSuffix(pathArguments, builder) {
32438
32435
  const trailing = [];
32439
32436
  for (const argument of pathArguments.toReversed()) {
@@ -32559,20 +32556,27 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
32559
32556
  }
32560
32557
  if (current.type !== AST_NODE_TYPES150.CallExpression) return false;
32561
32558
  const callee = unwrap6(current.callee);
32562
- return callee.type === AST_NODE_TYPES150.Identifier && ["mkdtemp", "mkdtempSync"].includes(nativeImportedName(callee, FS_MODULES) ?? "");
32559
+ if (callee.type !== AST_NODE_TYPES150.Identifier) return false;
32560
+ if (["mkdtemp", "mkdtempSync"].includes(nativeImportedName(callee, FS_MODULES) ?? "")) return true;
32561
+ const builder = nativePathBuilder(callee);
32562
+ return builder !== null && temporaryDescendant(current, builder, depth + 1);
32563
32563
  };
32564
- const temporaryDescendant = (node, builder) => {
32564
+ const temporaryDescendant = (node, builder, depth = 0) => {
32565
+ if (depth > 8) return false;
32565
32566
  const [directory, ...segments] = node.arguments;
32566
- if (directory === void 0 || directory.type === AST_NODE_TYPES150.SpreadElement || !temporaryDirectory(directory)) return false;
32567
- const values = [];
32567
+ if (directory === void 0 || directory.type === AST_NODE_TYPES150.SpreadElement || !temporaryDirectory(directory, depth)) return false;
32568
+ let relativeDepth = 0;
32568
32569
  for (const segment of segments) {
32569
32570
  if (segment.type === AST_NODE_TYPES150.SpreadElement) return false;
32570
32571
  const value = stringValue(segment);
32571
32572
  if (value === null) return false;
32572
- values.push(value.replaceAll("\\", "/"));
32573
+ const normalized = value.replaceAll("\\", "/");
32574
+ if (builder === "resolve" && (posix2.isAbsolute(normalized) || win322.parse(normalized).root !== "")) return false;
32575
+ const nextDepth = relativePathDepth(normalized, relativeDepth);
32576
+ if (nextDepth === null) return false;
32577
+ relativeDepth = nextDepth;
32573
32578
  }
32574
- const sentinel = "/__source_oracle_temporary_directory__";
32575
- return posix2[builder](sentinel, ...values).startsWith(`${sentinel}/`);
32579
+ return relativeDepth > 0;
32576
32580
  };
32577
32581
  const sourcePathValue = (value) => {
32578
32582
  const normalized = posix2.normalize(value.replaceAll("\\", "/"));
@@ -1,4 +1,4 @@
1
- import { D as DocumentedRule } from './_docs-DU-K223M.cjs';
1
+ import { DocumentedRule } from './index.cjs';
2
2
  import '@typescript-eslint/utils';
3
3
 
4
4
  /** Execute all authored examples, including private regressions, in isolated projects. */
@@ -1,4 +1,4 @@
1
- import { D as DocumentedRule } from './_docs-DU-K223M.js';
1
+ import { DocumentedRule } from './index.js';
2
2
  import '@typescript-eslint/utils';
3
3
 
4
4
  /** Execute all authored examples, including private regressions, in isolated projects. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.30.0",
3
+ "version": "16.30.4",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",
@@ -56,7 +56,6 @@
56
56
  "dependencies": {
57
57
  "@typescript-eslint/typescript-estree": "^8.72.0",
58
58
  "@typescript-eslint/utils": "^8.72.0",
59
- "eslint-plugin-sonarjs": "4.2.2",
60
59
  "jsonc-parser": "^3.3.1",
61
60
  "ts-api-utils": "2.5.0"
62
61
  },