@sarj/eslint-plugin 15.17.4 → 15.17.6

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
@@ -49,6 +49,214 @@ __export(index_exports, {
49
49
  });
50
50
  module.exports = __toCommonJS(index_exports);
51
51
 
52
+ // src/library-policy.ts
53
+ var LIBRARY_POLICY = [
54
+ {
55
+ "id": "LIB101",
56
+ "module": "request",
57
+ "note": "Standards standardizes HTTP clients on Ky; review errors, retries, hooks, and response parsing.",
58
+ "replacement": "ky"
59
+ },
60
+ {
61
+ "id": "LIB101",
62
+ "module": "node-fetch",
63
+ "note": "Standards standardizes HTTP clients on Ky; review errors, retries, hooks, and response parsing.",
64
+ "replacement": "ky"
65
+ },
66
+ {
67
+ "id": "LIB101",
68
+ "module": "cross-fetch",
69
+ "note": "Standards standardizes HTTP clients on Ky; review errors, retries, hooks, and response parsing.",
70
+ "replacement": "ky"
71
+ },
72
+ {
73
+ "id": "LIB101",
74
+ "module": "isomorphic-fetch",
75
+ "note": "Standards standardizes HTTP clients on Ky; review errors, retries, hooks, and response parsing.",
76
+ "replacement": "ky"
77
+ },
78
+ {
79
+ "id": "LIB101",
80
+ "module": "axios",
81
+ "note": "Standards standardizes HTTP clients on Ky; review errors, retries, hooks, and response parsing.",
82
+ "replacement": "ky"
83
+ },
84
+ {
85
+ "id": "LIB102",
86
+ "module": "moment",
87
+ "note": "Standards standardizes date utilities on date-fns; migration is not API-compatible.",
88
+ "replacement": "date-fns"
89
+ },
90
+ {
91
+ "id": "LIB102",
92
+ "module": "dayjs",
93
+ "note": "Standards standardizes date utilities on date-fns; migration is not API-compatible.",
94
+ "replacement": "date-fns"
95
+ },
96
+ {
97
+ "id": "LIB103",
98
+ "module": "lodash",
99
+ "note": "Standards standardizes collection utilities on Remeda and native APIs.",
100
+ "replacement": "remeda"
101
+ },
102
+ {
103
+ "id": "LIB103",
104
+ "module": "lodash-es",
105
+ "note": "Standards standardizes collection utilities on Remeda and native APIs.",
106
+ "replacement": "remeda"
107
+ },
108
+ {
109
+ "id": "LIB103",
110
+ "module": "underscore",
111
+ "note": "Standards standardizes collection utilities on Remeda and native APIs.",
112
+ "replacement": "remeda"
113
+ },
114
+ {
115
+ "id": "LIB104",
116
+ "module": "classnames",
117
+ "note": "Use clsx for conditional class-name composition.",
118
+ "replacement": "clsx"
119
+ },
120
+ {
121
+ "id": "LIB105",
122
+ "module": "joi",
123
+ "note": "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible.",
124
+ "replacement": "zod"
125
+ },
126
+ {
127
+ "id": "LIB105",
128
+ "module": "yup",
129
+ "note": "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible.",
130
+ "replacement": "zod"
131
+ },
132
+ {
133
+ "id": "LIB105",
134
+ "module": "superstruct",
135
+ "note": "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible.",
136
+ "replacement": "zod"
137
+ },
138
+ {
139
+ "id": "LIB105",
140
+ "module": "io-ts",
141
+ "note": "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible.",
142
+ "replacement": "zod"
143
+ },
144
+ {
145
+ "id": "LIB105",
146
+ "module": "runtypes",
147
+ "note": "Standards standardizes runtime validation on Zod; schemas are not drop-in compatible.",
148
+ "replacement": "zod"
149
+ },
150
+ {
151
+ "id": "LIB106",
152
+ "module": "jsonwebtoken",
153
+ "note": "Use jose; review key formats and async signing and verification APIs.",
154
+ "replacement": "jose"
155
+ },
156
+ {
157
+ "id": "LIB107",
158
+ "module": "express",
159
+ "note": "Standards standardizes servers on Hono; Node deployments also need @hono/node-server.",
160
+ "replacement": "hono"
161
+ },
162
+ {
163
+ "id": "LIB107",
164
+ "module": "koa",
165
+ "note": "Standards standardizes servers on Hono; Node deployments also need @hono/node-server.",
166
+ "replacement": "hono"
167
+ },
168
+ {
169
+ "id": "LIB108",
170
+ "module": "jest",
171
+ "note": "Standards standardizes tests on Vitest; review globals, timers, mocks, and environment setup.",
172
+ "replacement": "vitest"
173
+ },
174
+ {
175
+ "id": "LIB108",
176
+ "module": "mocha",
177
+ "note": "Standards standardizes tests on Vitest; review globals, timers, mocks, and environment setup.",
178
+ "replacement": "vitest"
179
+ },
180
+ {
181
+ "id": "LIB109",
182
+ "module": "sinon",
183
+ "note": "Use Vitest spies, mocks, and fake timers instead of Sinon.",
184
+ "replacement": "Vitest mocks"
185
+ },
186
+ {
187
+ "id": "LIB110",
188
+ "module": "commander",
189
+ "note": "Standards standardizes command-line interfaces on citty.",
190
+ "replacement": "citty"
191
+ },
192
+ {
193
+ "id": "LIB110",
194
+ "module": "yargs",
195
+ "note": "Standards standardizes command-line interfaces on citty.",
196
+ "replacement": "citty"
197
+ },
198
+ {
199
+ "id": "LIB111",
200
+ "module": "bluebird",
201
+ "note": "Use native Promise, adding p-limit or p-map only for the extensions actually needed.",
202
+ "replacement": "native Promise"
203
+ },
204
+ {
205
+ "id": "LIB112",
206
+ "module": "rimraf",
207
+ "note": "Prefer node:fs/promises; verify recursive removal, copy, path, and error semantics.",
208
+ "replacement": "node:fs/promises"
209
+ },
210
+ {
211
+ "id": "LIB112",
212
+ "module": "fs-extra",
213
+ "note": "Prefer node:fs/promises; verify recursive removal, copy, path, and error semantics.",
214
+ "replacement": "node:fs/promises"
215
+ },
216
+ {
217
+ "id": "LIB113",
218
+ "module": "abort-controller",
219
+ "note": "Node 22 provides global AbortController.",
220
+ "replacement": "AbortController"
221
+ },
222
+ {
223
+ "id": "LIB114",
224
+ "module": "querystring",
225
+ "note": "Use URLSearchParams and explicitly review repeated keys, escaping, arrays, and object coercion.",
226
+ "replacement": "URLSearchParams"
227
+ },
228
+ {
229
+ "id": "LIB115",
230
+ "module": "dotenv",
231
+ "note": "Standards standardizes environment loading on @dotenvx/dotenvx.",
232
+ "replacement": "@dotenvx/dotenvx"
233
+ },
234
+ {
235
+ "id": "LIB116",
236
+ "module": "chalk",
237
+ "note": "Use picocolors for terminal colors.",
238
+ "replacement": "picocolors"
239
+ },
240
+ {
241
+ "id": "LIB117",
242
+ "module": "faker",
243
+ "note": "The original faker package is abandoned; use @faker-js/faker.",
244
+ "replacement": "@faker-js/faker"
245
+ },
246
+ {
247
+ "id": "LIB118",
248
+ "module": "node-sass",
249
+ "note": "node-sass is end-of-life; use Dart Sass.",
250
+ "replacement": "sass"
251
+ },
252
+ {
253
+ "id": "LIB119",
254
+ "module": "tslint",
255
+ "note": "TSLint is deprecated; use ESLint with typescript-eslint.",
256
+ "replacement": "eslint"
257
+ }
258
+ ];
259
+
52
260
  // src/rules/enforce-file-structure.ts
