@sarj/eslint-plugin 2.4.0 → 2.4.1

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
@@ -37,7 +37,6 @@ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/rules/enforce-file-structure.ts
39
39
  var import_utils = require("@typescript-eslint/utils");
40
- var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
41
40
  var classifyStatement = (statement) => {
42
41
  switch (statement.type) {
43
42
  case import_utils.AST_NODE_TYPES.ImportDeclaration:
@@ -52,7 +51,6 @@ var classifyStatement = (statement) => {
52
51
  };
53
52
  var isStringDirective = (statement) => statement.type === import_utils.AST_NODE_TYPES.ExpressionStatement && statement.expression.type === import_utils.AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ");
54
53
  var isUseServerDirective = (statement) => {
55
- if (statement === void 0) return false;
56
54
  if (statement.type !== import_utils.AST_NODE_TYPES.ExpressionStatement) return false;
57
55
  const expr = statement.expression;
58
56
  if (expr.type !== import_utils.AST_NODE_TYPES.Literal) return false;
@@ -65,23 +63,25 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
65
63
  meta: {
66
64
  type: "suggestion",
67
65
  docs: {
68
- description: "Require `import` statements to come first, then allow step-down ordering (public API first, private helpers below) for the rest of the file. Exported statements are classified by WHAT they export \u2014 an exported interface is a declaration, an exported function is a function \u2014 so a public exported function followed by a private helper, or an exported interface among declarations, is allowed. Re-exports (`export { \u2026 } from`, `export *`, `export { \u2026 }`) are a neutral group, so generated namespace barrels pass. Server-action files (under `/actions/`, named `*.action.ts`, or `actions.ts`) must also begin with a `use server` directive."
66
+ description: "Require `import` statements to come first, then allow step-down ordering (public API first, private helpers below) for the rest of the file. Exported statements are classified by WHAT they export \u2014 an exported interface is a declaration, an exported function is a function \u2014 so a public exported function followed by a private helper, or an exported interface among declarations, is allowed. Re-exports (`export { \u2026 } from`, `export *`, `export { \u2026 }`) are a neutral group, so generated namespace barrels pass. When a module contains a `use server` directive, it must be the first statement in the file."
69
67
  },
70
68
  schema: [],
71
69
  messages: {
72
70
  importsFirst: "File structure violation: import statements must come before other declarations",
73
- useServerDirective: "Server action files must start with 'use server' directive"
71
+ useServerDirective: "A 'use server' directive must be the first statement in the file"
74
72
  }
75
73
  },
76
74
  defaultOptions: [],
77
75
  create(context) {
78
- const isServerAction = SERVER_ACTION_FILE_RE.test(context.filename);
79
76
  return {
80
77
  Program(node) {
81
78
  const body = node.body;
82
- if (isServerAction && !isUseServerDirective(body[0])) {
79
+ const misplacedUseServer = body.find(
80
+ (statement, index) => index > 0 && isUseServerDirective(statement)
81
+ );
82
+ if (misplacedUseServer !== void 0) {
83
83
  context.report({
84
- node,
84
+ node: misplacedUseServer,
85
85
  messageId: "useServerDirective"
86
86
  });
87
87
  }
@@ -663,12 +663,49 @@ function instanceofErrorSubject(test) {
663
663
  }
664
664
  return null;
665
665
  }
666
+ var TYPE_GUARD_PATTERN = /^(is|has)[A-Z]/;
667
+ function typeGuardSubject(test) {
668
+ const arg = test.type === "CallExpression" ? test.arguments[0] : void 0;
669
+ if (test.type === "CallExpression" && test.callee.type === "Identifier" && TYPE_GUARD_PATTERN.test(test.callee.name) && test.arguments.length === 1 && arg !== void 0 && arg.type !== "SpreadElement") {
670
+ return arg;
671
+ }
672
+ return null;
673
+ }
674
+ function positiveErrorSubject(test) {
675
+ return instanceofErrorSubject(test) ?? typeGuardSubject(test);
676
+ }
666
677
  function negatedInstanceofErrorSubject(test) {
667
678
  if (test.type === "UnaryExpression" && test.operator === "!") {
668
- return instanceofErrorSubject(test.argument);
679
+ return positiveErrorSubject(test.argument);
669
680
  }
670
681
  return null;
671
682
  }
683
+ function branchTerminates(branch) {
684
+ const body = branch.type === "BlockStatement" ? branch.body : [branch];
685
+ const last = body[body.length - 1];
686
+ return last !== void 0 && (last.type === "ReturnStatement" || last.type === "ThrowStatement");
687
+ }
688
+ function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
689
+ const argText = sourceCode.getText(argExpr);
690
+ let current = node.parent;
691
+ while (current) {
692
+ if (current.type === "BlockStatement" || current.type === "Program") {
693
+ for (const stmt of current.body) {
694
+ if (stmt.range[0] >= node.range[0]) {
695
+ break;
696
+ }
697
+ if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
698
+ const subject = positiveErrorSubject(stmt.test);
699
+ if (subject && sourceCode.getText(subject) === argText) {
700
+ return true;
701
+ }
702
+ }
703
+ }
704
+ }
705
+ current = current.parent;
706
+ }
707
+ return false;
708
+ }
672
709
  function nodeWithin(node, container) {
673
710
  return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
674
711
  }
@@ -678,7 +715,7 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
678
715
  let current = node.parent;
679
716
  while (current) {
680
717
  if (current.type === "ConditionalExpression") {
681
- const subject = instanceofErrorSubject(current.test);
718
+ const subject = positiveErrorSubject(current.test);
682
719
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
683
720
  return true;
684
721
  }
@@ -687,7 +724,7 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
687
724
  return true;
688
725
  }
689
726
  } else if (current.type === "IfStatement") {
690
- const subject = instanceofErrorSubject(current.test);
727
+ const subject = positiveErrorSubject(current.test);
691
728
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
692
729
  return true;
693
730
  }
@@ -740,7 +777,7 @@ var no_json_stringify_error_default = import_utils6.ESLintUtils.RuleCreator(
740
777
  if (!suggestsError) {
741
778
  return;
742
779
  }
743
- if (isGuardedByInstanceofError(node, firstArg, context.sourceCode)) {
780
+ if (isGuardedByInstanceofError(node, firstArg, context.sourceCode) || isNarrowedByEarlyReturn(node, firstArg, context.sourceCode)) {
744
781
  return;
745
782
  }
746
783
  context.report({
@@ -883,6 +920,17 @@ function isProcessEnv(node) {
883
920
  function isImportMetaEnv(node) {
884
921
  return !node.computed && node.property.type === "Identifier" && node.property.name === "env" && node.object.type === "MetaProperty" && node.object.meta.name === "import" && node.object.property.name === "meta";
885
922
  }
923
+ var BUILD_TIME_CONSTANTS = /* @__PURE__ */ new Set([
924
+ "NODE_ENV",
925
+ "MODE",
926
+ "DEV",
927
+ "PROD",
928
+ "SSR"
929
+ ]);
930
+ function isBuildTimeConstantAccess(node) {
931
+ const parent = node.parent;
932
+ return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
933
+ }
886
934
  var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
887
935
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
888
936
  )({
@@ -901,7 +949,7 @@ var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
901
949
  create(context) {
902
950
  return {
903
951
  MemberExpression(node) {
904
- if (isProcessEnv(node) || isImportMetaEnv(node)) {
952
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
905
953
  context.report({
906
954
  node,
907
955
  messageId: "noRawEnv"
@@ -1137,7 +1185,7 @@ var no_sentinel_return_on_catch_default = import_utils10.ESLintUtils.RuleCreator
1137
1185
  // src/rules/no-sequential-await.ts
1138
1186
  var import_utils11 = require("@typescript-eslint/utils");
1139
1187
  var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1140
- var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain/i;
1188
+ var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
1141
1189
  function isFunctionLike(node) {
1142
1190
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1143
1191
  }
@@ -1180,9 +1228,29 @@ function hasEarlyExit(root) {
1180
1228
  });
1181
1229
  return found;
1182
1230
  }
1231
+ var TIMER_HELPER_RE = /^(sleep|timeout|delay|wait|pause|tick)$/i;
1232
+ function calleeName2(callee) {
1233
+ if (callee.type === "Identifier") return callee.name;
1234
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
1235
+ return callee.property.name;
1236
+ }
1237
+ return null;
1238
+ }
1183
1239
  function isTimerYield(node) {
1184
1240
  const arg = node.argument;
1185
- return arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise";
1241
+ if (arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise") {
1242
+ return true;
1243
+ }
1244
+ if (arg.type === "CallExpression") {
1245
+ const name = calleeName2(arg.callee);
1246
+ return name !== null && TIMER_HELPER_RE.test(name);
1247
+ }
1248
+ return false;
1249
+ }
1250
+ var QUEUE_DRAIN_METHODS = /^(shift|pop|dequeue|next|poll)$/;
1251
+ function isQueueDrain(node) {
1252
+ const arg = node.argument;
1253
+ return arg.type === "CallExpression" && arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.property.type === "Identifier" && QUEUE_DRAIN_METHODS.test(arg.callee.property.name);
1186
1254
  }
1187
1255
  function referencesName(root, name) {
1188
1256
  let found = false;
@@ -1217,7 +1285,7 @@ function shouldReport(awaits, earlyExit, iterableText) {
1217
1285
  return false;
1218
1286
  }
1219
1287
  return awaits.some(
1220
- (node) => !isTimerYield(node) && !isThreadedAccumulator(node)
1288
+ (node) => !isTimerYield(node) && !isThreadedAccumulator(node) && !isQueueDrain(node)
1221
1289
  );
1222
1290
  }
1223
1291
  var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
@@ -1238,10 +1306,10 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1238
1306
  create(context) {
1239
1307
  function loopParts(node) {
1240
1308
  if (node.type === "ForStatement") {
1241
- return [node.body, node.init, node.test, node.update];
1309
+ return [node.body, node.test, node.update];
1242
1310
  }
1243
1311
  if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1244
- return [node.body, node.right];
1312
+ return [node.body];
1245
1313
  }
1246
1314
  return [node.body, node.test];
1247
1315
  }
@@ -1897,10 +1965,10 @@ var SVG_EXEMPT_COLOR_VALUES = /* @__PURE__ */ new Set([
1897
1965
  "currentcolor",
1898
1966
  "inherit"
1899
1967
  ]);
1900
- var isInsideSvgDefsContainer = (node) => {
1968
+ var isInsideSvg = (node) => {
1901
1969
  let current = node.parent;
1902
1970
  while (current !== void 0 && current !== null) {
1903
- if (current.type === import_utils17.AST_NODE_TYPES.JSXElement && current.openingElement.name.type === import_utils17.AST_NODE_TYPES.JSXIdentifier && SVG_DEFS_CONTAINERS.has(current.openingElement.name.name)) {
1971
+ if (current.type === import_utils17.AST_NODE_TYPES.JSXElement && current.openingElement.name.type === import_utils17.AST_NODE_TYPES.JSXIdentifier && (current.openingElement.name.name === "svg" || SVG_DEFS_CONTAINERS.has(current.openingElement.name.name))) {
1904
1972
  return true;
1905
1973
  }
1906
1974
  current = current.parent;
@@ -2010,7 +2078,7 @@ var prefer_semantic_colors_default = import_utils17.ESLintUtils.RuleCreator(
2010
2078
  if (typeof node.value.value === "string" && SVG_EXEMPT_COLOR_VALUES.has(node.value.value.toLowerCase())) {
2011
2079
  return;
2012
2080
  }
2013
- if (isInsideSvgDefsContainer(node)) return;
2081
+ if (isInsideSvg(node)) return;
2014
2082
  checkColorValueNode(node.value);
2015
2083
  },
2016
2084
  // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
@@ -2538,7 +2606,7 @@ function propertyKeyName(prop) {
2538
2606
  }
2539
2607
  return void 0;
2540
2608
  }
2541
- function calleeName2(node) {
2609
+ function calleeName3(node) {
2542
2610
  const callee = node.callee;
2543
2611
  if (callee.type === "Identifier") {
2544
2612
  return callee.name;
@@ -2549,7 +2617,7 @@ function calleeName2(node) {
2549
2617
  return void 0;
2550
2618
  }
2551
2619
  function isCorsWildcardCredentialsCall(node) {
2552
- const name = calleeName2(node);
2620
+ const name = calleeName3(node);
2553
2621
  if (name === void 0 || name.toLowerCase() !== "cors") {
2554
2622
  return false;
2555
2623
  }
@@ -3664,7 +3732,7 @@ var rules = {
3664
3732
  var plugin = {
3665
3733
  meta: {
3666
3734
  name: "@sarj/eslint-plugin",
3667
- version: "2.4.0"
3735
+ version: "2.4.1"
3668
3736
  },
3669
3737
  rules,
3670
3738
  configs: {