@sarj/eslint-plugin 13.0.0 → 13.1.0
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/README.md +2 -2
- package/dist/index.cjs +37 -16
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +37 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Custom ESLint rules for hypermodern TypeScript / React / Next.js projects
|
|
|
8
8
|
|
|
9
9
|
| Field | Value |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
| Version | `13.
|
|
11
|
+
| Version | `13.1.0` |
|
|
12
12
|
| Registry | npm |
|
|
13
13
|
| License | MIT |
|
|
14
14
|
| Runtime | `node >=22.13.0` |
|
|
@@ -47,7 +47,7 @@ Custom ESLint rules for hypermodern TypeScript / React / Next.js projects
|
|
|
47
47
|
| `eslint:no-insecure-random-id` | Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead. | `error` | `none` |
|
|
48
48
|
| `eslint:no-json-stringify-error` | Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable. | `error` | `none` |
|
|
49
49
|
| `eslint:no-log-only-catch` | Disallow `catch` clauses that only log (or silently do nothing) and then swallow the error; rethrow or handle it instead. | `error` | `none` |
|
|
50
|
-
| `eslint:no-long-comment` | Flag unusually large unstructured
|
|
50
|
+
| `eslint:no-long-comment` | Flag unusually large unstructured JSDoc blocks in implementation code. | `error` | `none` |
|
|
51
51
|
| `eslint:no-offset-pagination` | Disallow OFFSET pagination in embedded SQL; it is O(N) per page and drops or repeats rows under concurrent writes. Use a keyset cursor. | `error` | `none` |
|
|
52
52
|
| `eslint:no-positional-tuple-return` | Disallow returning a multi-field tuple from an exported function; return a named object so call sites cannot mismatch slots. | `error` | `none` |
|
|
53
53
|
| `eslint:no-raw-env` | Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries. | `error` | `none` |
|
package/dist/index.cjs
CHANGED
|
@@ -1142,6 +1142,7 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
|
|
|
1142
1142
|
var META_COMMENTARY_RE = /\b(?:keeping (?:it|this) simple|could be (?:refactored|improved|cleaned up|simplified)|refactor(?:ed|ing)? (?:later|this)|not sure (?:if|whether|why|how)|quick[- ](?:and[- ]dirty|fix)|(?:a |bit of a )?hacky|is a hack|temporary (?:solution|workaround|fix|hack)|revisit (?:this|later|below)|clean (?:this|it) up|not ideal|placeholder for now)\b/i;
|
|
1143
1143
|
var EDITORIAL_PLACEHOLDER_RE = /^(?:(?:implementation omitted|existing code here|your code here|rest of (?:the )?code (?:is )?unchanged|same as above|placeholder implementation)\s*[.!]?|in a real (?:app(?:lication)?|implementation),?\s+(?:this|we|you|it)\s+would\s+(?:call|fetch|generate|download|persist|save|send|store|write)\b[^,;]*[.!]?)$/i;
|
|
1144
1144
|
var FOR_NOW_RE = /\bfor now\b/i;
|
|
1145
|
+
var JSDOC_DEBT_RE = /^@?(?:todo|fixme)\b/i;
|
|
1145
1146
|
var DEFERRAL_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1146
1147
|
"a",
|
|
1147
1148
|
"an",
|
|
@@ -1270,6 +1271,7 @@ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
|
|
|
1270
1271
|
var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
|
|
1271
1272
|
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
1272
1273
|
var ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$/;
|
|
1274
|
+
var DECLARATION_RE = /^(?:export\s+)?(?:declare\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*(?:\s*:\s*[^=]+)?\s*=\s*(?:[A-Za-z_$][\w.$]*(?:\s*\(|\s*$)|["'`]|\[|\{|\d|true\b|false\b|null\b|undefined\b|new\b|await\b|async\b|function\b|class\b)/;
|
|
1273
1275
|
var CALL_RE = /^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
|
|
1274
1276
|
var ASSERTION_CODE_RE = /^(?:await\s+)?(?:expect(?:TypeOf)?|assert(?:\.\w+)?)\s*\(/;
|
|
1275
1277
|
var HTTP_CONTRACT_RE = /\b(?:GET|HEAD|OPTIONS|PATCH|POST|PUT|DELETE)\s+(?:https?:\/\/|\/|\{[A-Za-z_$])/;
|
|
@@ -1297,6 +1299,7 @@ function looksLikeCode(text, allowCall = true) {
|
|
|
1297
1299
|
if (!t) return false;
|
|
1298
1300
|
if (PROSE_ASSIGNMENT_RE.test(t)) return false;
|
|
1299
1301
|
if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
|
|
1302
|
+
if (DECLARATION_RE.test(t)) return true;
|
|
1300
1303
|
if (ASSIGN_RE.test(t)) return true;
|
|
1301
1304
|
if (ASSERTION_CODE_RE.test(t)) return true;
|
|
1302
1305
|
return allowCall && CALL_RE.test(t);
|
|
@@ -1626,6 +1629,11 @@ var no_comment_cruft_default = createRule({
|
|
|
1626
1629
|
}
|
|
1627
1630
|
if (wallMembers.has(comment)) continue;
|
|
1628
1631
|
if (isJsDoc(comment)) {
|
|
1632
|
+
const debt = comment.value.split("\n").map(stripCommentMarker).find((line) => JSDOC_DEBT_RE.test(line));
|
|
1633
|
+
if (debt !== void 0 && !runCitesAReference(comments, i)) {
|
|
1634
|
+
context.report({ node: comment, messageId: "untrackedTodo" });
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1629
1637
|
if (isStandalone(comment) && isSectionJsDoc(comment)) {
|
|
1630
1638
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
1631
1639
|
}
|
|
@@ -4432,11 +4440,11 @@ function typedFunction(node) {
|
|
|
4432
4440
|
|
|
4433
4441
|
// src/rules/no-long-comment.ts
|
|
4434
4442
|
var noLongCommentDocumentation = {
|
|
4435
|
-
summary: "Flag unusually large unstructured
|
|
4443
|
+
summary: "Flag unusually large unstructured JSDoc blocks in implementation code.",
|
|
4436
4444
|
rationale: "Large narrative comments become stale and obscure the local facts that belong beside the code.",
|
|
4437
4445
|
remediation: "Keep only durable local constraints and express the remaining behavior in code.",
|
|
4438
4446
|
category: "maintainability",
|
|
4439
|
-
limitations: ["
|
|
4447
|
+
limitations: ["Only JSDoc blocks are inspected; structured API docs, tests, scripts, generated files, and versioned dependencies are excluded."],
|
|
4440
4448
|
examples: [
|
|
4441
4449
|
{ id: "local-fact", title: "Keep a concise local fact", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// The cache is process local.\nconst cache = new Map();" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
|
|
4442
4450
|
{ id: "prose-wall", title: "Avoid an unstructured prose wall", outcome: "match", files: [{ path: "src/chart.ts", source: "/** One. Two. Three. Four. Five. Six. Seven. Eight. */\nconst chart = createChart();" }], focusPath: "src/chart.ts", expectedCount: 1, public: true }
|
|
@@ -4464,7 +4472,7 @@ var no_long_comment_default = createRule({
|
|
|
4464
4472
|
documentation: noLongCommentDocumentation,
|
|
4465
4473
|
meta: {
|
|
4466
4474
|
type: "suggestion",
|
|
4467
|
-
docs: { description: "Flag unusually large unstructured
|
|
4475
|
+
docs: { description: "Flag unusually large unstructured JSDoc blocks in implementation code." },
|
|
4468
4476
|
schema: [],
|
|
4469
4477
|
messages: {
|
|
4470
4478
|
tooLong: "Comment is an unusually large prose block \u2014 keep the local facts and clarify the code itself."
|
|
@@ -5409,6 +5417,7 @@ var MIN_DISTINCT_SCOPES = 2;
|
|
|
5409
5417
|
var PREVIEW_LENGTH = 40;
|
|
5410
5418
|
var SQL_KEYWORD_RE = /\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|VALUES|ON CONFLICT|RETURNING|GROUP BY|ORDER BY)\b/;
|
|
5411
5419
|
var IDENTIFIER_RE = /^[a-z_][a-z0-9_.]*$/;
|
|
5420
|
+
var URL_PATH_RE = /^\/(?=[^\s]*[A-Za-z0-9])[A-Za-z0-9._~!$&'()*+,;=:@%/?#{}\u005B\u005D-]+$/;
|
|
5412
5421
|
var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
|
|
5413
5422
|
import_utils29.AST_NODE_TYPES.FunctionDeclaration,
|
|
5414
5423
|
import_utils29.AST_NODE_TYPES.FunctionExpression,
|
|
@@ -5416,7 +5425,7 @@ var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
|
|
|
5416
5425
|
]);
|
|
5417
5426
|
var noRepeatedStringLiteralDocumentation = {
|
|
5418
5427
|
summary: "Disallow a long structured string literal repeated across functions; the copies drift when one is edited. Extract a module-level constant.",
|
|
5419
|
-
rationale: "Independent copies of a
|
|
5428
|
+
rationale: "Independent copies of a query, route template, or identifier can diverge and silently change behavior.",
|
|
5420
5429
|
remediation: "Extract the repeated value to one module-level constant and reference it from each function.",
|
|
5421
5430
|
category: "maintainability",
|
|
5422
5431
|
limitations: ["Test files, short strings, prose, substitutions, module sources, JSX attributes, and repetition within one function are excluded."],
|
|
@@ -5426,7 +5435,7 @@ var noRepeatedStringLiteralDocumentation = {
|
|
|
5426
5435
|
]
|
|
5427
5436
|
};
|
|
5428
5437
|
function isStructured(value) {
|
|
5429
|
-
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value);
|
|
5438
|
+
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
|
|
5430
5439
|
}
|
|
5431
5440
|
function preview(value) {
|
|
5432
5441
|
const oneLine = value.replaceAll("\n", " ").trim();
|
|
@@ -5445,8 +5454,9 @@ function isScaffolding(node) {
|
|
|
5445
5454
|
if (parent === void 0) {
|
|
5446
5455
|
return true;
|
|
5447
5456
|
}
|
|
5457
|
+
const isNonComputedPropertyKey = (parent.type === import_utils29.AST_NODE_TYPES.Property || parent.type === import_utils29.AST_NODE_TYPES.PropertyDefinition || parent.type === import_utils29.AST_NODE_TYPES.MethodDefinition || parent.type === import_utils29.AST_NODE_TYPES.AccessorProperty) && parent.key === node && !parent.computed;
|
|
5448
5458
|
const isRequireSource = parent.type === import_utils29.AST_NODE_TYPES.CallExpression && parent.callee.type === import_utils29.AST_NODE_TYPES.Identifier && parent.callee.name === "require";
|
|
5449
|
-
return parent.type === import_utils29.AST_NODE_TYPES.ImportDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ImportExpression || parent.type === import_utils29.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ExportAllDeclaration || parent.type === import_utils29.AST_NODE_TYPES.TSImportType || parent.type === import_utils29.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils29.AST_NODE_TYPES.TSLiteralType || isRequireSource;
|
|
5459
|
+
return parent.type === import_utils29.AST_NODE_TYPES.ImportDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ImportExpression || parent.type === import_utils29.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ExportAllDeclaration || parent.type === import_utils29.AST_NODE_TYPES.TSImportType || parent.type === import_utils29.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils29.AST_NODE_TYPES.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
|
|
5450
5460
|
}
|
|
5451
5461
|
var no_repeated_string_literal_default = createRule({
|
|
5452
5462
|
name: "no-repeated-string-literal",
|
|
@@ -5463,7 +5473,7 @@ var no_repeated_string_literal_default = createRule({
|
|
|
5463
5473
|
},
|
|
5464
5474
|
defaultOptions: [],
|
|
5465
5475
|
create(context) {
|
|
5466
|
-
if (isTestFile(context.filename)) {
|
|
5476
|
+
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
5467
5477
|
return {};
|
|
5468
5478
|
}
|
|
5469
5479
|
const occurrences = /* @__PURE__ */ new Map();
|
|
@@ -5783,10 +5793,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5783
5793
|
for (const comment of sourceCode.getAllComments()) {
|
|
5784
5794
|
if (comment.type !== "Block" || !comment.value.startsWith("*")) continue;
|
|
5785
5795
|
const { description, tags } = parseJsDoc(comment.value);
|
|
5786
|
-
|
|
5796
|
+
const describedText = [
|
|
5797
|
+
description,
|
|
5798
|
+
...tags.filter((tag) => tag.name === "description").map((tag) => tag.text)
|
|
5799
|
+
].filter((text) => text.length > 0).join("\n");
|
|
5800
|
+
if (DIRECTIVE_RE4.test(describedText)) continue;
|
|
5787
5801
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
5788
5802
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
5789
|
-
if (isProtected(
|
|
5803
|
+
if (isProtected(describedText)) continue;
|
|
5790
5804
|
const token = sourceCode.getTokenAfter(comment, { includeComments: false });
|
|
5791
5805
|
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
|
|
5792
5806
|
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
@@ -5799,13 +5813,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5799
5813
|
if (declaration === null) continue;
|
|
5800
5814
|
const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
|
|
5801
5815
|
const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
|
|
5802
|
-
if (
|
|
5816
|
+
if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
|
|
5803
5817
|
continue;
|
|
5804
5818
|
}
|
|
5819
|
+
if ((paramTags.length > 0 || returnTags.length > 0) && documentsTypedFunction(sourceCode, comment)) continue;
|
|
5805
5820
|
const nameTokens = tokensOf([declaration.name]);
|
|
5806
5821
|
const paramTokens = tokensOf(declaration.params);
|
|
5807
5822
|
const known = /* @__PURE__ */ new Set([...nameTokens, ...paramTokens]);
|
|
5808
|
-
let addsNothing = covered(
|
|
5823
|
+
let addsNothing = covered(describedText, known);
|
|
5809
5824
|
for (const tag of paramTags) {
|
|
5810
5825
|
const text = tag.text.replace(/^\{[^}]*\}\s*/, "");
|
|
5811
5826
|
const match = /^\[?([A-Za-z_$][\w.$]*)\]?\s*-?\s*([\s\S]*)$/.exec(text);
|
|
@@ -5813,7 +5828,13 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5813
5828
|
addsNothing = false;
|
|
5814
5829
|
break;
|
|
5815
5830
|
}
|
|
5816
|
-
const
|
|
5831
|
+
const path = match[1] ?? "";
|
|
5832
|
+
const root = path.split(".")[0] ?? "";
|
|
5833
|
+
if (!declaration.params.includes(root)) {
|
|
5834
|
+
addsNothing = false;
|
|
5835
|
+
break;
|
|
5836
|
+
}
|
|
5837
|
+
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...nameTokens]);
|
|
5817
5838
|
if (!covered(match[2] ?? "", own)) {
|
|
5818
5839
|
addsNothing = false;
|
|
5819
5840
|
break;
|
|
@@ -6638,10 +6659,10 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
6638
6659
|
return false;
|
|
6639
6660
|
}
|
|
6640
6661
|
if (matcher.isLoggingCall(current)) {
|
|
6641
|
-
return
|
|
6662
|
+
return caughtName === null || argsIncludeBinding(current.arguments, caughtName);
|
|
6642
6663
|
}
|
|
6643
6664
|
const name = calleeName2(current.callee);
|
|
6644
|
-
return name !== null && REPORT_NAME_RE.test(name) && argsIncludeBinding(current.arguments, caughtName);
|
|
6665
|
+
return name !== null && REPORT_NAME_RE.test(name) && (caughtName === null || argsIncludeBinding(current.arguments, caughtName));
|
|
6645
6666
|
});
|
|
6646
6667
|
}
|
|
6647
6668
|
return {
|
|
@@ -8110,7 +8131,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
8110
8131
|
claimed.add(comment);
|
|
8111
8132
|
commented += 1;
|
|
8112
8133
|
const body2 = commentBody(comment);
|
|
8113
|
-
if (body2.length === 0 || carriesValue(body2)) continue;
|
|
8134
|
+
if (body2.length === 0 || carriesValue(body2) || isTagsOnly(body2)) continue;
|
|
8114
8135
|
if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
|
|
8115
8136
|
restated += 1;
|
|
8116
8137
|
}
|
|
@@ -14016,7 +14037,7 @@ var rules = {
|
|
|
14016
14037
|
};
|
|
14017
14038
|
var meta = {
|
|
14018
14039
|
name: "@sarj/eslint-plugin",
|
|
14019
|
-
version: "13.
|
|
14040
|
+
version: "13.1.0"
|
|
14020
14041
|
};
|
|
14021
14042
|
var applicationOnlyRules = [
|
|
14022
14043
|
"no-restricted-library-load",
|
package/dist/index.d.cts
CHANGED
|
@@ -399,7 +399,7 @@ type FlatPreset = {
|
|
|
399
399
|
declare const plugin: {
|
|
400
400
|
readonly meta: {
|
|
401
401
|
readonly name: "@sarj/eslint-plugin";
|
|
402
|
-
readonly version: "13.
|
|
402
|
+
readonly version: "13.1.0";
|
|
403
403
|
};
|
|
404
404
|
readonly rules: {
|
|
405
405
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
package/dist/index.d.ts
CHANGED
|
@@ -399,7 +399,7 @@ type FlatPreset = {
|
|
|
399
399
|
declare const plugin: {
|
|
400
400
|
readonly meta: {
|
|
401
401
|
readonly name: "@sarj/eslint-plugin";
|
|
402
|
-
readonly version: "13.
|
|
402
|
+
readonly version: "13.1.0";
|
|
403
403
|
};
|
|
404
404
|
readonly rules: {
|
|
405
405
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
package/dist/index.js
CHANGED
|
@@ -1099,6 +1099,7 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
|
|
|
1099
1099
|
var META_COMMENTARY_RE = /\b(?:keeping (?:it|this) simple|could be (?:refactored|improved|cleaned up|simplified)|refactor(?:ed|ing)? (?:later|this)|not sure (?:if|whether|why|how)|quick[- ](?:and[- ]dirty|fix)|(?:a |bit of a )?hacky|is a hack|temporary (?:solution|workaround|fix|hack)|revisit (?:this|later|below)|clean (?:this|it) up|not ideal|placeholder for now)\b/i;
|
|
1100
1100
|
var EDITORIAL_PLACEHOLDER_RE = /^(?:(?:implementation omitted|existing code here|your code here|rest of (?:the )?code (?:is )?unchanged|same as above|placeholder implementation)\s*[.!]?|in a real (?:app(?:lication)?|implementation),?\s+(?:this|we|you|it)\s+would\s+(?:call|fetch|generate|download|persist|save|send|store|write)\b[^,;]*[.!]?)$/i;
|
|
1101
1101
|
var FOR_NOW_RE = /\bfor now\b/i;
|
|
1102
|
+
var JSDOC_DEBT_RE = /^@?(?:todo|fixme)\b/i;
|
|
1102
1103
|
var DEFERRAL_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1103
1104
|
"a",
|
|
1104
1105
|
"an",
|
|
@@ -1227,6 +1228,7 @@ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
|
|
|
1227
1228
|
var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
|
|
1228
1229
|
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
1229
1230
|
var ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$/;
|
|
1231
|
+
var DECLARATION_RE = /^(?:export\s+)?(?:declare\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*(?:\s*:\s*[^=]+)?\s*=\s*(?:[A-Za-z_$][\w.$]*(?:\s*\(|\s*$)|["'`]|\[|\{|\d|true\b|false\b|null\b|undefined\b|new\b|await\b|async\b|function\b|class\b)/;
|
|
1230
1232
|
var CALL_RE = /^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
|
|
1231
1233
|
var ASSERTION_CODE_RE = /^(?:await\s+)?(?:expect(?:TypeOf)?|assert(?:\.\w+)?)\s*\(/;
|
|
1232
1234
|
var HTTP_CONTRACT_RE = /\b(?:GET|HEAD|OPTIONS|PATCH|POST|PUT|DELETE)\s+(?:https?:\/\/|\/|\{[A-Za-z_$])/;
|
|
@@ -1254,6 +1256,7 @@ function looksLikeCode(text, allowCall = true) {
|
|
|
1254
1256
|
if (!t) return false;
|
|
1255
1257
|
if (PROSE_ASSIGNMENT_RE.test(t)) return false;
|
|
1256
1258
|
if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
|
|
1259
|
+
if (DECLARATION_RE.test(t)) return true;
|
|
1257
1260
|
if (ASSIGN_RE.test(t)) return true;
|
|
1258
1261
|
if (ASSERTION_CODE_RE.test(t)) return true;
|
|
1259
1262
|
return allowCall && CALL_RE.test(t);
|
|
@@ -1583,6 +1586,11 @@ var no_comment_cruft_default = createRule({
|
|
|
1583
1586
|
}
|
|
1584
1587
|
if (wallMembers.has(comment)) continue;
|
|
1585
1588
|
if (isJsDoc(comment)) {
|
|
1589
|
+
const debt = comment.value.split("\n").map(stripCommentMarker).find((line) => JSDOC_DEBT_RE.test(line));
|
|
1590
|
+
if (debt !== void 0 && !runCitesAReference(comments, i)) {
|
|
1591
|
+
context.report({ node: comment, messageId: "untrackedTodo" });
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1586
1594
|
if (isStandalone(comment) && isSectionJsDoc(comment)) {
|
|
1587
1595
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
1588
1596
|
}
|
|
@@ -4391,11 +4399,11 @@ function typedFunction(node) {
|
|
|
4391
4399
|
|
|
4392
4400
|
// src/rules/no-long-comment.ts
|
|
4393
4401
|
var noLongCommentDocumentation = {
|
|
4394
|
-
summary: "Flag unusually large unstructured
|
|
4402
|
+
summary: "Flag unusually large unstructured JSDoc blocks in implementation code.",
|
|
4395
4403
|
rationale: "Large narrative comments become stale and obscure the local facts that belong beside the code.",
|
|
4396
4404
|
remediation: "Keep only durable local constraints and express the remaining behavior in code.",
|
|
4397
4405
|
category: "maintainability",
|
|
4398
|
-
limitations: ["
|
|
4406
|
+
limitations: ["Only JSDoc blocks are inspected; structured API docs, tests, scripts, generated files, and versioned dependencies are excluded."],
|
|
4399
4407
|
examples: [
|
|
4400
4408
|
{ id: "local-fact", title: "Keep a concise local fact", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// The cache is process local.\nconst cache = new Map();" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
|
|
4401
4409
|
{ id: "prose-wall", title: "Avoid an unstructured prose wall", outcome: "match", files: [{ path: "src/chart.ts", source: "/** One. Two. Three. Four. Five. Six. Seven. Eight. */\nconst chart = createChart();" }], focusPath: "src/chart.ts", expectedCount: 1, public: true }
|
|
@@ -4423,7 +4431,7 @@ var no_long_comment_default = createRule({
|
|
|
4423
4431
|
documentation: noLongCommentDocumentation,
|
|
4424
4432
|
meta: {
|
|
4425
4433
|
type: "suggestion",
|
|
4426
|
-
docs: { description: "Flag unusually large unstructured
|
|
4434
|
+
docs: { description: "Flag unusually large unstructured JSDoc blocks in implementation code." },
|
|
4427
4435
|
schema: [],
|
|
4428
4436
|
messages: {
|
|
4429
4437
|
tooLong: "Comment is an unusually large prose block \u2014 keep the local facts and clarify the code itself."
|
|
@@ -5368,6 +5376,7 @@ var MIN_DISTINCT_SCOPES = 2;
|
|
|
5368
5376
|
var PREVIEW_LENGTH = 40;
|
|
5369
5377
|
var SQL_KEYWORD_RE = /\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|VALUES|ON CONFLICT|RETURNING|GROUP BY|ORDER BY)\b/;
|
|
5370
5378
|
var IDENTIFIER_RE = /^[a-z_][a-z0-9_.]*$/;
|
|
5379
|
+
var URL_PATH_RE = /^\/(?=[^\s]*[A-Za-z0-9])[A-Za-z0-9._~!$&'()*+,;=:@%/?#{}\u005B\u005D-]+$/;
|
|
5371
5380
|
var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
|
|
5372
5381
|
AST_NODE_TYPES21.FunctionDeclaration,
|
|
5373
5382
|
AST_NODE_TYPES21.FunctionExpression,
|
|
@@ -5375,7 +5384,7 @@ var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
|
|
|
5375
5384
|
]);
|
|
5376
5385
|
var noRepeatedStringLiteralDocumentation = {
|
|
5377
5386
|
summary: "Disallow a long structured string literal repeated across functions; the copies drift when one is edited. Extract a module-level constant.",
|
|
5378
|
-
rationale: "Independent copies of a
|
|
5387
|
+
rationale: "Independent copies of a query, route template, or identifier can diverge and silently change behavior.",
|
|
5379
5388
|
remediation: "Extract the repeated value to one module-level constant and reference it from each function.",
|
|
5380
5389
|
category: "maintainability",
|
|
5381
5390
|
limitations: ["Test files, short strings, prose, substitutions, module sources, JSX attributes, and repetition within one function are excluded."],
|
|
@@ -5385,7 +5394,7 @@ var noRepeatedStringLiteralDocumentation = {
|
|
|
5385
5394
|
]
|
|
5386
5395
|
};
|
|
5387
5396
|
function isStructured(value) {
|
|
5388
|
-
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value);
|
|
5397
|
+
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
|
|
5389
5398
|
}
|
|
5390
5399
|
function preview(value) {
|
|
5391
5400
|
const oneLine = value.replaceAll("\n", " ").trim();
|
|
@@ -5404,8 +5413,9 @@ function isScaffolding(node) {
|
|
|
5404
5413
|
if (parent === void 0) {
|
|
5405
5414
|
return true;
|
|
5406
5415
|
}
|
|
5416
|
+
const isNonComputedPropertyKey = (parent.type === AST_NODE_TYPES21.Property || parent.type === AST_NODE_TYPES21.PropertyDefinition || parent.type === AST_NODE_TYPES21.MethodDefinition || parent.type === AST_NODE_TYPES21.AccessorProperty) && parent.key === node && !parent.computed;
|
|
5407
5417
|
const isRequireSource = parent.type === AST_NODE_TYPES21.CallExpression && parent.callee.type === AST_NODE_TYPES21.Identifier && parent.callee.name === "require";
|
|
5408
|
-
return parent.type === AST_NODE_TYPES21.ImportDeclaration || parent.type === AST_NODE_TYPES21.ImportExpression || parent.type === AST_NODE_TYPES21.ExportNamedDeclaration || parent.type === AST_NODE_TYPES21.ExportAllDeclaration || parent.type === AST_NODE_TYPES21.TSImportType || parent.type === AST_NODE_TYPES21.JSXAttribute || parent.type === AST_NODE_TYPES21.TSLiteralType || isRequireSource;
|
|
5418
|
+
return parent.type === AST_NODE_TYPES21.ImportDeclaration || parent.type === AST_NODE_TYPES21.ImportExpression || parent.type === AST_NODE_TYPES21.ExportNamedDeclaration || parent.type === AST_NODE_TYPES21.ExportAllDeclaration || parent.type === AST_NODE_TYPES21.TSImportType || parent.type === AST_NODE_TYPES21.JSXAttribute || parent.type === AST_NODE_TYPES21.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
|
|
5409
5419
|
}
|
|
5410
5420
|
var no_repeated_string_literal_default = createRule({
|
|
5411
5421
|
name: "no-repeated-string-literal",
|
|
@@ -5422,7 +5432,7 @@ var no_repeated_string_literal_default = createRule({
|
|
|
5422
5432
|
},
|
|
5423
5433
|
defaultOptions: [],
|
|
5424
5434
|
create(context) {
|
|
5425
|
-
if (isTestFile(context.filename)) {
|
|
5435
|
+
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
5426
5436
|
return {};
|
|
5427
5437
|
}
|
|
5428
5438
|
const occurrences = /* @__PURE__ */ new Map();
|
|
@@ -5742,10 +5752,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5742
5752
|
for (const comment of sourceCode.getAllComments()) {
|
|
5743
5753
|
if (comment.type !== "Block" || !comment.value.startsWith("*")) continue;
|
|
5744
5754
|
const { description, tags } = parseJsDoc(comment.value);
|
|
5745
|
-
|
|
5755
|
+
const describedText = [
|
|
5756
|
+
description,
|
|
5757
|
+
...tags.filter((tag) => tag.name === "description").map((tag) => tag.text)
|
|
5758
|
+
].filter((text) => text.length > 0).join("\n");
|
|
5759
|
+
if (DIRECTIVE_RE4.test(describedText)) continue;
|
|
5746
5760
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
5747
5761
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
5748
|
-
if (isProtected(
|
|
5762
|
+
if (isProtected(describedText)) continue;
|
|
5749
5763
|
const token = sourceCode.getTokenAfter(comment, { includeComments: false });
|
|
5750
5764
|
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
|
|
5751
5765
|
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
@@ -5758,13 +5772,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5758
5772
|
if (declaration === null) continue;
|
|
5759
5773
|
const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
|
|
5760
5774
|
const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
|
|
5761
|
-
if (
|
|
5775
|
+
if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
|
|
5762
5776
|
continue;
|
|
5763
5777
|
}
|
|
5778
|
+
if ((paramTags.length > 0 || returnTags.length > 0) && documentsTypedFunction(sourceCode, comment)) continue;
|
|
5764
5779
|
const nameTokens = tokensOf([declaration.name]);
|
|
5765
5780
|
const paramTokens = tokensOf(declaration.params);
|
|
5766
5781
|
const known = /* @__PURE__ */ new Set([...nameTokens, ...paramTokens]);
|
|
5767
|
-
let addsNothing = covered(
|
|
5782
|
+
let addsNothing = covered(describedText, known);
|
|
5768
5783
|
for (const tag of paramTags) {
|
|
5769
5784
|
const text = tag.text.replace(/^\{[^}]*\}\s*/, "");
|
|
5770
5785
|
const match = /^\[?([A-Za-z_$][\w.$]*)\]?\s*-?\s*([\s\S]*)$/.exec(text);
|
|
@@ -5772,7 +5787,13 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5772
5787
|
addsNothing = false;
|
|
5773
5788
|
break;
|
|
5774
5789
|
}
|
|
5775
|
-
const
|
|
5790
|
+
const path = match[1] ?? "";
|
|
5791
|
+
const root = path.split(".")[0] ?? "";
|
|
5792
|
+
if (!declaration.params.includes(root)) {
|
|
5793
|
+
addsNothing = false;
|
|
5794
|
+
break;
|
|
5795
|
+
}
|
|
5796
|
+
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...nameTokens]);
|
|
5776
5797
|
if (!covered(match[2] ?? "", own)) {
|
|
5777
5798
|
addsNothing = false;
|
|
5778
5799
|
break;
|
|
@@ -6597,10 +6618,10 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
6597
6618
|
return false;
|
|
6598
6619
|
}
|
|
6599
6620
|
if (matcher.isLoggingCall(current)) {
|
|
6600
|
-
return
|
|
6621
|
+
return caughtName === null || argsIncludeBinding(current.arguments, caughtName);
|
|
6601
6622
|
}
|
|
6602
6623
|
const name = calleeName2(current.callee);
|
|
6603
|
-
return name !== null && REPORT_NAME_RE.test(name) && argsIncludeBinding(current.arguments, caughtName);
|
|
6624
|
+
return name !== null && REPORT_NAME_RE.test(name) && (caughtName === null || argsIncludeBinding(current.arguments, caughtName));
|
|
6604
6625
|
});
|
|
6605
6626
|
}
|
|
6606
6627
|
return {
|
|
@@ -8069,7 +8090,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
8069
8090
|
claimed.add(comment);
|
|
8070
8091
|
commented += 1;
|
|
8071
8092
|
const body2 = commentBody(comment);
|
|
8072
|
-
if (body2.length === 0 || carriesValue(body2)) continue;
|
|
8093
|
+
if (body2.length === 0 || carriesValue(body2) || isTagsOnly(body2)) continue;
|
|
8073
8094
|
if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
|
|
8074
8095
|
restated += 1;
|
|
8075
8096
|
}
|
|
@@ -13987,7 +14008,7 @@ var rules = {
|
|
|
13987
14008
|
};
|
|
13988
14009
|
var meta = {
|
|
13989
14010
|
name: "@sarj/eslint-plugin",
|
|
13990
|
-
version: "13.
|
|
14011
|
+
version: "13.1.0"
|
|
13991
14012
|
};
|
|
13992
14013
|
var applicationOnlyRules = [
|
|
13993
14014
|
"no-restricted-library-load",
|