53
261
  var import_utils2 = require("@typescript-eslint/utils");
54
262
 
@@ -1240,6 +1448,10 @@ function normalizeToken(word) {
1240
1448
  return lower.length > TOKEN_PLURAL_MIN && lower.endsWith("s") && !lower.endsWith("ss") ? lower.slice(0, -1) : lower;
1241
1449
  }
1242
1450
  function restatableStatementBelow(comment, sourceCode) {
1451
+ const node = restatableStatementNodeBelow(comment, sourceCode);
1452
+ return node === null ? null : sourceCode.getText(node);
1453
+ }
1454
+ function restatableStatementNodeBelow(comment, sourceCode) {
1243
1455
  const token = sourceCode.getTokenAfter(comment, { includeComments: false });
1244
1456
  if (token === null || token.loc.start.line !== comment.loc.end.line + 1) return null;
1245
1457
  for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node != null && node.type !== import_utils7.AST_NODE_TYPES.Program; node = node.parent) {
@@ -1250,7 +1462,7 @@ function restatableStatementBelow(comment, sourceCode) {
1250
1462
  if (node.type === import_utils7.AST_NODE_TYPES.VariableDeclaration && isTrivialInitializer(node)) {
1251
1463
  return null;
1252
1464
  }
1253
- return sourceCode.getText(node);
1465
+ return node;
1254
1466
  }
1255
1467
  return null;
1256
1468
  }
