@sarj/eslint-plugin 16.23.1 → 16.23.3

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
@@ -424,7 +424,21 @@ function isScriptFile(filename) {
424
424
  return SCRIPT_FILE_RE.test(scopedFilename(filename));
425
425
  }
426
426
  function scopedFilename(input) {
427
- return (typeof input === "string" ? input : input.filename).replaceAll("\\", "/");
427
+ if (typeof input === "string") return input.replaceAll("\\", "/");
428
+ const filename = input.filename.replaceAll("\\", "/");
429
+ const cwd = input.cwd.replaceAll("\\", "/");
430
+ const fileWindows = windowsAbsolute(filename);
431
+ const cwdWindows = windowsAbsolute(cwd);
432
+ let scoped = filename;
433
+ if (fileWindows && cwdWindows) scoped = import_node_path.win32.relative(cwd, filename).replaceAll("\\", "/");
434
+ else if (!fileWindows && !cwdWindows && import_node_path.posix.isAbsolute(filename) && import_node_path.posix.isAbsolute(cwd)) {
435
+ scoped = import_node_path.posix.relative(cwd, filename);
436
+ }
437
+ if (windowsAbsolute(scoped) || import_node_path.posix.isAbsolute(scoped)) return scoped;
438
+ return `./${import_node_path.posix.normalize(scoped)}`;
439
+ }
440
+ function windowsAbsolute(filename) {
441
+ return /^[A-Za-z]:\//u.test(filename) || filename.startsWith("//");
428
442
  }
