@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.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // src/rules/enforce-file-structure.ts
2
2
  import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
3
- var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
4
3
  var classifyStatement = (statement) => {
5
4
  switch (statement.type) {
6
5
  case AST_NODE_TYPES.ImportDeclaration:
@@ -15,7 +14,6 @@ var classifyStatement = (statement) => {
15
14
  };
16
15
  var isStringDirective = (statement) => statement.type === AST_NODE_TYPES.ExpressionStatement && statement.expression.type === AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ");
17
16
  var isUseServerDirective = (statement) => {
18
- if (statement === void 0) return false;
19
17
  if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;
20
18
  const expr = statement.expression;
21
19
  if (expr.type !== AST_NODE_TYPES.Literal) return false;
@@ -28,23 +26,25 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
28
26
  meta: {
29
27
  type: "suggestion",
30
28
  docs: {
31
- 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."
29
+ 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."
32
30
  },
33
31
  schema: [],
34
32
  messages: {
35
33
  importsFirst: "File structure violation: import statements must come before other declarations",
36
- useServerDirective: "Server action files must start with 'use server' directive"
34
+ useServerDirective: "A 'use server' directive must be the first statement in the file"
37
35
  }
38
36
  },
39
37
  defaultOptions: [],
40
38
  create(context) {
41
- const isServerAction = SERVER_ACTION_FILE_RE.test(context.filename);
42
39
  return {
43
40
  Program(node) {
44
41
  const body = node.body;
45
- if (isServerAction && !isUseServerDirective(body[0])) {
42
+ const misplacedUseServer = body.find(
43
+ (statement, index) => index > 0 && isUseServerDirective(statement)
44
+ );
45
+ if (misplacedUseServer !== void 0) {
46
46
  context.report({
47
- node,
47
+ node: misplacedUseServer,
48
48
  messageId: "useServerDirective"
49
49
  });
50
50
  }
@@ -629,12 +629,49 @@ function instanceofErrorSubject(test) {
629
629
  }
630
630
  return null;
631
631
  }
632
+ var TYPE_GUARD_PATTERN = /^(is|has)[A-Z]/;
633
+ function typeGuardSubject(test) {
634
+ const arg = test.type === "CallExpression" ? test.arguments[0] : void 0;
635
+ 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") {
636
+ return arg;
637
+ }
638
+ return null;
639
+ }
640
+ function positiveErrorSubject(test) {
641
+ return instanceofErrorSubject(test) ?? typeGuardSubject(test);
642
+ }
632
643
  function negatedInstanceofErrorSubject(test) {
633
644
  if (test.type === "UnaryExpression" && test.operator === "!") {
634
- return instanceofErrorSubject(test.argument);
645
+ return positiveErrorSubject(test.argument);
635
646
  }
636
647
  return null;
637
648
  }
649
+ function branchTerminates(branch) {
650
+ const body = branch.type === "BlockStatement" ? branch.body : [branch];
651
+ const last = body[body.length - 1];
652
+ return last !== void 0 && (last.type === "ReturnStatement" || last.type === "ThrowStatement");
653
+ }
654
+ function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
655
+ const argText = sourceCode.getText(argExpr);
656
+ let current = node.parent;
657
+ while (current) {
658
+ if (current.type === "BlockStatement" || current.type === "Program") {
659
+ for (const stmt of current.body) {
660
+ if (stmt.range[0] >= node.range[0]) {
661
+ break;
662
+ }
663
+ if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
664
+ const subject = positiveErrorSubject(stmt.test);
665
+ if (subject && sourceCode.getText(subject) === argText) {
666
+ return true;
667
+ }
668
+ }
669
+ }
670
+ }
671
+ current = current.parent;
672
+ }
673
+ return false;
674
+ }
638
675
  function nodeWithin(node, container) {
639
676
  return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
640
677
  }
@@ -644,7 +681,7 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
644
681
  let current = node.parent;
645
682
  while (current) {
646
683
  if (current.type === "ConditionalExpression") {
647
- const subject = instanceofErrorSubject(current.test);
684
+ const subject = positiveErrorSubject(current.test);
648
685
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
649
686
  return true;
650
687
  }
@@ -653,7 +690,7 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
653
690
  return true;
654
691
  }
655
692
  } else if (current.type === "IfStatement") {
656
- const subject = instanceofErrorSubject(current.test);
693
+ const subject = positiveErrorSubject(current.test);
657
694
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
658
695
  return true;
659
696
  }
@@ -706,7 +743,7 @@ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
706
743
  if (!suggestsError) {
707
744
  return;
708
745
  }
709
- if (isGuardedByInstanceofError(node, firstArg, context.sourceCode)) {
746
+ if (isGuardedByInstanceofError(node, firstArg, context.sourceCode) || isNarrowedByEarlyReturn(node, firstArg, context.sourceCode)) {
710
747
  return;
711
748
  }
712
749
  context.report({
@@ -849,6 +886,17 @@ function isProcessEnv(node) {
849
886
  function isImportMetaEnv(node) {
850
887
  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";
851
888
  }
889
+ var BUILD_TIME_CONSTANTS = /* @__PURE__ */ new Set([
890
+ "NODE_ENV",
891
+ "MODE",
892
+ "DEV",
893
+ "PROD",
894
+ "SSR"
895
+ ]);
896
+ function isBuildTimeConstantAccess(node) {
897
+ const parent = node.parent;
898
+ return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
899
+ }
852
900
  var no_raw_env_default = ESLintUtils8.RuleCreator(
853
901
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
854
902
  )({
@@ -867,7 +915,7 @@ var no_raw_env_default = ESLintUtils8.RuleCreator(
867
915
  create(context) {
868
916
  return {
869
917
  MemberExpression(node) {
870
- if (isProcessEnv(node) || isImportMetaEnv(node)) {
918
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
871
919
  context.report({
872
920
  node,
873
921
  messageId: "noRawEnv"
@@ -1106,7 +1154,7 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
1106
1154
  // src/rules/no-sequential-await.ts
1107
1155
  import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
1108
1156
  var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1109
- var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain/i;
1157
+ var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
1110
1158
  function isFunctionLike(node) {
1111
1159
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1112
1160
  }
@@ -1149,9 +1197,29 @@ function hasEarlyExit(root) {
1149
1197
  });
1150
1198
  return found;
1151
1199
  }
1200
+ var TIMER_HELPER_RE = /^(sleep|timeout|delay|wait|pause|tick)$/i;
1201
+ function calleeName2(callee) {
1202
+ if (callee.type === "Identifier") return callee.name;
1203
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
1204
+ return callee.property.name;
1205
+ }
1206
+ return null;
1207
+ }
1152
1208
  function isTimerYield(node) {
1153
1209
  const arg = node.argument;
1154
- return arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise";
1210
+ if (arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise") {
1211
+ return true;
1212
+ }
1213
+ if (arg.type === "CallExpression") {
1214
+ const name = calleeName2(arg.callee);
1215
+ return name !== null && TIMER_HELPER_RE.test(name);
1216
+ }
1217
+ return false;
1218
+ }
1219
+ var QUEUE_DRAIN_METHODS = /^(shift|pop|dequeue|next|poll)$/;
1220
+ function isQueueDrain(node) {
1221
+ const arg = node.argument;
1222
+ return arg.type === "CallExpression" && arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.property.type === "Identifier" && QUEUE_DRAIN_METHODS.test(arg.callee.property.name);
1155
1223
  }
1156
1224
  function referencesName(root, name) {
1157
1225
  let found = false;
@@ -1186,7 +1254,7 @@ function shouldReport(awaits, earlyExit, iterableText) {
1186
1254
  return false;
1187
1255
  }
1188
1256
  return awaits.some(
1189
- (node) => !isTimerYield(node) && !isThreadedAccumulator(node)
1257
+ (node) => !isTimerYield(node) && !isThreadedAccumulator(node) && !isQueueDrain(node)
1190
1258
  );
1191
1259
  }
1192
1260
  var no_sequential_await_default = ESLintUtils10.RuleCreator(
@@ -1207,10 +1275,10 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1207
1275
  create(context) {
1208
1276
  function loopParts(node) {
1209
1277
  if (node.type === "ForStatement") {
1210
- return [node.body, node.init, node.test, node.update];
1278
+ return [node.body, node.test, node.update];
1211
1279
  }
1212
1280
  if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1213
- return [node.body, node.right];
1281
+ return [node.body];
1214
1282
  }
1215
1283
  return [node.body, node.test];
1216
1284
  }
@@ -1872,10 +1940,10 @@ var SVG_EXEMPT_COLOR_VALUES = /* @__PURE__ */ new Set([
1872
1940
  "currentcolor",
1873
1941
  "inherit"
1874
1942
  ]);
1875
- var isInsideSvgDefsContainer = (node) => {
1943
+ var isInsideSvg = (node) => {
1876
1944
  let current = node.parent;
1877
1945
  while (current !== void 0 && current !== null) {
1878
- if (current.type === AST_NODE_TYPES7.JSXElement && current.openingElement.name.type === AST_NODE_TYPES7.JSXIdentifier && SVG_DEFS_CONTAINERS.has(current.openingElement.name.name)) {
1946
+ if (current.type === AST_NODE_TYPES7.JSXElement && current.openingElement.name.type === AST_NODE_TYPES7.JSXIdentifier && (current.openingElement.name.name === "svg" || SVG_DEFS_CONTAINERS.has(current.openingElement.name.name))) {
1879
1947
  return true;
1880
1948
  }
1881
1949
  current = current.parent;
@@ -1985,7 +2053,7 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1985
2053
  if (typeof node.value.value === "string" && SVG_EXEMPT_COLOR_VALUES.has(node.value.value.toLowerCase())) {
1986
2054
  return;
1987
2055
  }
1988
- if (isInsideSvgDefsContainer(node)) return;
2056
+ if (isInsideSvg(node)) return;
1989
2057
  checkColorValueNode(node.value);
1990
2058
  },
1991
2059
  // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
@@ -2519,7 +2587,7 @@ function propertyKeyName(prop) {
2519
2587
  }
2520
2588
  return void 0;
2521
2589
  }
2522
- function calleeName2(node) {
2590
+ function calleeName3(node) {
2523
2591
  const callee = node.callee;
2524
2592
  if (callee.type === "Identifier") {
2525
2593
  return callee.name;
@@ -2530,7 +2598,7 @@ function calleeName2(node) {
2530
2598
  return void 0;
2531
2599
  }
2532
2600
  function isCorsWildcardCredentialsCall(node) {
2533
- const name = calleeName2(node);
2601
+ const name = calleeName3(node);
2534
2602
  if (name === void 0 || name.toLowerCase() !== "cors") {
2535
2603
  return false;
2536
2604
  }
@@ -3651,7 +3719,7 @@ var rules = {
3651
3719
  var plugin = {
3652
3720
  meta: {
3653
3721
  name: "@sarj/eslint-plugin",
3654
- version: "2.4.0"
3722
+ version: "2.4.1"
3655
3723
  },
3656
3724
  rules,
3657
3725
  configs: {