@sarj/eslint-plugin 2.5.0 → 2.7.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/dist/index.cjs +362 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.js +368 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -213,6 +213,8 @@ var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
|
|
|
213
213
|
// src/rules/no-comment-cruft.ts
|
|
214
214
|
import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
|
|
215
215
|
var LEADING_PREAMBLE_MIN = 4;
|
|
216
|
+
var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S|^step\s+\d+\b/i;
|
|
217
|
+
var META_COMMENTARY_RE = /\b(?:for now|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;
|
|
216
218
|
var DIRECTIVE_RE = /^(eslint\b|eslint-|@ts-|prettier-ignore|prettier\b|biome-|c8\b|v8\b|istanbul\b|@type\b|@vite|webpack|<reference|<amd|global\b|noinspection|todo\b|fixme\b|hack\b|xxx\b)/i;
|
|
217
219
|
var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
|
|
218
220
|
var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
|
|
@@ -251,6 +253,13 @@ function isProse(text) {
|
|
|
251
253
|
}
|
|
252
254
|
return false;
|
|
253
255
|
}
|
|
256
|
+
function isRedundantNarration(body) {
|
|
257
|
+
const t = body.trim();
|
|
258
|
+
if (!t || looksLikeCode(t) || hasPseudocode(t)) return false;
|
|
259
|
+
if (STEP_NARRATION_RE.test(t)) return true;
|
|
260
|
+
if (META_COMMENTARY_RE.test(t)) return true;
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
254
263
|
function hasCommentedOutCode(texts, precedingProse) {
|
|
255
264
|
for (let i = 0; i < texts.length; i++) {
|
|
256
265
|
const line = texts[i];
|
|
@@ -276,7 +285,8 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
|
|
|
276
285
|
messages: {
|
|
277
286
|
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
278
287
|
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
279
|
-
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
|
|
288
|
+
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
|
|
289
|
+
redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting."
|
|
280
290
|
}
|
|
281
291
|
},
|
|
282
292
|
defaultOptions: [],
|
|
@@ -331,6 +341,13 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
|
|
|
331
341
|
const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
|
|
332
342
|
if (hasCommentedOutCode(texts, precedingProse)) {
|
|
333
343
|
context.report({ node: comment, messageId: "commentedOutCode" });
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (comment.type === "Line" && texts.length === 1) {
|
|
347
|
+
const body = texts[0];
|
|
348
|
+
if (body !== void 0 && isRedundantNarration(body)) {
|
|
349
|
+
context.report({ node: comment, messageId: "redundantNarration" });
|
|
350
|
+
}
|
|
334
351
|
}
|
|
335
352
|
}
|
|
336
353
|
reportLeadingPreamble(comments, firstCodeLine);
|
|
@@ -2738,16 +2755,299 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
|
|
|
2738
2755
|
}
|
|
2739
2756
|
});
|
|
2740
2757
|
|
|
2758
|
+
// src/rules/no-silent-promise-catch.ts
|
|
2759
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES12, ESLintUtils as ESLintUtils22 } from "@typescript-eslint/utils";
|
|
2760
|
+
|
|
2761
|
+
// src/rules/_paths.ts
|
|
2762
|
+
var TEST_FILE_RE = /(\.(test|spec)\.)|([\\/]__tests__[\\/])/;
|
|
2763
|
+
var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
|
|
2764
|
+
function isTestFile(filename) {
|
|
2765
|
+
return TEST_FILE_RE.test(filename);
|
|
2766
|
+
}
|
|
2767
|
+
function isScriptFile(filename) {
|
|
2768
|
+
return SCRIPT_FILE_RE.test(filename);
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
// src/rules/no-silent-promise-catch.ts
|
|
2772
|
+
function isBodyParseCall(node) {
|
|
2773
|
+
return node.type === AST_NODE_TYPES12.CallExpression && node.arguments.length === 0 && node.callee.type === AST_NODE_TYPES12.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES12.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
|
|
2774
|
+
}
|
|
2775
|
+
function isSilentExpression(node) {
|
|
2776
|
+
switch (node.type) {
|
|
2777
|
+
case AST_NODE_TYPES12.Literal:
|
|
2778
|
+
return !("regex" in node);
|
|
2779
|
+
case AST_NODE_TYPES12.Identifier:
|
|
2780
|
+
return node.name === "undefined";
|
|
2781
|
+
case AST_NODE_TYPES12.UnaryExpression:
|
|
2782
|
+
return node.operator === "void" && node.argument.type === AST_NODE_TYPES12.Literal;
|
|
2783
|
+
case AST_NODE_TYPES12.ObjectExpression:
|
|
2784
|
+
return node.properties.length === 0;
|
|
2785
|
+
case AST_NODE_TYPES12.ArrayExpression:
|
|
2786
|
+
return node.elements.length === 0;
|
|
2787
|
+
case AST_NODE_TYPES12.TSAsExpression:
|
|
2788
|
+
return isSilentExpression(node.expression);
|
|
2789
|
+
default:
|
|
2790
|
+
return false;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
function isSilentHandler(handler) {
|
|
2794
|
+
const body = handler.body;
|
|
2795
|
+
if (body.type !== AST_NODE_TYPES12.BlockStatement) {
|
|
2796
|
+
return isSilentExpression(body);
|
|
2797
|
+
}
|
|
2798
|
+
if (body.body.length === 0) {
|
|
2799
|
+
return true;
|
|
2800
|
+
}
|
|
2801
|
+
if (body.body.length === 1) {
|
|
2802
|
+
const only = body.body[0];
|
|
2803
|
+
if (only !== void 0 && only.type === AST_NODE_TYPES12.ReturnStatement) {
|
|
2804
|
+
return only.argument === null || isSilentExpression(only.argument);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
|
|
2810
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2811
|
+
)({
|
|
2812
|
+
name: "no-silent-promise-catch",
|
|
2813
|
+
meta: {
|
|
2814
|
+
type: "problem",
|
|
2815
|
+
docs: {
|
|
2816
|
+
description: "Disallow `.catch()` handlers that silently swallow the rejection (e.g. `.catch(() => null)`); log, rethrow, or handle the error."
|
|
2817
|
+
},
|
|
2818
|
+
schema: [],
|
|
2819
|
+
messages: {
|
|
2820
|
+
silentCatch: "This `.catch()` swallows the rejection without logging, rethrowing, or handling it \u2014 failures become invisible and callers get an indistinguishable sentinel. Log the error (and only then map to a fallback), or let it propagate."
|
|
2821
|
+
}
|
|
2822
|
+
},
|
|
2823
|
+
defaultOptions: [],
|
|
2824
|
+
create(context) {
|
|
2825
|
+
if (isTestFile(context.filename)) {
|
|
2826
|
+
return {};
|
|
2827
|
+
}
|
|
2828
|
+
return {
|
|
2829
|
+
CallExpression(node) {
|
|
2830
|
+
if (node.callee.type !== AST_NODE_TYPES12.MemberExpression || node.callee.computed || node.callee.property.type !== AST_NODE_TYPES12.Identifier || node.callee.property.name !== "catch") {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
if (isBodyParseCall(node.callee.object)) {
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
if (node.arguments.length !== 1) {
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
const handler = node.arguments[0];
|
|
2840
|
+
if (handler === void 0 || handler.type !== AST_NODE_TYPES12.ArrowFunctionExpression && handler.type !== AST_NODE_TYPES12.FunctionExpression) {
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
if (isSilentHandler(handler)) {
|
|
2844
|
+
context.report({ node, messageId: "silentCatch" });
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
|
|
2851
|
+
// src/rules/require-fetch-timeout.ts
|
|
2852
|
+
import {
|
|
2853
|
+
AST_NODE_TYPES as AST_NODE_TYPES13,
|
|
2854
|
+
ASTUtils,
|
|
2855
|
+
ESLintUtils as ESLintUtils23
|
|
2856
|
+
} from "@typescript-eslint/utils";
|
|
2857
|
+
var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
|
|
2858
|
+
"globalThis",
|
|
2859
|
+
"window",
|
|
2860
|
+
"self"
|
|
2861
|
+
]);
|
|
2862
|
+
function matchesAnyPattern2(filename, patterns) {
|
|
2863
|
+
for (const pattern of patterns) {
|
|
2864
|
+
const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
|
|
2865
|
+
if (new RegExp(`^${regexSource}$`).test(filename)) {
|
|
2866
|
+
return true;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
function initProvablyLacksSignal(init) {
|
|
2872
|
+
if (init.type !== AST_NODE_TYPES13.ObjectExpression) {
|
|
2873
|
+
return false;
|
|
2874
|
+
}
|
|
2875
|
+
for (const prop of init.properties) {
|
|
2876
|
+
if (prop.type === AST_NODE_TYPES13.SpreadElement) {
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
if (prop.key.type === AST_NODE_TYPES13.Identifier && prop.key.name === "signal" || prop.key.type === AST_NODE_TYPES13.Literal && prop.key.value === "signal") {
|
|
2880
|
+
return false;
|
|
2881
|
+
}
|
|
2882
|
+
if (prop.computed) {
|
|
2883
|
+
return false;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return true;
|
|
2887
|
+
}
|
|
2888
|
+
function isStringish(node) {
|
|
2889
|
+
return node.type === AST_NODE_TYPES13.Literal && typeof node.value === "string" || node.type === AST_NODE_TYPES13.TemplateLiteral;
|
|
2890
|
+
}
|
|
2891
|
+
var require_fetch_timeout_default = ESLintUtils23.RuleCreator(
|
|
2892
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2893
|
+
)({
|
|
2894
|
+
name: "require-fetch-timeout",
|
|
2895
|
+
meta: {
|
|
2896
|
+
type: "problem",
|
|
2897
|
+
docs: {
|
|
2898
|
+
description: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever."
|
|
2899
|
+
},
|
|
2900
|
+
schema: [
|
|
2901
|
+
{
|
|
2902
|
+
type: "object",
|
|
2903
|
+
additionalProperties: false,
|
|
2904
|
+
properties: {
|
|
2905
|
+
allowIn: {
|
|
2906
|
+
description: "Glob patterns for wrapper modules exempt from the rule. Matched against the ABSOLUTE file path, so anchor with a `**/` prefix (e.g. `**/http-client.ts`).",
|
|
2907
|
+
type: "array",
|
|
2908
|
+
items: { type: "string" }
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
],
|
|
2913
|
+
messages: {
|
|
2914
|
+
missingSignal: "This `fetch()` has no abort `signal` \u2014 a stalled upstream will hang it forever. Pass `{ signal: AbortSignal.timeout(ms) }` or a signal from an AbortController."
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2917
|
+
defaultOptions: [{}],
|
|
2918
|
+
create(context, [optionsArg]) {
|
|
2919
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename)) {
|
|
2920
|
+
return {};
|
|
2921
|
+
}
|
|
2922
|
+
const allowIn = optionsArg?.allowIn ?? [];
|
|
2923
|
+
if (allowIn.length > 0 && matchesAnyPattern2(context.filename, allowIn)) {
|
|
2924
|
+
return {};
|
|
2925
|
+
}
|
|
2926
|
+
function resolvesToGlobal(identifier) {
|
|
2927
|
+
const scope = context.sourceCode.getScope(identifier);
|
|
2928
|
+
const variable = ASTUtils.findVariable(scope, identifier.name);
|
|
2929
|
+
return variable === null || variable.defs.length === 0;
|
|
2930
|
+
}
|
|
2931
|
+
function isGlobalFetchCall(callee) {
|
|
2932
|
+
if (callee.type === AST_NODE_TYPES13.Identifier) {
|
|
2933
|
+
return callee.name === "fetch" && resolvesToGlobal(callee);
|
|
2934
|
+
}
|
|
2935
|
+
return callee.type === AST_NODE_TYPES13.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES13.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES13.Identifier && GLOBAL_OBJECTS.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
2936
|
+
}
|
|
2937
|
+
return {
|
|
2938
|
+
CallExpression(node) {
|
|
2939
|
+
if (!isGlobalFetchCall(node.callee)) {
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
const [first, init] = node.arguments;
|
|
2943
|
+
if (node.arguments.length === 1 && first !== void 0 && !isStringish(first)) {
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
if (init === void 0 || initProvablyLacksSignal(init)) {
|
|
2947
|
+
context.report({ node, messageId: "missingSignal" });
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
});
|
|
2953
|
+
|
|
2954
|
+
// src/rules/require-schema-validate-search.ts
|
|
2955
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES14, ESLintUtils as ESLintUtils24 } from "@typescript-eslint/utils";
|
|
2956
|
+
var VALIDATOR_METHODS = /* @__PURE__ */ new Set([
|
|
2957
|
+
"parse",
|
|
2958
|
+
"safeParse",
|
|
2959
|
+
"decode"
|
|
2960
|
+
]);
|
|
2961
|
+
function isConstTypeAnnotation(typeAnnotation) {
|
|
2962
|
+
return typeAnnotation.type === AST_NODE_TYPES14.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES14.Identifier && typeAnnotation.typeName.name === "const";
|
|
2963
|
+
}
|
|
2964
|
+
function isValidatorCall(node) {
|
|
2965
|
+
return node.callee.type === AST_NODE_TYPES14.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES14.Identifier && VALIDATOR_METHODS.has(node.callee.property.name);
|
|
2966
|
+
}
|
|
2967
|
+
function findCastExpression(node, insideValidatorArg) {
|
|
2968
|
+
if ((node.type === AST_NODE_TYPES14.TSAsExpression || node.type === AST_NODE_TYPES14.TSTypeAssertion) && !isConstTypeAnnotation(node.typeAnnotation) && !insideValidatorArg) {
|
|
2969
|
+
return node;
|
|
2970
|
+
}
|
|
2971
|
+
if (node.type === AST_NODE_TYPES14.CallExpression && isValidatorCall(node)) {
|
|
2972
|
+
const inCallee = findCastExpression(node.callee, insideValidatorArg);
|
|
2973
|
+
if (inCallee !== null) {
|
|
2974
|
+
return inCallee;
|
|
2975
|
+
}
|
|
2976
|
+
for (const arg of node.arguments) {
|
|
2977
|
+
const found = findCastExpression(arg, true);
|
|
2978
|
+
if (found !== null) {
|
|
2979
|
+
return found;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
return null;
|
|
2983
|
+
}
|
|
2984
|
+
for (const key of Object.keys(node)) {
|
|
2985
|
+
if (key === "parent") {
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
const value = node[key];
|
|
2989
|
+
const children = Array.isArray(value) ? value : [value];
|
|
2990
|
+
for (const child of children) {
|
|
2991
|
+
if (child !== null && typeof child === "object" && "type" in child && typeof child.type === "string") {
|
|
2992
|
+
const found = findCastExpression(
|
|
2993
|
+
child,
|
|
2994
|
+
insideValidatorArg
|
|
2995
|
+
);
|
|
2996
|
+
if (found !== null) {
|
|
2997
|
+
return found;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return null;
|
|
3003
|
+
}
|
|
3004
|
+
var require_schema_validate_search_default = ESLintUtils24.RuleCreator(
|
|
3005
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3006
|
+
)({
|
|
3007
|
+
name: "require-schema-validate-search",
|
|
3008
|
+
meta: {
|
|
3009
|
+
type: "problem",
|
|
3010
|
+
docs: {
|
|
3011
|
+
description: "Disallow `as` casts inside hand-rolled `validateSearch` functions; use a schema validator (e.g. zodValidator) so search params are validated at runtime."
|
|
3012
|
+
},
|
|
3013
|
+
schema: [],
|
|
3014
|
+
messages: {
|
|
3015
|
+
castInValidateSearch: "This `validateSearch` asserts the search-param shape with `as` instead of validating it \u2014 malformed query params flow through typed as clean data. Use a schema validator (e.g. `zodValidator(searchSchema)` or `searchSchema.parse`) instead of casting."
|
|
3016
|
+
}
|
|
3017
|
+
},
|
|
3018
|
+
defaultOptions: [],
|
|
3019
|
+
create(context) {
|
|
3020
|
+
if (isTestFile(context.filename)) {
|
|
3021
|
+
return {};
|
|
3022
|
+
}
|
|
3023
|
+
return {
|
|
3024
|
+
Property(node) {
|
|
3025
|
+
const isValidateSearchKey = !node.computed && node.key.type === AST_NODE_TYPES14.Identifier && node.key.name === "validateSearch" || node.key.type === AST_NODE_TYPES14.Literal && node.key.value === "validateSearch";
|
|
3026
|
+
if (!isValidateSearchKey) {
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
if (node.value.type !== AST_NODE_TYPES14.ArrowFunctionExpression && node.value.type !== AST_NODE_TYPES14.FunctionExpression) {
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
const cast = findCastExpression(node.value.body, false);
|
|
3033
|
+
if (cast !== null) {
|
|
3034
|
+
context.report({ node: cast, messageId: "castInValidateSearch" });
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
|
|
2741
3041
|
// src/rules/no-fat-try-blocks.ts
|
|
2742
3042
|
import {
|
|
2743
|
-
ESLintUtils as
|
|
2744
|
-
AST_NODE_TYPES as
|
|
3043
|
+
ESLintUtils as ESLintUtils25,
|
|
3044
|
+
AST_NODE_TYPES as AST_NODE_TYPES15
|
|
2745
3045
|
} from "@typescript-eslint/utils";
|
|
2746
3046
|
var MAX_TRY_BODY_STATEMENTS = 3;
|
|
2747
3047
|
var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
3048
|
+
AST_NODE_TYPES15.FunctionDeclaration,
|
|
3049
|
+
AST_NODE_TYPES15.FunctionExpression,
|
|
3050
|
+
AST_NODE_TYPES15.ArrowFunctionExpression
|
|
2751
3051
|
]);
|
|
2752
3052
|
var PURE_METHODS = /* @__PURE__ */ new Set([
|
|
2753
3053
|
"map",
|
|
@@ -2845,20 +3145,20 @@ function isNode4(value) {
|
|
|
2845
3145
|
}
|
|
2846
3146
|
function isPureCall(node) {
|
|
2847
3147
|
const callee = node.callee;
|
|
2848
|
-
if (callee.type !==
|
|
3148
|
+
if (callee.type !== AST_NODE_TYPES15.MemberExpression) {
|
|
2849
3149
|
return false;
|
|
2850
3150
|
}
|
|
2851
3151
|
const property = callee.property;
|
|
2852
|
-
if (property.type !==
|
|
3152
|
+
if (property.type !== AST_NODE_TYPES15.Identifier) {
|
|
2853
3153
|
return false;
|
|
2854
3154
|
}
|
|
2855
|
-
if (callee.object.type ===
|
|
3155
|
+
if (callee.object.type === AST_NODE_TYPES15.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
|
|
2856
3156
|
return true;
|
|
2857
3157
|
}
|
|
2858
3158
|
return PURE_METHODS.has(property.name);
|
|
2859
3159
|
}
|
|
2860
3160
|
function isPureNew(node) {
|
|
2861
|
-
return node.callee.type ===
|
|
3161
|
+
return node.callee.type === AST_NODE_TYPES15.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
|
|
2862
3162
|
}
|
|
2863
3163
|
function subtreeMatches(stmt, predicate) {
|
|
2864
3164
|
let found = false;
|
|
@@ -2895,14 +3195,14 @@ function subtreeMatches(stmt, predicate) {
|
|
|
2895
3195
|
visit(stmt);
|
|
2896
3196
|
return found;
|
|
2897
3197
|
}
|
|
2898
|
-
var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type ===
|
|
3198
|
+
var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES15.AwaitExpression);
|
|
2899
3199
|
var hasThrowingCallOrNew = (stmt) => subtreeMatches(
|
|
2900
3200
|
stmt,
|
|
2901
|
-
(n) => n.type ===
|
|
3201
|
+
(n) => n.type === AST_NODE_TYPES15.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES15.NewExpression && !isPureNew(n)
|
|
2902
3202
|
);
|
|
2903
3203
|
function unwrap2(expr) {
|
|
2904
3204
|
let current = expr;
|
|
2905
|
-
while (current.type ===
|
|
3205
|
+
while (current.type === AST_NODE_TYPES15.ChainExpression || current.type === AST_NODE_TYPES15.TSNonNullExpression) {
|
|
2906
3206
|
current = current.expression;
|
|
2907
3207
|
}
|
|
2908
3208
|
return current;
|
|
@@ -2911,7 +3211,7 @@ function canThrow(stmt) {
|
|
|
2911
3211
|
if (hasAwait(stmt)) {
|
|
2912
3212
|
return true;
|
|
2913
3213
|
}
|
|
2914
|
-
if (stmt.type ===
|
|
3214
|
+
if (stmt.type === AST_NODE_TYPES15.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES15.CallExpression) {
|
|
2915
3215
|
return false;
|
|
2916
3216
|
}
|
|
2917
3217
|
return hasThrowingCallOrNew(stmt);
|
|
@@ -2922,9 +3222,9 @@ function handlerRethrows(handler) {
|
|
|
2922
3222
|
}
|
|
2923
3223
|
const body = handler.body.body;
|
|
2924
3224
|
const last = body[body.length - 1];
|
|
2925
|
-
return last !== void 0 && last.type ===
|
|
3225
|
+
return last !== void 0 && last.type === AST_NODE_TYPES15.ThrowStatement;
|
|
2926
3226
|
}
|
|
2927
|
-
var no_fat_try_blocks_default =
|
|
3227
|
+
var no_fat_try_blocks_default = ESLintUtils25.RuleCreator(
|
|
2928
3228
|
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2929
3229
|
)({
|
|
2930
3230
|
name: "no-fat-try-blocks",
|
|
@@ -2965,7 +3265,7 @@ var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
|
|
|
2965
3265
|
});
|
|
2966
3266
|
|
|
2967
3267
|
// src/rules/no-secret-in-log.ts
|
|
2968
|
-
import { ESLintUtils as
|
|
3268
|
+
import { ESLintUtils as ESLintUtils26 } from "@typescript-eslint/utils";
|
|
2969
3269
|
var LOG_METHODS2 = /* @__PURE__ */ new Set([
|
|
2970
3270
|
"debug",
|
|
2971
3271
|
"info",
|
|
@@ -3163,7 +3463,7 @@ function propertyKeyName2(prop) {
|
|
|
3163
3463
|
}
|
|
3164
3464
|
return null;
|
|
3165
3465
|
}
|
|
3166
|
-
var no_secret_in_log_default =
|
|
3466
|
+
var no_secret_in_log_default = ESLintUtils26.RuleCreator(
|
|
3167
3467
|
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3168
3468
|
)({
|
|
3169
3469
|
name: "no-secret-in-log",
|
|
@@ -3231,15 +3531,15 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
|
|
|
3231
3531
|
});
|
|
3232
3532
|
|
|
3233
3533
|
// src/rules/no-unsafe-cast.ts
|
|
3234
|
-
import { ESLintUtils as
|
|
3235
|
-
import { AST_NODE_TYPES as
|
|
3534
|
+
import { ESLintUtils as ESLintUtils27 } from "@typescript-eslint/utils";
|
|
3535
|
+
import { AST_NODE_TYPES as AST_NODE_TYPES16 } from "@typescript-eslint/utils";
|
|
3236
3536
|
function isAnyAnnotation(node) {
|
|
3237
|
-
return node.type ===
|
|
3537
|
+
return node.type === AST_NODE_TYPES16.TSAnyKeyword;
|
|
3238
3538
|
}
|
|
3239
3539
|
function isConstAssertion(typeAnnotation) {
|
|
3240
|
-
return typeAnnotation.type ===
|
|
3540
|
+
return typeAnnotation.type === AST_NODE_TYPES16.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES16.Identifier && typeAnnotation.typeName.name === "const";
|
|
3241
3541
|
}
|
|
3242
|
-
var no_unsafe_cast_default =
|
|
3542
|
+
var no_unsafe_cast_default = ESLintUtils27.RuleCreator(
|
|
3243
3543
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3244
3544
|
)({
|
|
3245
3545
|
name: "no-unsafe-cast",
|
|
@@ -3265,7 +3565,7 @@ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
|
|
|
3265
3565
|
return;
|
|
3266
3566
|
}
|
|
3267
3567
|
const inner = node.expression;
|
|
3268
|
-
if (inner.type ===
|
|
3568
|
+
if (inner.type === AST_NODE_TYPES16.TSAsExpression || inner.type === AST_NODE_TYPES16.TSTypeAssertion) {
|
|
3269
3569
|
context.report({ node, messageId: "doubleCast" });
|
|
3270
3570
|
}
|
|
3271
3571
|
}
|
|
@@ -3278,8 +3578,8 @@ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
|
|
|
3278
3578
|
|
|
3279
3579
|
// src/rules/prefer-string-literal-union.ts
|
|
3280
3580
|
import {
|
|
3281
|
-
ESLintUtils as
|
|
3282
|
-
AST_NODE_TYPES as
|
|
3581
|
+
ESLintUtils as ESLintUtils28,
|
|
3582
|
+
AST_NODE_TYPES as AST_NODE_TYPES17
|
|
3283
3583
|
} from "@typescript-eslint/utils";
|
|
3284
3584
|
import * as ts from "typescript";
|
|
3285
3585
|
var CHOICE_TOKENS = /* @__PURE__ */ new Set([
|
|
@@ -3323,19 +3623,19 @@ function isChoiceLikeName(name) {
|
|
|
3323
3623
|
return CHOICE_TOKENS.has(lastWord(name));
|
|
3324
3624
|
}
|
|
3325
3625
|
function keyName(key) {
|
|
3326
|
-
if (key.type ===
|
|
3626
|
+
if (key.type === AST_NODE_TYPES17.Identifier) {
|
|
3327
3627
|
return key.name;
|
|
3328
3628
|
}
|
|
3329
|
-
if (key.type ===
|
|
3629
|
+
if (key.type === AST_NODE_TYPES17.Literal && typeof key.value === "string") {
|
|
3330
3630
|
return key.value;
|
|
3331
3631
|
}
|
|
3332
3632
|
return null;
|
|
3333
3633
|
}
|
|
3334
3634
|
function isStringLiteralMember(t) {
|
|
3335
|
-
return t.type ===
|
|
3635
|
+
return t.type === AST_NODE_TYPES17.TSLiteralType && t.literal.type === AST_NODE_TYPES17.Literal && typeof t.literal.value === "string";
|
|
3336
3636
|
}
|
|
3337
3637
|
function isStringLiteralUnion(node) {
|
|
3338
|
-
if (node?.type !==
|
|
3638
|
+
if (node?.type !== AST_NODE_TYPES17.TSUnionType) {
|
|
3339
3639
|
return false;
|
|
3340
3640
|
}
|
|
3341
3641
|
return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
|
|
@@ -3364,12 +3664,12 @@ function bindingSourceExpression(decl) {
|
|
|
3364
3664
|
return ts.isForOfStatement(node) ? node.expression : node.initializer;
|
|
3365
3665
|
}
|
|
3366
3666
|
function refKey(node) {
|
|
3367
|
-
if (node.type ===
|
|
3667
|
+
if (node.type === AST_NODE_TYPES17.Identifier) {
|
|
3368
3668
|
return node.name;
|
|
3369
3669
|
}
|
|
3370
|
-
if (node.type ===
|
|
3670
|
+
if (node.type === AST_NODE_TYPES17.MemberExpression && !node.computed) {
|
|
3371
3671
|
const inner = refKey(node.object);
|
|
3372
|
-
if (inner === null || node.property.type !==
|
|
3672
|
+
if (inner === null || node.property.type !== AST_NODE_TYPES17.Identifier) {
|
|
3373
3673
|
return null;
|
|
3374
3674
|
}
|
|
3375
3675
|
return `${inner}.${node.property.name}`;
|
|
@@ -3377,12 +3677,12 @@ function refKey(node) {
|
|
|
3377
3677
|
return null;
|
|
3378
3678
|
}
|
|
3379
3679
|
function strLiteral(node) {
|
|
3380
|
-
if (node.type ===
|
|
3680
|
+
if (node.type === AST_NODE_TYPES17.Literal && typeof node.value === "string") {
|
|
3381
3681
|
return node.value;
|
|
3382
3682
|
}
|
|
3383
3683
|
return null;
|
|
3384
3684
|
}
|
|
3385
|
-
var prefer_string_literal_union_default =
|
|
3685
|
+
var prefer_string_literal_union_default = ESLintUtils28.RuleCreator(
|
|
3386
3686
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3387
3687
|
)({
|
|
3388
3688
|
name: "prefer-string-literal-union",
|
|
@@ -3406,7 +3706,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
|
|
|
3406
3706
|
}
|
|
3407
3707
|
let services;
|
|
3408
3708
|
try {
|
|
3409
|
-
services =
|
|
3709
|
+
services = ESLintUtils28.getParserServices(context);
|
|
3410
3710
|
} catch {
|
|
3411
3711
|
services = null;
|
|
3412
3712
|
}
|
|
@@ -3490,7 +3790,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
|
|
|
3490
3790
|
containersWithUnion.add(container);
|
|
3491
3791
|
return;
|
|
3492
3792
|
}
|
|
3493
|
-
if (typeNode?.type !==
|
|
3793
|
+
if (typeNode?.type !== AST_NODE_TYPES17.TSStringKeyword) {
|
|
3494
3794
|
return;
|
|
3495
3795
|
}
|
|
3496
3796
|
const name = keyName(key);
|
|
@@ -3578,10 +3878,10 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
|
|
|
3578
3878
|
}
|
|
3579
3879
|
};
|
|
3580
3880
|
function refKeyText(node) {
|
|
3581
|
-
if (node.type ===
|
|
3881
|
+
if (node.type === AST_NODE_TYPES17.BinaryExpression) {
|
|
3582
3882
|
return refKey(node.left) ?? refKey(node.right) ?? "value";
|
|
3583
3883
|
}
|
|
3584
|
-
if (node.type ===
|
|
3884
|
+
if (node.type === AST_NODE_TYPES17.SwitchStatement) {
|
|
3585
3885
|
return refKey(node.discriminant) ?? "value";
|
|
3586
3886
|
}
|
|
3587
3887
|
return "value";
|
|
@@ -3590,7 +3890,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
|
|
|
3590
3890
|
});
|
|
3591
3891
|
|
|
3592
3892
|
// src/rules/single-public-export.ts
|
|
3593
|
-
import { ESLintUtils as
|
|
3893
|
+
import { ESLintUtils as ESLintUtils29, AST_NODE_TYPES as AST_NODE_TYPES18 } from "@typescript-eslint/utils";
|
|
3594
3894
|
var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
|
|
3595
3895
|
"util",
|
|
3596
3896
|
"utils",
|
|
@@ -3613,7 +3913,7 @@ var ACRONYM_OVERRIDES = [
|
|
|
3613
3913
|
[/gRPC/g, "Grpc"]
|
|
3614
3914
|
];
|
|
3615
3915
|
var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
|
|
3616
|
-
var
|
|
3916
|
+
var TEST_FILE_RE2 = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
3617
3917
|
var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
|
|
3618
3918
|
var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
|
|
3619
3919
|
var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
|
|
@@ -3624,12 +3924,12 @@ var kebabCase2 = (name) => {
|
|
|
3624
3924
|
}
|
|
3625
3925
|
return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
|
|
3626
3926
|
};
|
|
3627
|
-
var isFunctionExpression = (node) => node !== null && (node.type ===
|
|
3927
|
+
var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES18.ArrowFunctionExpression || node.type === AST_NODE_TYPES18.FunctionExpression);
|
|
3628
3928
|
var functionConstName = (decl) => {
|
|
3629
3929
|
if (decl.declarations.length !== 1) return null;
|
|
3630
3930
|
const [declarator] = decl.declarations;
|
|
3631
3931
|
if (declarator === void 0) return null;
|
|
3632
|
-
if (declarator.id.type !==
|
|
3932
|
+
if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return null;
|
|
3633
3933
|
if (!isFunctionExpression(declarator.init)) return null;
|
|
3634
3934
|
return declarator.id.name;
|
|
3635
3935
|
};
|
|
@@ -3643,20 +3943,20 @@ var summarizeExports = (body) => {
|
|
|
3643
3943
|
};
|
|
3644
3944
|
for (const statement of body) {
|
|
3645
3945
|
switch (statement.type) {
|
|
3646
|
-
case
|
|
3946
|
+
case AST_NODE_TYPES18.ExportAllDeclaration:
|
|
3647
3947
|
hasReExport = true;
|
|
3648
3948
|
break;
|
|
3649
|
-
case
|
|
3949
|
+
case AST_NODE_TYPES18.ExportDefaultDeclaration: {
|
|
3650
3950
|
names += 1;
|
|
3651
3951
|
const decl = statement.declaration;
|
|
3652
|
-
if (decl.type ===
|
|
3952
|
+
if (decl.type === AST_NODE_TYPES18.FunctionDeclaration && decl.id !== null) {
|
|
3653
3953
|
candidate = { name: decl.id.name, node: statement };
|
|
3654
|
-
} else if (decl.type ===
|
|
3954
|
+
} else if (decl.type === AST_NODE_TYPES18.ClassDeclaration && decl.id !== null) {
|
|
3655
3955
|
candidate = { name: decl.id.name, node: statement };
|
|
3656
3956
|
}
|
|
3657
3957
|
break;
|
|
3658
3958
|
}
|
|
3659
|
-
case
|
|
3959
|
+
case AST_NODE_TYPES18.ExportNamedDeclaration: {
|
|
3660
3960
|
if (statement.source !== null) {
|
|
3661
3961
|
hasReExport = true;
|
|
3662
3962
|
break;
|
|
@@ -3667,15 +3967,15 @@ var summarizeExports = (body) => {
|
|
|
3667
3967
|
break;
|
|
3668
3968
|
}
|
|
3669
3969
|
switch (decl.type) {
|
|
3670
|
-
case
|
|
3970
|
+
case AST_NODE_TYPES18.FunctionDeclaration:
|
|
3671
3971
|
if (decl.id !== null) addCandidate(decl.id.name, statement);
|
|
3672
3972
|
else names += 1;
|
|
3673
3973
|
break;
|
|
3674
|
-
case
|
|
3974
|
+
case AST_NODE_TYPES18.ClassDeclaration:
|
|
3675
3975
|
if (decl.id !== null) addCandidate(decl.id.name, statement);
|
|
3676
3976
|
else names += 1;
|
|
3677
3977
|
break;
|
|
3678
|
-
case
|
|
3978
|
+
case AST_NODE_TYPES18.VariableDeclaration: {
|
|
3679
3979
|
const fnName = functionConstName(decl);
|
|
3680
3980
|
if (fnName !== null && decl.declarations.length === 1) {
|
|
3681
3981
|
addCandidate(fnName, statement);
|
|
@@ -3695,7 +3995,7 @@ var summarizeExports = (body) => {
|
|
|
3695
3995
|
}
|
|
3696
3996
|
return { names, hasReExport, candidate };
|
|
3697
3997
|
};
|
|
3698
|
-
var single_public_export_default =
|
|
3998
|
+
var single_public_export_default = ESLintUtils29.RuleCreator(
|
|
3699
3999
|
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3700
4000
|
)({
|
|
3701
4001
|
name: "single-public-export",
|
|
@@ -3713,7 +4013,7 @@ var single_public_export_default = ESLintUtils26.RuleCreator(
|
|
|
3713
4013
|
create(context) {
|
|
3714
4014
|
const base = basename(context.filename);
|
|
3715
4015
|
if (base.endsWith(".d.ts")) return {};
|
|
3716
|
-
if (
|
|
4016
|
+
if (TEST_FILE_RE2.test(base)) return {};
|
|
3717
4017
|
const stem = stemOf(base);
|
|
3718
4018
|
if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
|
|
3719
4019
|
return {
|
|
@@ -3761,12 +4061,15 @@ var rules = {
|
|
|
3761
4061
|
"no-secret-in-log": no_secret_in_log_default,
|
|
3762
4062
|
"no-unsafe-cast": no_unsafe_cast_default,
|
|
3763
4063
|
"prefer-string-literal-union": prefer_string_literal_union_default,
|
|
3764
|
-
"single-public-export": single_public_export_default
|
|
4064
|
+
"single-public-export": single_public_export_default,
|
|
4065
|
+
"no-silent-promise-catch": no_silent_promise_catch_default,
|
|
4066
|
+
"require-fetch-timeout": require_fetch_timeout_default,
|
|
4067
|
+
"require-schema-validate-search": require_schema_validate_search_default
|
|
3765
4068
|
};
|
|
3766
4069
|
var plugin = {
|
|
3767
4070
|
meta: {
|
|
3768
4071
|
name: "@sarj/eslint-plugin",
|
|
3769
|
-
version: "2.
|
|
4072
|
+
version: "2.7.0"
|
|
3770
4073
|
},
|
|
3771
4074
|
rules,
|
|
3772
4075
|
configs: {
|
|
@@ -3798,7 +4101,11 @@ var plugin = {
|
|
|
3798
4101
|
"@sarj/no-secret-in-log": "warn",
|
|
3799
4102
|
"@sarj/no-unsafe-cast": "warn",
|
|
3800
4103
|
"@sarj/single-public-export": "warn",
|
|
3801
|
-
"@sarj/prefer-string-literal-union": "warn"
|
|
4104
|
+
"@sarj/prefer-string-literal-union": "warn",
|
|
4105
|
+
// Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
|
|
4106
|
+
"@sarj/require-fetch-timeout": "warn",
|
|
4107
|
+
"@sarj/no-silent-promise-catch": "warn",
|
|
4108
|
+
"@sarj/require-schema-validate-search": "warn"
|
|
3802
4109
|
}
|
|
3803
4110
|
},
|
|
3804
4111
|
strict: {
|
|
@@ -3834,7 +4141,11 @@ var plugin = {
|
|
|
3834
4141
|
"@sarj/no-unsafe-cast": "warn",
|
|
3835
4142
|
"@sarj/single-public-export": "error",
|
|
3836
4143
|
// High-volume/stylistic — warn until rollout proves FP rate.
|
|
3837
|
-
"@sarj/prefer-string-literal-union": "warn"
|
|
4144
|
+
"@sarj/prefer-string-literal-union": "warn",
|
|
4145
|
+
// Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
|
|
4146
|
+
"@sarj/require-fetch-timeout": "error",
|
|
4147
|
+
"@sarj/no-silent-promise-catch": "error",
|
|
4148
|
+
"@sarj/require-schema-validate-search": "error"
|
|
3838
4149
|
}
|
|
3839
4150
|
}
|
|
3840
4151
|
}
|