429
443
  function matchesAnyPattern(filename, patterns) {
430
444
  const normalized = filename.replaceAll("\\", "/");
@@ -869,7 +883,7 @@ function isSearchParamReceiver(call, services) {
869
883
  // package.json
870
884
  var package_default = {
871
885
  name: "@sarj/eslint-plugin",
872
- version: "16.23.1",
886
+ version: "16.23.3",
873
887
  packageManager: "npm@12.2.0",
874
888
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
875
889
  type: "module",
@@ -26573,7 +26587,7 @@ var STEPDOWN_DOCUMENTATION = {
26573
26587
  category: "maintainability",
26574
26588
  limitations: [
26575
26589
  "Generated and test files, cycles, dynamic or escaped references, overload targets, and helpers with multiple callers are excluded; immutable local callable aliases are followed only when every use stays in the caller. Function bodies include nested sibling helpers.",
26576
- "Class helpers must be private; their sole caller may be public, protected, or private.",
26590
+ "Class helpers must be private; their sole caller may be public, protected, or private by default. Set privateBandOnly alongside accessibility ordering to restrict class reports to private callers; module and nested function ordering is unchanged.",
26577
26591
  "Runtime class-field, static-block, computed-member, and decorator barriers are never crossed; non-hoisted module helpers also cannot cross eager execution or unrelated initialization.",
26578
26592
  "This rule is report-only: reordering methods can change reflective property order, and moving module declarations can change initialization behavior or introduce temporal-dead-zone failures. Review module cycles and eager callers manually."
26579
26593
  ],
@@ -26894,7 +26908,7 @@ function walk(node, visitorKeys, visit, nestedFunction = false) {
26894
26908
  const nested = nestedFunction || isFunction(node) || node.type === import_utils142.AST_NODE_TYPES.ClassDeclaration || node.type === import_utils142.AST_NODE_TYPES.ClassExpression;
26895
26909
  forEachAstChild(node, visitorKeys, (child) => walk(child, visitorKeys, visit, nested));
26896
26910
  }
26897
- function classScope(context, node, computedReferences) {
26911
+ function classScope(context, node, computedReferences, privateBandOnly) {
26898
26912
  const methods = node.body.body.filter(
26899
26913
  (member) => member.type === import_utils142.AST_NODE_TYPES.MethodDefinition
26900
26914
  );
@@ -26907,7 +26921,7 @@ function classScope(context, node, computedReferences) {
26907
26921
  return name !== null && key !== null && implementationCounts.get(key) === 1 ? [{ key, name, node: method }] : [];
26908
26922
  });
26909
26923
  const definitions2 = scopeDefinitions.filter(
26910
- ({ key, node: method }) => (method.accessibility === "private" || method.key.type === import_utils142.AST_NODE_TYPES.PrivateIdentifier) && counts.get(key) === 1 && method.decorators.length === 0 && !method.computed && method.kind === "method"
26924
+ ({ key, node: method }) => isPrivateMethod(method) && counts.get(key) === 1 && method.decorators.length === 0 && !method.computed && method.kind === "method"
26911
26925
  );
26912
26926
  if (definitions2.length === 0) return;
26913
26927
  const methodKeys = new Set(scopeDefinitions.map((definition) => definition.key));
@@ -27036,6 +27050,7 @@ function classScope(context, node, computedReferences) {
27036
27050
  runtimeBarrierPrefix.push((runtimeBarrierPrefix.at(-1) ?? 0) + (isClassRuntimeBarrier(member) ? 1 : 0));
27037
27051
  }
27038
27052
  const canMove = (helper, caller) => {
27053
+ if (privateBandOnly && !isPrivateMethod(caller.node)) return false;
27039
27054
  const helperIndex = memberIndexes.get(helper.node);
27040
27055
  const callerIndex = memberIndexes.get(caller.node);
27041
27056
  if (helperIndex === void 0 || callerIndex === void 0) return false;
@@ -27043,6 +27058,9 @@ function classScope(context, node, computedReferences) {
27043
27058
  };
27044
27059
  reportMisordered(context, definitions2, scopeDefinitions, calls, pinned, canMove);
27045
27060
  }
27061
+ function isPrivateMethod(node) {
27062
+ return node.type === import_utils142.AST_NODE_TYPES.MethodDefinition && (node.accessibility === "private" || node.key.type === import_utils142.AST_NODE_TYPES.PrivateIdentifier);
27063
+ }
27046
27064
  function classReceiverBindings(context, node) {
27047
27065
  const classReceivers = /* @__PURE__ */ new Map();
27048
27066
  if (node.id !== null) {
@@ -27083,13 +27101,22 @@ var stepdown_default = createRule({
27083
27101
  meta: {
27084
27102
  type: "suggestion",
27085
27103
  docs: { description: "Place a private helper below its sole direct same-scope caller." },
27086
- schema: [],
27104
+ schema: [{
27105
+ type: "object",
27106
+ additionalProperties: false,
27107
+ properties: {
27108
+ privateBandOnly: {
27109
+ type: "boolean",
27110
+ description: "Restrict class reports to private callers when another rule owns accessibility-band ordering."
27111
+ }
27112
+ }
27113
+ }],
27087
27114
  messages: {
27088
27115
  helperAboveOnlyCaller: "Private helper `{{helper}}` is defined above its only caller `{{caller}}`; move it below the caller."
27089
27116
  }
27090
27117
  },
27091
- defaultOptions: [],
27092
- create(context) {
27118
+ defaultOptions: [{}],
27119
+ create(context, [options]) {
27093
27120
  if (isTestFile(context) || isGeneratedFile(context, context.sourceCode.text)) return {};
27094
27121
  const classes = [];
27095
27122
  const functionBodies = [];
@@ -27110,7 +27137,7 @@ var stepdown_default = createRule({
27110
27137
  walk(program, context.sourceCode.visitorKeys, (node) => {
27111
27138
  if (node.type === import_utils142.AST_NODE_TYPES.MemberExpression && node.computed) computedReferences.push(node);
27112
27139
  });
27113
- for (const node of classes) classScope(context, node, computedReferences);
27140
+ for (const node of classes) classScope(context, node, computedReferences, options.privateBandOnly === true);
27114
27141
  }
27115
27142
  };
27116
27143
  }
package/dist/index.d.cts CHANGED
@@ -295,7 +295,9 @@ declare const RULES: {
295
295
  readonly "require-static-next-matcher": DocumentedRule<readonly [], "dynamicMatcher">;
296
296
  readonly "require-zod-form-validation": DocumentedRule<readonly [], "missingZodValidation">;
297
297
  readonly "store-insert-requires-on-conflict": DocumentedRule<readonly [], "storeInsertRequiresOnConflict">;
298
- readonly stepdown: DocumentedRule<[], "helperAboveOnlyCaller">;
298
+ readonly stepdown: DocumentedRule<[{
299
+ readonly privateBandOnly?: boolean;
300
+ }], "helperAboveOnlyCaller">;
299
301
  readonly "source-coupled-test": DocumentedRule<readonly [], "rawSourceOracle">;
300
302
  readonly "sole-export-matches-filename": DocumentedRule<[], "matchSoleExport">;
301
303
  readonly "require-pascal-case-zod-schema-name": DocumentedRule<readonly [], "requirePascalSchema">;
@@ -1134,7 +1136,9 @@ declare const PLUGIN: {
1134
1136
  readonly "require-static-next-matcher": DocumentedRule<readonly [], "dynamicMatcher">;
1135
1137
  readonly "require-zod-form-validation": DocumentedRule<readonly [], "missingZodValidation">;
1136
1138
  readonly "store-insert-requires-on-conflict": DocumentedRule<readonly [], "storeInsertRequiresOnConflict">;
1137
- readonly stepdown: DocumentedRule<[], "helperAboveOnlyCaller">;
1139
+ readonly stepdown: DocumentedRule<[{
1140
+ readonly privateBandOnly?: boolean;
1141
+ }], "helperAboveOnlyCaller">;
1138
1142
  readonly "source-coupled-test": DocumentedRule<readonly [], "rawSourceOracle">;
1139
1143
  readonly "sole-export-matches-filename": DocumentedRule<[], "matchSoleExport">;
1140
1144
  readonly "require-pascal-case-zod-schema-name": DocumentedRule<readonly [], "requirePascalSchema">;
package/dist/index.d.ts CHANGED
@@ -295,7 +295,9 @@ declare const RULES: {
295
295
  readonly "require-static-next-matcher": DocumentedRule<readonly [], "dynamicMatcher">;
296
296
  readonly "require-zod-form-validation": DocumentedRule<readonly [], "missingZodValidation">;
297
297
  readonly "store-insert-requires-on-conflict": DocumentedRule<readonly [], "storeInsertRequiresOnConflict">;
298
- readonly stepdown: DocumentedRule<[], "helperAboveOnlyCaller">;
298
+ readonly stepdown: DocumentedRule<[{
299
+ readonly privateBandOnly?: boolean;
300
+ }], "helperAboveOnlyCaller">;
299
301
  readonly "source-coupled-test": DocumentedRule<readonly [], "rawSourceOracle">;
300
302
  readonly "sole-export-matches-filename": DocumentedRule<[], "matchSoleExport">;
301
303
  readonly "require-pascal-case-zod-schema-name": DocumentedRule<readonly [], "requirePascalSchema">;
@@ -1134,7 +1136,9 @@ declare const PLUGIN: {
1134
1136
  readonly "require-static-next-matcher": DocumentedRule<readonly [], "dynamicMatcher">;
1135
1137
  readonly "require-zod-form-validation": DocumentedRule<readonly [], "missingZodValidation">;
1136
1138
  readonly "store-insert-requires-on-conflict": DocumentedRule<readonly [], "storeInsertRequiresOnConflict">;
1137
- readonly stepdown: DocumentedRule<[], "helperAboveOnlyCaller">;
1139
+ readonly stepdown: DocumentedRule<[{
1140
+ readonly privateBandOnly?: boolean;
1141
+ }], "helperAboveOnlyCaller">;
1138
1142
  readonly "source-coupled-test": DocumentedRule<readonly [], "rawSourceOracle">;
1139
1143
  readonly "sole-export-matches-filename": DocumentedRule<[], "matchSoleExport">;
1140
1144
  readonly "require-pascal-case-zod-schema-name": DocumentedRule<readonly [], "requirePascalSchema">;
package/dist/index.js CHANGED
@@ -288,7 +288,7 @@ function isTypeOnly(node) {
288
288
  import { AST_NODE_TYPES as AST_NODE_TYPES4, ASTUtils as ASTUtils2 } from "@typescript-eslint/utils";
289
289
 
290
290
  // src/rules/_paths.ts
291
- import { posix } from "path";
291
+ import { posix, win32 } from "path";
292
292
  var LINE_TERMINATOR_RE = /[\n\r\u2028\u2029]/u;
293
293
  var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
294
294
  var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
@@ -373,7 +373,21 @@ function isScriptFile(filename) {
373
373
  return SCRIPT_FILE_RE.test(scopedFilename(filename));
374
374
  }
375
375
  function scopedFilename(input) {
376
- return (typeof input === "string" ? input : input.filename).replaceAll("\\", "/");
376
+ if (typeof input === "string") return input.replaceAll("\\", "/");
377
+ const filename = input.filename.replaceAll("\\", "/");
378
+ const cwd = input.cwd.replaceAll("\\", "/");
379
+ const fileWindows = windowsAbsolute(filename);
380
+ const cwdWindows = windowsAbsolute(cwd);
381
+ let scoped = filename;
382
+ if (fileWindows && cwdWindows) scoped = win32.relative(cwd, filename).replaceAll("\\", "/");
383
+ else if (!fileWindows && !cwdWindows && posix.isAbsolute(filename) && posix.isAbsolute(cwd)) {
384
+ scoped = posix.relative(cwd, filename);
385
+ }
386
+ if (windowsAbsolute(scoped) || posix.isAbsolute(scoped)) return scoped;
387
+ return `./${posix.normalize(scoped)}`;
388
+ }
389
+ function windowsAbsolute(filename) {
390
+ return /^[A-Za-z]:\//u.test(filename) || filename.startsWith("//");
377
391
  }
378
392
  function matchesAnyPattern(filename, patterns) {
379
393
  const normalized = filename.replaceAll("\\", "/");
@@ -822,7 +836,7 @@ function isSearchParamReceiver(call, services) {
822
836
  // package.json
823
837
  var package_default = {
824
838
  name: "@sarj/eslint-plugin",
825
- version: "16.23.1",
839
+ version: "16.23.3",
826
840
  packageManager: "npm@12.2.0",
827
841
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
828
842
  type: "module",
@@ -26597,7 +26611,7 @@ var STEPDOWN_DOCUMENTATION = {
26597
26611
  category: "maintainability",
26598
26612
  limitations: [
26599
26613
  "Generated and test files, cycles, dynamic or escaped references, overload targets, and helpers with multiple callers are excluded; immutable local callable aliases are followed only when every use stays in the caller. Function bodies include nested sibling helpers.",
26600
- "Class helpers must be private; their sole caller may be public, protected, or private.",
26614
+ "Class helpers must be private; their sole caller may be public, protected, or private by default. Set privateBandOnly alongside accessibility ordering to restrict class reports to private callers; module and nested function ordering is unchanged.",
26601
26615
  "Runtime class-field, static-block, computed-member, and decorator barriers are never crossed; non-hoisted module helpers also cannot cross eager execution or unrelated initialization.",
26602
26616
  "This rule is report-only: reordering methods can change reflective property order, and moving module declarations can change initialization behavior or introduce temporal-dead-zone failures. Review module cycles and eager callers manually."
26603
26617
  ],
@@ -26918,7 +26932,7 @@ function walk(node, visitorKeys, visit, nestedFunction = false) {
26918
26932
  const nested = nestedFunction || isFunction(node) || node.type === AST_NODE_TYPES121.ClassDeclaration || node.type === AST_NODE_TYPES121.ClassExpression;
26919
26933
  forEachAstChild(node, visitorKeys, (child) => walk(child, visitorKeys, visit, nested));
26920
26934
  }
26921
- function classScope(context, node, computedReferences) {
26935
+ function classScope(context, node, computedReferences, privateBandOnly) {
26922
26936
  const methods = node.body.body.filter(
26923
26937
  (member) => member.type === AST_NODE_TYPES121.MethodDefinition
26924
26938
  );
@@ -26931,7 +26945,7 @@ function classScope(context, node, computedReferences) {
26931
26945
  return name !== null && key !== null && implementationCounts.get(key) === 1 ? [{ key, name, node: method }] : [];
26932
26946
  });
26933
26947
  const definitions2 = scopeDefinitions.filter(
26934
- ({ key, node: method }) => (method.accessibility === "private" || method.key.type === AST_NODE_TYPES121.PrivateIdentifier) && counts.get(key) === 1 && method.decorators.length === 0 && !method.computed && method.kind === "method"
26948
+ ({ key, node: method }) => isPrivateMethod(method) && counts.get(key) === 1 && method.decorators.length === 0 && !method.computed && method.kind === "method"
26935
26949
  );
26936
26950
  if (definitions2.length === 0) return;
26937
26951
  const methodKeys = new Set(scopeDefinitions.map((definition) => definition.key));
@@ -27060,6 +27074,7 @@ function classScope(context, node, computedReferences) {
27060
27074
  runtimeBarrierPrefix.push((runtimeBarrierPrefix.at(-1) ?? 0) + (isClassRuntimeBarrier(member) ? 1 : 0));
27061
27075
  }
27062
27076
  const canMove = (helper, caller) => {
27077
+ if (privateBandOnly && !isPrivateMethod(caller.node)) return false;
27063
27078
  const helperIndex = memberIndexes.get(helper.node);
27064
27079
  const callerIndex = memberIndexes.get(caller.node);
27065
27080
  if (helperIndex === void 0 || callerIndex === void 0) return false;
@@ -27067,6 +27082,9 @@ function classScope(context, node, computedReferences) {
27067
27082
  };
27068
27083
  reportMisordered(context, definitions2, scopeDefinitions, calls, pinned, canMove);
27069
27084
  }
27085
+ function isPrivateMethod(node) {
27086
+ return node.type === AST_NODE_TYPES121.MethodDefinition && (node.accessibility === "private" || node.key.type === AST_NODE_TYPES121.PrivateIdentifier);
27087
+ }
27070
27088
  function classReceiverBindings(context, node) {
27071
27089
  const classReceivers = /* @__PURE__ */ new Map();
27072
27090
  if (node.id !== null) {
@@ -27107,13 +27125,22 @@ var stepdown_default = createRule({
27107
27125
  meta: {
27108
27126
  type: "suggestion",
27109
27127
  docs: { description: "Place a private helper below its sole direct same-scope caller." },
27110
- schema: [],
27128
+ schema: [{
27129
+ type: "object",
27130
+ additionalProperties: false,
27131
+ properties: {
27132
+ privateBandOnly: {
27133
+ type: "boolean",
27134
+ description: "Restrict class reports to private callers when another rule owns accessibility-band ordering."
27135
+ }
27136
+ }
27137
+ }],
27111
27138
  messages: {
27112
27139
  helperAboveOnlyCaller: "Private helper `{{helper}}` is defined above its only caller `{{caller}}`; move it below the caller."
27113
27140
  }
27114
27141
  },
27115
- defaultOptions: [],
27116
- create(context) {
27142
+ defaultOptions: [{}],
27143
+ create(context, [options]) {
27117
27144
  if (isTestFile(context) || isGeneratedFile(context, context.sourceCode.text)) return {};
27118
27145
  const classes = [];
27119
27146
  const functionBodies = [];
@@ -27134,7 +27161,7 @@ var stepdown_default = createRule({
27134
27161
  walk(program, context.sourceCode.visitorKeys, (node) => {
27135
27162
  if (node.type === AST_NODE_TYPES121.MemberExpression && node.computed) computedReferences.push(node);
27136
27163
  });
27137
- for (const node of classes) classScope(context, node, computedReferences);
27164
+ for (const node of classes) classScope(context, node, computedReferences, options.privateBandOnly === true);
27138
27165
  }
27139
27166
  };
27140
27167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.23.1",
3
+ "version": "16.23.3",
4
4
  "packageManager": "npm@12.2.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",