@@ -6508,25 +6720,31 @@ var MODALITY_RE = /\b(?:can|could|should|shall|may|might|must|will|would|cannot)
6508
6720
  var LEAD_IN_RE = /:$/;
6509
6721
  var EMPHASIS_RE = /\*\w[^*]*\*|`[^`]+`/;
6510
6722
  var NEGATION_WORD_RE = /\b(?:no|not|never|neither|nor|without|none|non)\b/i;
6723
+ var SEMANTIC_RELATION_RE = /\b(?:only|if|unless|when|while|except|each|every|any|all|before|after|until|since|again|once|twice|already|still|from|to|into|onto|back|versus|rather|instead)\b/i;
6511
6724
  var ACTION_STMT_RE = /[\w.$\])]\s*\(|^\s*(?:return|throw|await|yield)\b/;
6512
6725
  var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
6513
6726
  var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|build|call|check|compute|copy|count|create|fetch|filter|find|get|handle|load|map|merge|parse|process|read|remove|return|save|send|set|sort|store|update|validate|write)(?:s|es|d|ed|ing)?\b/i;
6514
6727
  var WALL_CLUSTER_MAX_LINE_GAP = 8;
6515
6728
  var WALL_CLUSTER_MIN_COMMENTS = 3;
6516
6729
  var NO_RESTATED_COMMENT_DOCUMENTATION = {
6517
- summary: "Flag a single-line comment whose every word already appears on the statement below it.",
6730
+ summary: "Flag short standalone comments that repeat the adjacent statement's identifiers.",
6518
6731
  rationale: "A comment that only repeats code adds no context and can become stale independently.",
6519
- remediation: "Delete the comment or replace it with the reason, constraint, or consequence absent from the code.",
6732
+ remediation: "Remove a genuine restatement; retain conditions, constraints, rationale, and information absent from the statement.",
6520
6733
  category: "maintainability",
6521
6734
  autofix: "suggestion",
6522
- limitations: ["Directives, protected references, questions, multi-line prose, comments with novel content, and generated files are excluded."],
6735
+ limitations: [
6736
+ "Only standalone line comments of two to eight words with at least two content tokens directly above a supported single-line statement are inspected; one-word headings and sentence-ending punctuation are excluded.",
6737
+ "Evidence comes from that statement's identifier tokens, not strings, template text, comments, or neighboring statements. Stopwords and inflection folding are heuristic, not proof of semantic equivalence.",
6738
+ "Directives, protected references, conditions, restrictions, ordering, repetition, direction, questions, prose paragraphs, sibling-group labels, novel content, and generated files are excluded. Deletion is an optional suggestion, never an automatic fix.",
6739
+ "The conservative sibling-group exemption includes enclosing sibling groups and declaration/type-query pairs; it can miss genuine restatements inside those groups."
6740
+ ],
6523
6741
  examples: [
6524
- { id: "reason-comment", title: "Keep the reason the code cannot express", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize because the cache key is stable across deploys.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
6742
+ { id: "reason-comment", title: "Keep a condition the statement does not establish", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize key only after validation.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
6525
6743
  { id: "restated-comment", title: "Remove a comment that repeats the statement", outcome: "match", files: [{ path: "src/cache.ts", source: "// Serialize key\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 1, public: true }
6526
6744
  ]
6527
6745
  };
6528
- function areAdjacentLineComments2(a, b) {
6529
- return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
6746
+ function areAdjacentLineComments2(a, b, isStandalone) {
6747
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && isStandalone(a) && isStandalone(b) && b.loc.start.line === a.loc.end.line + 1;
6530
6748
  }
6531
6749
  function headsSiblingRun(node) {
6532
6750
  const parent = node.parent;
@@ -6544,12 +6762,12 @@ var no_restated_comment_default = createRule({
6544
6762
  type: "suggestion",
6545
6763
  hasSuggestions: true,
6546
6764
  docs: {
6547
- description: "Flag a single-line comment whose every word already appears on the statement below it."
6765
+ description: NO_RESTATED_COMMENT_DOCUMENTATION.summary
6548
6766
  },
6549
6767
  schema: [],
6550
6768
  messages: {
6551
6769
  deleteComment: "Delete the redundant comment.",
6552
- restatesLineBelow: "Comment restates the statement below it \u2014 delete it, or replace it with the *why*; the code already carries the *what*."
6770
+ restatesLineBelow: "Comment repeats the adjacent statement's identifiers \u2014 consider removing it; retain any condition, constraint, or rationale absent from the code."
6553
6771
  }
6554
6772
  },
6555
6773
  defaultOptions: [],
@@ -6558,19 +6776,30 @@ var no_restated_comment_default = createRule({
6558
6776
  return {};
6559
6777
  }
6560
6778
  const sourceCode = context.sourceCode;
6561
- const lines = sourceCode.lines;
6562
6779
  function isStandalone(comment) {
6563
6780
  const before = sourceCode.getTokenBefore(comment, { includeComments: false });
6564
6781
  return !before || before.loc.end.line < comment.loc.start.line;
6565
6782
  }
6566
- function labelsASiblingRun(comment) {
6567
- const token = sourceCode.getTokenAfter(comment, { includeComments: false });
6568
- if (token === null) return false;
6569
- for (let node = sourceCode.getNodeByRangeIndex(token.range[0]); node != null && node.type !== import_utils38.AST_NODE_TYPES.Program; node = node.parent) {
6783
+ function labelsASiblingRun(statement) {
6784
+ for (let node = statement; node !== void 0 && node.type !== import_utils38.AST_NODE_TYPES.Program; node = node.parent) {
6570
6785
  if (headsSiblingRun(node)) return true;
6571
6786
  }
6572
6787
  return false;
6573
6788
  }
6789
+ function headsValueTypeGroup(statement) {
6790
+ if (statement.type !== import_utils38.AST_NODE_TYPES.VariableDeclaration) return false;
6791
+ const parent = statement.parent;
6792
+ if (!("body" in parent) || !Array.isArray(parent.body)) return false;
6793
+ const body2 = parent.body;
6794
+ const next = body2[body2.indexOf(statement) + 1];
6795
+ if (next?.type !== import_utils38.AST_NODE_TYPES.TSTypeAliasDeclaration) return false;
6796
+ const names = new Set(statement.declarations.flatMap(({ id }) => id.type === import_utils38.AST_NODE_TYPES.Identifier ? [id.name] : []));
6797
+ const tokens = sourceCode.getTokens(next);
6798
+ return tokens.some((token, index) => {
6799
+ const nextToken = tokens[index + 1];
6800
+ return token.value === "typeof" && nextToken?.type === import_utils38.AST_TOKEN_TYPES.Identifier && names.has(nextToken.value);
6801
+ });
6802
+ }
6574
6803
  return {
6575
6804
  Program() {
6576
6805
  const comments = sourceCode.getAllComments();
@@ -6598,7 +6827,7 @@ var no_restated_comment_default = createRule({
6598
6827
  if (comment === void 0 || comment.type !== "Line") continue;
6599
6828
  if (wallMembers.has(comment)) continue;
6600
6829
  if (!isStandalone(comment)) continue;
6601
- if (areAdjacentLineComments2(comments[i - 1], comment) || areAdjacentLineComments2(comment, comments[i + 1])) {
6830
+ if (areAdjacentLineComments2(comments[i - 1], comment, isStandalone) || areAdjacentLineComments2(comment, comments[i + 1], isStandalone)) {
6602
6831
  continue;
6603
6832
  }
6604
6833
  const body2 = comment.value.replace(/^\/*/, "").trim();
@@ -6606,17 +6835,19 @@ var no_restated_comment_default = createRule({
6606
6835
  if (DIRECTIVE_RE3.test(body2) || CODEY_RE.test(body2) || BANNERISH_RE.test(body2)) continue;
6607
6836
  if (NON_ASCII_LETTER_RE.test(body2) || isProtected(body2)) continue;
6608
6837
  if (MODALITY_RE.test(body2) || LEAD_IN_RE.test(body2) || EMPHASIS_RE.test(body2)) continue;
6609
- if (NEGATION_WORD_RE.test(body2)) continue;
6610
- if (body2.split(/\s+/).length > MAX_WORDS) continue;
6838
+ if (NEGATION_WORD_RE.test(body2) || SEMANTIC_RELATION_RE.test(body2)) continue;
6839
+ const wordCount2 = body2.split(/\s+/).length;
6840
+ if (wordCount2 < 2 || wordCount2 > MAX_WORDS || /[.!?]$/.test(body2)) continue;
6611
6841
  const tokens = contentTokens(body2);
6612
6842
  if (tokens.length < MIN_CONTENT_TOKENS) continue;
6613
- const statement = restatableStatementBelow(comment, sourceCode);
6614
- if (statement === null) continue;
6843
+ const statementNode = restatableStatementNodeBelow(comment, sourceCode);
6844
+ if (statementNode === null) continue;
6845
+ const statement = sourceCode.getText(statementNode);
6615
6846
  if (restatesStatementHead(body2, statement)) continue;
6616
- const line = lines[comment.loc.start.line] ?? "";
6617
- if (!ACTION_STMT_RE.test(line)) continue;
6618
- if (labelsASiblingRun(comment)) continue;
6619
- if (restates(tokens, codeTokens(line))) {
6847
+ if (!ACTION_STMT_RE.test(statement)) continue;
6848
+ if (labelsASiblingRun(statementNode) || headsValueTypeGroup(statementNode)) continue;
6849
+ const identifiers = sourceCode.getTokens(statementNode).filter((token) => token.type === import_utils38.AST_TOKEN_TYPES.Identifier).map((token) => token.value).join(" ");
6850
+ if (restates(tokens, codeTokens(identifiers))) {
6620
6851
  const removal = wholeLineRemovalRange(sourceCode.text, comment);
6621
6852
  context.report({
6622
6853
  node: comment,
@@ -19521,16 +19752,17 @@ var RULES = {
19521
19752
  };
19522
19753
  var meta = {
19523
19754
  name: "@sarj/eslint-plugin",
19524
- version: "15.17.4"
19755
+ version: "15.17.6"
19525
19756
  };
19526
- var APPLICATION_ONLY_RULES = [
19527
- "no-restricted-library-load",
19528
- "prefer-native-random-uuid",
19529
- "prefer-shadcn-primitives"
19530
- ];
19757
+ var APPLICATION_ONLY_RULES = [];
19758
+ var LIBRARY_IMPORT_POLICY = ["error", {
19759
+ paths: LIBRARY_POLICY.map(({ module: module2, note }) => ({ name: module2, message: note })),
19760
+ patterns: LIBRARY_POLICY.map(({ module: module2, note }) => ({ group: [`${module2}/*`], message: note }))
19761
+ }];
19531
19762
  var ADVISORY_RULES = [
19532
19763
  "@sarj/excessive-commentary",
19533
19764
  "@sarj/no-bespoke-api-case-conversion",
19765
+ "@sarj/no-restated-comment",
19534
19766
  "@sarj/prefer-millisecond-control-duration-schema",
19535
19767
  "@sarj/prefer-module-level-refined-schema",
19536
19768
  "@sarj/prefer-multi-value-zod-literal",
@@ -19546,6 +19778,10 @@ var ADVISORY_RULES = [
19546
19778
  "@sarj/sole-export-matches-filename"
19547
19779
  ];
19548
19780
  var RECOMMENDED_RULES = {
19781
+ "no-restricted-imports": LIBRARY_IMPORT_POLICY,
19782
+ "@sarj/no-restricted-library-load": ["error", { libraries: LIBRARY_POLICY }],
19783
+ "@sarj/prefer-native-random-uuid": "error",
19784
+ "@sarj/prefer-shadcn-primitives": "error",
19549
19785
  "@sarj/excessive-commentary": "warn",
19550
19786
  "@sarj/interface-contract-members-private": "error",
19551
19787
  "@sarj/iac-source-coupled-test": "error",
@@ -19574,7 +19810,7 @@ var RECOMMENDED_RULES = {
19574
19810
  "@sarj/no-production-browser-source-maps": "error",
19575
19811
  "@sarj/no-repeated-string-literal": "error",
19576
19812
  "@sarj/no-router-refresh-polling": "error",
19577
- "@sarj/no-restated-comment": "error",
19813
+ "@sarj/no-restated-comment": "warn",
19578
19814
  "@sarj/no-restated-jsdoc": "error",
19579
19815
  "@sarj/no-secret-in-log": "error",
19580
19816
  "@sarj/no-server-env-in-client-component": "error",
@@ -19635,6 +19871,10 @@ var RECOMMENDED_RULES = {
19635
19871
  "@sarj/require-pascal-case-zod-schema-name": "error"
19636
19872
  };
19637
19873
  var STRICT_RULES = {
19874
+ "no-restricted-imports": LIBRARY_IMPORT_POLICY,
19875
+ "@sarj/no-restricted-library-load": ["error", { libraries: LIBRARY_POLICY }],
19876
+ "@sarj/prefer-native-random-uuid": "error",
19877
+ "@sarj/prefer-shadcn-primitives": "error",
19638
19878
  "@sarj/excessive-commentary": "warn",
19639
19879
  "@sarj/interface-contract-members-private": "error",
19640
19880
  "@sarj/iac-source-coupled-test": "error",
@@ -19666,7 +19906,7 @@ var STRICT_RULES = {
19666
19906
  "@sarj/no-raw-fetch-outside-clients": "error",
19667
19907
  "@sarj/no-repeated-string-literal": "error",
19668
19908
  "@sarj/no-router-refresh-polling": "error",
19669
- "@sarj/no-restated-comment": "error",
19909
+ "@sarj/no-restated-comment": "warn",
19670
19910
  "@sarj/no-restated-jsdoc": "error",
19671
19911
  "@sarj/no-secret-in-log": "error",
19672
19912
  "@sarj/no-server-env-in-client-component": "error",