@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.cjs
CHANGED
|
@@ -247,6 +247,8 @@ var no_client_side_data_fetching_default = import_utils2.ESLintUtils.RuleCreator
|
|
|
247
247
|
// src/rules/no-comment-cruft.ts
|
|
248
248
|
var import_utils3 = require("@typescript-eslint/utils");
|
|
249
249
|
var LEADING_PREAMBLE_MIN = 4;
|
|
250
|
+
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;
|
|
251
|
+
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;
|
|
250
252
|
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;
|
|
251
253
|
var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
|
|
252
254
|
var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
|
|
@@ -285,6 +287,13 @@ function isProse(text) {
|
|
|
285
287
|
}
|
|
286
288
|
return false;
|
|
287
289
|
}
|
|
290
|
+
function isRedundantNarration(body) {
|
|
291
|
+
const t = body.trim();
|
|
292
|
+
if (!t || looksLikeCode(t) || hasPseudocode(t)) return false;
|
|
293
|
+
if (STEP_NARRATION_RE.test(t)) return true;
|
|
294
|
+
if (META_COMMENTARY_RE.test(t)) return true;
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
288
297
|
function hasCommentedOutCode(texts, precedingProse) {
|
|
289
298
|
for (let i = 0; i < texts.length; i++) {
|
|
290
299
|
const line = texts[i];
|
|
@@ -310,7 +319,8 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
|
|
|
310
319
|
messages: {
|
|
311
320
|
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
312
321
|
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
313
|
-
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
|
|
322
|
+
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
|
|
323
|
+
redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting."
|
|
314
324
|
}
|
|
315
325
|
},
|
|
316
326
|
defaultOptions: [],
|
|
@@ -365,6 +375,13 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
|
|
|
365
375
|
const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
|
|
366
376
|
if (hasCommentedOutCode(texts, precedingProse)) {
|
|
367
377
|
context.report({ node: comment, messageId: "commentedOutCode" });
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (comment.type === "Line" && texts.length === 1) {
|
|
381
|
+
const body = texts[0];
|
|
382
|
+
if (body !== void 0 && isRedundantNarration(body)) {
|
|
383
|
+
context.report({ node: comment, messageId: "redundantNarration" });
|
|
384
|
+
}
|
|
368
385
|
}
|
|
369
386
|
}
|
|
370
387
|
reportLeadingPreamble(comments, firstCodeLine);
|
|
@@ -2757,13 +2774,292 @@ var no_cors_wildcard_with_credentials_default = import_utils23.ESLintUtils.RuleC
|
|
|
2757
2774
|
}
|
|
2758
2775
|
});
|
|
2759
2776
|
|
|
2760
|
-
// src/rules/no-
|
|
2777
|
+
// src/rules/no-silent-promise-catch.ts
|
|
2761
2778
|
var import_utils24 = require("@typescript-eslint/utils");
|
|
2779
|
+
|
|
2780
|
+
// src/rules/_paths.ts
|
|
2781
|
+
var TEST_FILE_RE = /(\.(test|spec)\.)|([\\/]__tests__[\\/])/;
|
|
2782
|
+
var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
|
|
2783
|
+
function isTestFile(filename) {
|
|
2784
|
+
return TEST_FILE_RE.test(filename);
|
|
2785
|
+
}
|
|
2786
|
+
function isScriptFile(filename) {
|
|
2787
|
+
return SCRIPT_FILE_RE.test(filename);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
// src/rules/no-silent-promise-catch.ts
|
|
2791
|
+
function isBodyParseCall(node) {
|
|
2792
|
+
return node.type === import_utils24.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && node.callee.type === import_utils24.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils24.AST_NODE_TYPES.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
|
|
2793
|
+
}
|
|
2794
|
+
function isSilentExpression(node) {
|
|
2795
|
+
switch (node.type) {
|
|
2796
|
+
case import_utils24.AST_NODE_TYPES.Literal:
|
|
2797
|
+
return !("regex" in node);
|
|
2798
|
+
case import_utils24.AST_NODE_TYPES.Identifier:
|
|
2799
|
+
return node.name === "undefined";
|
|
2800
|
+
case import_utils24.AST_NODE_TYPES.UnaryExpression:
|
|
2801
|
+
return node.operator === "void" && node.argument.type === import_utils24.AST_NODE_TYPES.Literal;
|
|
2802
|
+
case import_utils24.AST_NODE_TYPES.ObjectExpression:
|
|
2803
|
+
return node.properties.length === 0;
|
|
2804
|
+
case import_utils24.AST_NODE_TYPES.ArrayExpression:
|
|
2805
|
+
return node.elements.length === 0;
|
|
2806
|
+
case import_utils24.AST_NODE_TYPES.TSAsExpression:
|
|
2807
|
+
return isSilentExpression(node.expression);
|
|
2808
|
+
default:
|
|
2809
|
+
return false;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
function isSilentHandler(handler) {
|
|
2813
|
+
const body = handler.body;
|
|
2814
|
+
if (body.type !== import_utils24.AST_NODE_TYPES.BlockStatement) {
|
|
2815
|
+
return isSilentExpression(body);
|
|
2816
|
+
}
|
|
2817
|
+
if (body.body.length === 0) {
|
|
2818
|
+
return true;
|
|
2819
|
+
}
|
|
2820
|
+
if (body.body.length === 1) {
|
|
2821
|
+
const only = body.body[0];
|
|
2822
|
+
if (only !== void 0 && only.type === import_utils24.AST_NODE_TYPES.ReturnStatement) {
|
|
2823
|
+
return only.argument === null || isSilentExpression(only.argument);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
return false;
|
|
2827
|
+
}
|
|
2828
|
+
var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
|
|
2829
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2830
|
+
)({
|
|
2831
|
+
name: "no-silent-promise-catch",
|
|
2832
|
+
meta: {
|
|
2833
|
+
type: "problem",
|
|
2834
|
+
docs: {
|
|
2835
|
+
description: "Disallow `.catch()` handlers that silently swallow the rejection (e.g. `.catch(() => null)`); log, rethrow, or handle the error."
|
|
2836
|
+
},
|
|
2837
|
+
schema: [],
|
|
2838
|
+
messages: {
|
|
2839
|
+
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."
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
defaultOptions: [],
|
|
2843
|
+
create(context) {
|
|
2844
|
+
if (isTestFile(context.filename)) {
|
|
2845
|
+
return {};
|
|
2846
|
+
}
|
|
2847
|
+
return {
|
|
2848
|
+
CallExpression(node) {
|
|
2849
|
+
if (node.callee.type !== import_utils24.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.property.type !== import_utils24.AST_NODE_TYPES.Identifier || node.callee.property.name !== "catch") {
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
if (isBodyParseCall(node.callee.object)) {
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
if (node.arguments.length !== 1) {
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
const handler = node.arguments[0];
|
|
2859
|
+
if (handler === void 0 || handler.type !== import_utils24.AST_NODE_TYPES.ArrowFunctionExpression && handler.type !== import_utils24.AST_NODE_TYPES.FunctionExpression) {
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
if (isSilentHandler(handler)) {
|
|
2863
|
+
context.report({ node, messageId: "silentCatch" });
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
// src/rules/require-fetch-timeout.ts
|
|
2871
|
+
var import_utils25 = require("@typescript-eslint/utils");
|
|
2872
|
+
var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
|
|
2873
|
+
"globalThis",
|
|
2874
|
+
"window",
|
|
2875
|
+
"self"
|
|
2876
|
+
]);
|
|
2877
|
+
function matchesAnyPattern2(filename, patterns) {
|
|
2878
|
+
for (const pattern of patterns) {
|
|
2879
|
+
const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
|
|
2880
|
+
if (new RegExp(`^${regexSource}$`).test(filename)) {
|
|
2881
|
+
return true;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
return false;
|
|
2885
|
+
}
|
|
2886
|
+
function initProvablyLacksSignal(init) {
|
|
2887
|
+
if (init.type !== import_utils25.AST_NODE_TYPES.ObjectExpression) {
|
|
2888
|
+
return false;
|
|
2889
|
+
}
|
|
2890
|
+
for (const prop of init.properties) {
|
|
2891
|
+
if (prop.type === import_utils25.AST_NODE_TYPES.SpreadElement) {
|
|
2892
|
+
return false;
|
|
2893
|
+
}
|
|
2894
|
+
if (prop.key.type === import_utils25.AST_NODE_TYPES.Identifier && prop.key.name === "signal" || prop.key.type === import_utils25.AST_NODE_TYPES.Literal && prop.key.value === "signal") {
|
|
2895
|
+
return false;
|
|
2896
|
+
}
|
|
2897
|
+
if (prop.computed) {
|
|
2898
|
+
return false;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
return true;
|
|
2902
|
+
}
|
|
2903
|
+
function isStringish(node) {
|
|
2904
|
+
return node.type === import_utils25.AST_NODE_TYPES.Literal && typeof node.value === "string" || node.type === import_utils25.AST_NODE_TYPES.TemplateLiteral;
|
|
2905
|
+
}
|
|
2906
|
+
var require_fetch_timeout_default = import_utils25.ESLintUtils.RuleCreator(
|
|
2907
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2908
|
+
)({
|
|
2909
|
+
name: "require-fetch-timeout",
|
|
2910
|
+
meta: {
|
|
2911
|
+
type: "problem",
|
|
2912
|
+
docs: {
|
|
2913
|
+
description: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever."
|
|
2914
|
+
},
|
|
2915
|
+
schema: [
|
|
2916
|
+
{
|
|
2917
|
+
type: "object",
|
|
2918
|
+
additionalProperties: false,
|
|
2919
|
+
properties: {
|
|
2920
|
+
allowIn: {
|
|
2921
|
+
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`).",
|
|
2922
|
+
type: "array",
|
|
2923
|
+
items: { type: "string" }
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
],
|
|
2928
|
+
messages: {
|
|
2929
|
+
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."
|
|
2930
|
+
}
|
|
2931
|
+
},
|
|
2932
|
+
defaultOptions: [{}],
|
|
2933
|
+
create(context, [optionsArg]) {
|
|
2934
|
+
if (isTestFile(context.filename) || isScriptFile(context.filename)) {
|
|
2935
|
+
return {};
|
|
2936
|
+
}
|
|
2937
|
+
const allowIn = optionsArg?.allowIn ?? [];
|
|
2938
|
+
if (allowIn.length > 0 && matchesAnyPattern2(context.filename, allowIn)) {
|
|
2939
|
+
return {};
|
|
2940
|
+
}
|
|
2941
|
+
function resolvesToGlobal(identifier) {
|
|
2942
|
+
const scope = context.sourceCode.getScope(identifier);
|
|
2943
|
+
const variable = import_utils25.ASTUtils.findVariable(scope, identifier.name);
|
|
2944
|
+
return variable === null || variable.defs.length === 0;
|
|
2945
|
+
}
|
|
2946
|
+
function isGlobalFetchCall(callee) {
|
|
2947
|
+
if (callee.type === import_utils25.AST_NODE_TYPES.Identifier) {
|
|
2948
|
+
return callee.name === "fetch" && resolvesToGlobal(callee);
|
|
2949
|
+
}
|
|
2950
|
+
return callee.type === import_utils25.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils25.AST_NODE_TYPES.Identifier && callee.property.name === "fetch" && callee.object.type === import_utils25.AST_NODE_TYPES.Identifier && GLOBAL_OBJECTS.has(callee.object.name) && resolvesToGlobal(callee.object);
|
|
2951
|
+
}
|
|
2952
|
+
return {
|
|
2953
|
+
CallExpression(node) {
|
|
2954
|
+
if (!isGlobalFetchCall(node.callee)) {
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
const [first, init] = node.arguments;
|
|
2958
|
+
if (node.arguments.length === 1 && first !== void 0 && !isStringish(first)) {
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
if (init === void 0 || initProvablyLacksSignal(init)) {
|
|
2962
|
+
context.report({ node, messageId: "missingSignal" });
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
|
|
2969
|
+
// src/rules/require-schema-validate-search.ts
|
|
2970
|
+
var import_utils26 = require("@typescript-eslint/utils");
|
|
2971
|
+
var VALIDATOR_METHODS = /* @__PURE__ */ new Set([
|
|
2972
|
+
"parse",
|
|
2973
|
+
"safeParse",
|
|
2974
|
+
"decode"
|
|
2975
|
+
]);
|
|
2976
|
+
function isConstTypeAnnotation(typeAnnotation) {
|
|
2977
|
+
return typeAnnotation.type === import_utils26.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === import_utils26.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
|
|
2978
|
+
}
|
|
2979
|
+
function isValidatorCall(node) {
|
|
2980
|
+
return node.callee.type === import_utils26.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils26.AST_NODE_TYPES.Identifier && VALIDATOR_METHODS.has(node.callee.property.name);
|
|
2981
|
+
}
|
|
2982
|
+
function findCastExpression(node, insideValidatorArg) {
|
|
2983
|
+
if ((node.type === import_utils26.AST_NODE_TYPES.TSAsExpression || node.type === import_utils26.AST_NODE_TYPES.TSTypeAssertion) && !isConstTypeAnnotation(node.typeAnnotation) && !insideValidatorArg) {
|
|
2984
|
+
return node;
|
|
2985
|
+
}
|
|
2986
|
+
if (node.type === import_utils26.AST_NODE_TYPES.CallExpression && isValidatorCall(node)) {
|
|
2987
|
+
const inCallee = findCastExpression(node.callee, insideValidatorArg);
|
|
2988
|
+
if (inCallee !== null) {
|
|
2989
|
+
return inCallee;
|
|
2990
|
+
}
|
|
2991
|
+
for (const arg of node.arguments) {
|
|
2992
|
+
const found = findCastExpression(arg, true);
|
|
2993
|
+
if (found !== null) {
|
|
2994
|
+
return found;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
return null;
|
|
2998
|
+
}
|
|
2999
|
+
for (const key of Object.keys(node)) {
|
|
3000
|
+
if (key === "parent") {
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
const value = node[key];
|
|
3004
|
+
const children = Array.isArray(value) ? value : [value];
|
|
3005
|
+
for (const child of children) {
|
|
3006
|
+
if (child !== null && typeof child === "object" && "type" in child && typeof child.type === "string") {
|
|
3007
|
+
const found = findCastExpression(
|
|
3008
|
+
child,
|
|
3009
|
+
insideValidatorArg
|
|
3010
|
+
);
|
|
3011
|
+
if (found !== null) {
|
|
3012
|
+
return found;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
var require_schema_validate_search_default = import_utils26.ESLintUtils.RuleCreator(
|
|
3020
|
+
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3021
|
+
)({
|
|
3022
|
+
name: "require-schema-validate-search",
|
|
3023
|
+
meta: {
|
|
3024
|
+
type: "problem",
|
|
3025
|
+
docs: {
|
|
3026
|
+
description: "Disallow `as` casts inside hand-rolled `validateSearch` functions; use a schema validator (e.g. zodValidator) so search params are validated at runtime."
|
|
3027
|
+
},
|
|
3028
|
+
schema: [],
|
|
3029
|
+
messages: {
|
|
3030
|
+
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."
|
|
3031
|
+
}
|
|
3032
|
+
},
|
|
3033
|
+
defaultOptions: [],
|
|
3034
|
+
create(context) {
|
|
3035
|
+
if (isTestFile(context.filename)) {
|
|
3036
|
+
return {};
|
|
3037
|
+
}
|
|
3038
|
+
return {
|
|
3039
|
+
Property(node) {
|
|
3040
|
+
const isValidateSearchKey = !node.computed && node.key.type === import_utils26.AST_NODE_TYPES.Identifier && node.key.name === "validateSearch" || node.key.type === import_utils26.AST_NODE_TYPES.Literal && node.key.value === "validateSearch";
|
|
3041
|
+
if (!isValidateSearchKey) {
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
if (node.value.type !== import_utils26.AST_NODE_TYPES.ArrowFunctionExpression && node.value.type !== import_utils26.AST_NODE_TYPES.FunctionExpression) {
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
const cast = findCastExpression(node.value.body, false);
|
|
3048
|
+
if (cast !== null) {
|
|
3049
|
+
context.report({ node: cast, messageId: "castInValidateSearch" });
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
});
|
|
3055
|
+
|
|
3056
|
+
// src/rules/no-fat-try-blocks.ts
|
|
3057
|
+
var import_utils27 = require("@typescript-eslint/utils");
|
|
2762
3058
|
var MAX_TRY_BODY_STATEMENTS = 3;
|
|
2763
3059
|
var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
3060
|
+
import_utils27.AST_NODE_TYPES.FunctionDeclaration,
|
|
3061
|
+
import_utils27.AST_NODE_TYPES.FunctionExpression,
|
|
3062
|
+
import_utils27.AST_NODE_TYPES.ArrowFunctionExpression
|
|
2767
3063
|
]);
|
|
2768
3064
|
var PURE_METHODS = /* @__PURE__ */ new Set([
|
|
2769
3065
|
"map",
|
|
@@ -2861,20 +3157,20 @@ function isNode4(value) {
|
|
|
2861
3157
|
}
|
|
2862
3158
|
function isPureCall(node) {
|
|
2863
3159
|
const callee = node.callee;
|
|
2864
|
-
if (callee.type !==
|
|
3160
|
+
if (callee.type !== import_utils27.AST_NODE_TYPES.MemberExpression) {
|
|
2865
3161
|
return false;
|
|
2866
3162
|
}
|
|
2867
3163
|
const property = callee.property;
|
|
2868
|
-
if (property.type !==
|
|
3164
|
+
if (property.type !== import_utils27.AST_NODE_TYPES.Identifier) {
|
|
2869
3165
|
return false;
|
|
2870
3166
|
}
|
|
2871
|
-
if (callee.object.type ===
|
|
3167
|
+
if (callee.object.type === import_utils27.AST_NODE_TYPES.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
|
|
2872
3168
|
return true;
|
|
2873
3169
|
}
|
|
2874
3170
|
return PURE_METHODS.has(property.name);
|
|
2875
3171
|
}
|
|
2876
3172
|
function isPureNew(node) {
|
|
2877
|
-
return node.callee.type ===
|
|
3173
|
+
return node.callee.type === import_utils27.AST_NODE_TYPES.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
|
|
2878
3174
|
}
|
|
2879
3175
|
function subtreeMatches(stmt, predicate) {
|
|
2880
3176
|
let found = false;
|
|
@@ -2911,14 +3207,14 @@ function subtreeMatches(stmt, predicate) {
|
|
|
2911
3207
|
visit(stmt);
|
|
2912
3208
|
return found;
|
|
2913
3209
|
}
|
|
2914
|
-
var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type ===
|
|
3210
|
+
var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === import_utils27.AST_NODE_TYPES.AwaitExpression);
|
|
2915
3211
|
var hasThrowingCallOrNew = (stmt) => subtreeMatches(
|
|
2916
3212
|
stmt,
|
|
2917
|
-
(n) => n.type ===
|
|
3213
|
+
(n) => n.type === import_utils27.AST_NODE_TYPES.CallExpression && !isPureCall(n) || n.type === import_utils27.AST_NODE_TYPES.NewExpression && !isPureNew(n)
|
|
2918
3214
|
);
|
|
2919
3215
|
function unwrap2(expr) {
|
|
2920
3216
|
let current = expr;
|
|
2921
|
-
while (current.type ===
|
|
3217
|
+
while (current.type === import_utils27.AST_NODE_TYPES.ChainExpression || current.type === import_utils27.AST_NODE_TYPES.TSNonNullExpression) {
|
|
2922
3218
|
current = current.expression;
|
|
2923
3219
|
}
|
|
2924
3220
|
return current;
|
|
@@ -2927,7 +3223,7 @@ function canThrow(stmt) {
|
|
|
2927
3223
|
if (hasAwait(stmt)) {
|
|
2928
3224
|
return true;
|
|
2929
3225
|
}
|
|
2930
|
-
if (stmt.type ===
|
|
3226
|
+
if (stmt.type === import_utils27.AST_NODE_TYPES.ExpressionStatement && unwrap2(stmt.expression).type === import_utils27.AST_NODE_TYPES.CallExpression) {
|
|
2931
3227
|
return false;
|
|
2932
3228
|
}
|
|
2933
3229
|
return hasThrowingCallOrNew(stmt);
|
|
@@ -2938,9 +3234,9 @@ function handlerRethrows(handler) {
|
|
|
2938
3234
|
}
|
|
2939
3235
|
const body = handler.body.body;
|
|
2940
3236
|
const last = body[body.length - 1];
|
|
2941
|
-
return last !== void 0 && last.type ===
|
|
3237
|
+
return last !== void 0 && last.type === import_utils27.AST_NODE_TYPES.ThrowStatement;
|
|
2942
3238
|
}
|
|
2943
|
-
var no_fat_try_blocks_default =
|
|
3239
|
+
var no_fat_try_blocks_default = import_utils27.ESLintUtils.RuleCreator(
|
|
2944
3240
|
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
2945
3241
|
)({
|
|
2946
3242
|
name: "no-fat-try-blocks",
|
|
@@ -2981,7 +3277,7 @@ var no_fat_try_blocks_default = import_utils24.ESLintUtils.RuleCreator(
|
|
|
2981
3277
|
});
|
|
2982
3278
|
|
|
2983
3279
|
// src/rules/no-secret-in-log.ts
|
|
2984
|
-
var
|
|
3280
|
+
var import_utils28 = require("@typescript-eslint/utils");
|
|
2985
3281
|
var LOG_METHODS2 = /* @__PURE__ */ new Set([
|
|
2986
3282
|
"debug",
|
|
2987
3283
|
"info",
|
|
@@ -3179,7 +3475,7 @@ function propertyKeyName2(prop) {
|
|
|
3179
3475
|
}
|
|
3180
3476
|
return null;
|
|
3181
3477
|
}
|
|
3182
|
-
var no_secret_in_log_default =
|
|
3478
|
+
var no_secret_in_log_default = import_utils28.ESLintUtils.RuleCreator(
|
|
3183
3479
|
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3184
3480
|
)({
|
|
3185
3481
|
name: "no-secret-in-log",
|
|
@@ -3247,15 +3543,15 @@ var no_secret_in_log_default = import_utils25.ESLintUtils.RuleCreator(
|
|
|
3247
3543
|
});
|
|
3248
3544
|
|
|
3249
3545
|
// src/rules/no-unsafe-cast.ts
|
|
3250
|
-
var
|
|
3251
|
-
var
|
|
3546
|
+
var import_utils29 = require("@typescript-eslint/utils");
|
|
3547
|
+
var import_utils30 = require("@typescript-eslint/utils");
|
|
3252
3548
|
function isAnyAnnotation(node) {
|
|
3253
|
-
return node.type ===
|
|
3549
|
+
return node.type === import_utils30.AST_NODE_TYPES.TSAnyKeyword;
|
|
3254
3550
|
}
|
|
3255
3551
|
function isConstAssertion(typeAnnotation) {
|
|
3256
|
-
return typeAnnotation.type ===
|
|
3552
|
+
return typeAnnotation.type === import_utils30.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === import_utils30.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
|
|
3257
3553
|
}
|
|
3258
|
-
var no_unsafe_cast_default =
|
|
3554
|
+
var no_unsafe_cast_default = import_utils29.ESLintUtils.RuleCreator(
|
|
3259
3555
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3260
3556
|
)({
|
|
3261
3557
|
name: "no-unsafe-cast",
|
|
@@ -3281,7 +3577,7 @@ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
|
|
|
3281
3577
|
return;
|
|
3282
3578
|
}
|
|
3283
3579
|
const inner = node.expression;
|
|
3284
|
-
if (inner.type ===
|
|
3580
|
+
if (inner.type === import_utils30.AST_NODE_TYPES.TSAsExpression || inner.type === import_utils30.AST_NODE_TYPES.TSTypeAssertion) {
|
|
3285
3581
|
context.report({ node, messageId: "doubleCast" });
|
|
3286
3582
|
}
|
|
3287
3583
|
}
|
|
@@ -3293,7 +3589,7 @@ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
|
|
|
3293
3589
|
});
|
|
3294
3590
|
|
|
3295
3591
|
// src/rules/prefer-string-literal-union.ts
|
|
3296
|
-
var
|
|
3592
|
+
var import_utils31 = require("@typescript-eslint/utils");
|
|
3297
3593
|
var ts = __toESM(require("typescript"), 1);
|
|
3298
3594
|
var CHOICE_TOKENS = /* @__PURE__ */ new Set([
|
|
3299
3595
|
"status",
|
|
@@ -3336,19 +3632,19 @@ function isChoiceLikeName(name) {
|
|
|
3336
3632
|
return CHOICE_TOKENS.has(lastWord(name));
|
|
3337
3633
|
}
|
|
3338
3634
|
function keyName(key) {
|
|
3339
|
-
if (key.type ===
|
|
3635
|
+
if (key.type === import_utils31.AST_NODE_TYPES.Identifier) {
|
|
3340
3636
|
return key.name;
|
|
3341
3637
|
}
|
|
3342
|
-
if (key.type ===
|
|
3638
|
+
if (key.type === import_utils31.AST_NODE_TYPES.Literal && typeof key.value === "string") {
|
|
3343
3639
|
return key.value;
|
|
3344
3640
|
}
|
|
3345
3641
|
return null;
|
|
3346
3642
|
}
|
|
3347
3643
|
function isStringLiteralMember(t) {
|
|
3348
|
-
return t.type ===
|
|
3644
|
+
return t.type === import_utils31.AST_NODE_TYPES.TSLiteralType && t.literal.type === import_utils31.AST_NODE_TYPES.Literal && typeof t.literal.value === "string";
|
|
3349
3645
|
}
|
|
3350
3646
|
function isStringLiteralUnion(node) {
|
|
3351
|
-
if (node?.type !==
|
|
3647
|
+
if (node?.type !== import_utils31.AST_NODE_TYPES.TSUnionType) {
|
|
3352
3648
|
return false;
|
|
3353
3649
|
}
|
|
3354
3650
|
return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
|
|
@@ -3377,12 +3673,12 @@ function bindingSourceExpression(decl) {
|
|
|
3377
3673
|
return ts.isForOfStatement(node) ? node.expression : node.initializer;
|
|
3378
3674
|
}
|
|
3379
3675
|
function refKey(node) {
|
|
3380
|
-
if (node.type ===
|
|
3676
|
+
if (node.type === import_utils31.AST_NODE_TYPES.Identifier) {
|
|
3381
3677
|
return node.name;
|
|
3382
3678
|
}
|
|
3383
|
-
if (node.type ===
|
|
3679
|
+
if (node.type === import_utils31.AST_NODE_TYPES.MemberExpression && !node.computed) {
|
|
3384
3680
|
const inner = refKey(node.object);
|
|
3385
|
-
if (inner === null || node.property.type !==
|
|
3681
|
+
if (inner === null || node.property.type !== import_utils31.AST_NODE_TYPES.Identifier) {
|
|
3386
3682
|
return null;
|
|
3387
3683
|
}
|
|
3388
3684
|
return `${inner}.${node.property.name}`;
|
|
@@ -3390,12 +3686,12 @@ function refKey(node) {
|
|
|
3390
3686
|
return null;
|
|
3391
3687
|
}
|
|
3392
3688
|
function strLiteral(node) {
|
|
3393
|
-
if (node.type ===
|
|
3689
|
+
if (node.type === import_utils31.AST_NODE_TYPES.Literal && typeof node.value === "string") {
|
|
3394
3690
|
return node.value;
|
|
3395
3691
|
}
|
|
3396
3692
|
return null;
|
|
3397
3693
|
}
|
|
3398
|
-
var prefer_string_literal_union_default =
|
|
3694
|
+
var prefer_string_literal_union_default = import_utils31.ESLintUtils.RuleCreator(
|
|
3399
3695
|
(name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3400
3696
|
)({
|
|
3401
3697
|
name: "prefer-string-literal-union",
|
|
@@ -3419,7 +3715,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
|
|
|
3419
3715
|
}
|
|
3420
3716
|
let services;
|
|
3421
3717
|
try {
|
|
3422
|
-
services =
|
|
3718
|
+
services = import_utils31.ESLintUtils.getParserServices(context);
|
|
3423
3719
|
} catch {
|
|
3424
3720
|
services = null;
|
|
3425
3721
|
}
|
|
@@ -3503,7 +3799,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
|
|
|
3503
3799
|
containersWithUnion.add(container);
|
|
3504
3800
|
return;
|
|
3505
3801
|
}
|
|
3506
|
-
if (typeNode?.type !==
|
|
3802
|
+
if (typeNode?.type !== import_utils31.AST_NODE_TYPES.TSStringKeyword) {
|
|
3507
3803
|
return;
|
|
3508
3804
|
}
|
|
3509
3805
|
const name = keyName(key);
|
|
@@ -3591,10 +3887,10 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
|
|
|
3591
3887
|
}
|
|
3592
3888
|
};
|
|
3593
3889
|
function refKeyText(node) {
|
|
3594
|
-
if (node.type ===
|
|
3890
|
+
if (node.type === import_utils31.AST_NODE_TYPES.BinaryExpression) {
|
|
3595
3891
|
return refKey(node.left) ?? refKey(node.right) ?? "value";
|
|
3596
3892
|
}
|
|
3597
|
-
if (node.type ===
|
|
3893
|
+
if (node.type === import_utils31.AST_NODE_TYPES.SwitchStatement) {
|
|
3598
3894
|
return refKey(node.discriminant) ?? "value";
|
|
3599
3895
|
}
|
|
3600
3896
|
return "value";
|
|
@@ -3603,7 +3899,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
|
|
|
3603
3899
|
});
|
|
3604
3900
|
|
|
3605
3901
|
// src/rules/single-public-export.ts
|
|
3606
|
-
var
|
|
3902
|
+
var import_utils32 = require("@typescript-eslint/utils");
|
|
3607
3903
|
var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
|
|
3608
3904
|
"util",
|
|
3609
3905
|
"utils",
|
|
@@ -3626,7 +3922,7 @@ var ACRONYM_OVERRIDES = [
|
|
|
3626
3922
|
[/gRPC/g, "Grpc"]
|
|
3627
3923
|
];
|
|
3628
3924
|
var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
|
|
3629
|
-
var
|
|
3925
|
+
var TEST_FILE_RE2 = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
3630
3926
|
var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
|
|
3631
3927
|
var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
|
|
3632
3928
|
var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
|
|
@@ -3637,12 +3933,12 @@ var kebabCase2 = (name) => {
|
|
|
3637
3933
|
}
|
|
3638
3934
|
return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
|
|
3639
3935
|
};
|
|
3640
|
-
var isFunctionExpression = (node) => node !== null && (node.type ===
|
|
3936
|
+
var isFunctionExpression = (node) => node !== null && (node.type === import_utils32.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils32.AST_NODE_TYPES.FunctionExpression);
|
|
3641
3937
|
var functionConstName = (decl) => {
|
|
3642
3938
|
if (decl.declarations.length !== 1) return null;
|
|
3643
3939
|
const [declarator] = decl.declarations;
|
|
3644
3940
|
if (declarator === void 0) return null;
|
|
3645
|
-
if (declarator.id.type !==
|
|
3941
|
+
if (declarator.id.type !== import_utils32.AST_NODE_TYPES.Identifier) return null;
|
|
3646
3942
|
if (!isFunctionExpression(declarator.init)) return null;
|
|
3647
3943
|
return declarator.id.name;
|
|
3648
3944
|
};
|
|
@@ -3656,20 +3952,20 @@ var summarizeExports = (body) => {
|
|
|
3656
3952
|
};
|
|
3657
3953
|
for (const statement of body) {
|
|
3658
3954
|
switch (statement.type) {
|
|
3659
|
-
case
|
|
3955
|
+
case import_utils32.AST_NODE_TYPES.ExportAllDeclaration:
|
|
3660
3956
|
hasReExport = true;
|
|
3661
3957
|
break;
|
|
3662
|
-
case
|
|
3958
|
+
case import_utils32.AST_NODE_TYPES.ExportDefaultDeclaration: {
|
|
3663
3959
|
names += 1;
|
|
3664
3960
|
const decl = statement.declaration;
|
|
3665
|
-
if (decl.type ===
|
|
3961
|
+
if (decl.type === import_utils32.AST_NODE_TYPES.FunctionDeclaration && decl.id !== null) {
|
|
3666
3962
|
candidate = { name: decl.id.name, node: statement };
|
|
3667
|
-
} else if (decl.type ===
|
|
3963
|
+
} else if (decl.type === import_utils32.AST_NODE_TYPES.ClassDeclaration && decl.id !== null) {
|
|
3668
3964
|
candidate = { name: decl.id.name, node: statement };
|
|
3669
3965
|
}
|
|
3670
3966
|
break;
|
|
3671
3967
|
}
|
|
3672
|
-
case
|
|
3968
|
+
case import_utils32.AST_NODE_TYPES.ExportNamedDeclaration: {
|
|
3673
3969
|
if (statement.source !== null) {
|
|
3674
3970
|
hasReExport = true;
|
|
3675
3971
|
break;
|
|
@@ -3680,15 +3976,15 @@ var summarizeExports = (body) => {
|
|
|
3680
3976
|
break;
|
|
3681
3977
|
}
|
|
3682
3978
|
switch (decl.type) {
|
|
3683
|
-
case
|
|
3979
|
+
case import_utils32.AST_NODE_TYPES.FunctionDeclaration:
|
|
3684
3980
|
if (decl.id !== null) addCandidate(decl.id.name, statement);
|
|
3685
3981
|
else names += 1;
|
|
3686
3982
|
break;
|
|
3687
|
-
case
|
|
3983
|
+
case import_utils32.AST_NODE_TYPES.ClassDeclaration:
|
|
3688
3984
|
if (decl.id !== null) addCandidate(decl.id.name, statement);
|
|
3689
3985
|
else names += 1;
|
|
3690
3986
|
break;
|
|
3691
|
-
case
|
|
3987
|
+
case import_utils32.AST_NODE_TYPES.VariableDeclaration: {
|
|
3692
3988
|
const fnName = functionConstName(decl);
|
|
3693
3989
|
if (fnName !== null && decl.declarations.length === 1) {
|
|
3694
3990
|
addCandidate(fnName, statement);
|
|
@@ -3708,7 +4004,7 @@ var summarizeExports = (body) => {
|
|
|
3708
4004
|
}
|
|
3709
4005
|
return { names, hasReExport, candidate };
|
|
3710
4006
|
};
|
|
3711
|
-
var single_public_export_default =
|
|
4007
|
+
var single_public_export_default = import_utils32.ESLintUtils.RuleCreator(
|
|
3712
4008
|
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
3713
4009
|
)({
|
|
3714
4010
|
name: "single-public-export",
|
|
@@ -3726,7 +4022,7 @@ var single_public_export_default = import_utils29.ESLintUtils.RuleCreator(
|
|
|
3726
4022
|
create(context) {
|
|
3727
4023
|
const base = basename(context.filename);
|
|
3728
4024
|
if (base.endsWith(".d.ts")) return {};
|
|
3729
|
-
if (
|
|
4025
|
+
if (TEST_FILE_RE2.test(base)) return {};
|
|
3730
4026
|
const stem = stemOf(base);
|
|
3731
4027
|
if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
|
|
3732
4028
|
return {
|
|
@@ -3774,12 +4070,15 @@ var rules = {
|
|
|
3774
4070
|
"no-secret-in-log": no_secret_in_log_default,
|
|
3775
4071
|
"no-unsafe-cast": no_unsafe_cast_default,
|
|
3776
4072
|
"prefer-string-literal-union": prefer_string_literal_union_default,
|
|
3777
|
-
"single-public-export": single_public_export_default
|
|
4073
|
+
"single-public-export": single_public_export_default,
|
|
4074
|
+
"no-silent-promise-catch": no_silent_promise_catch_default,
|
|
4075
|
+
"require-fetch-timeout": require_fetch_timeout_default,
|
|
4076
|
+
"require-schema-validate-search": require_schema_validate_search_default
|
|
3778
4077
|
};
|
|
3779
4078
|
var plugin = {
|
|
3780
4079
|
meta: {
|
|
3781
4080
|
name: "@sarj/eslint-plugin",
|
|
3782
|
-
version: "2.
|
|
4081
|
+
version: "2.7.0"
|
|
3783
4082
|
},
|
|
3784
4083
|
rules,
|
|
3785
4084
|
configs: {
|
|
@@ -3811,7 +4110,11 @@ var plugin = {
|
|
|
3811
4110
|
"@sarj/no-secret-in-log": "warn",
|
|
3812
4111
|
"@sarj/no-unsafe-cast": "warn",
|
|
3813
4112
|
"@sarj/single-public-export": "warn",
|
|
3814
|
-
"@sarj/prefer-string-literal-union": "warn"
|
|
4113
|
+
"@sarj/prefer-string-literal-union": "warn",
|
|
4114
|
+
// Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
|
|
4115
|
+
"@sarj/require-fetch-timeout": "warn",
|
|
4116
|
+
"@sarj/no-silent-promise-catch": "warn",
|
|
4117
|
+
"@sarj/require-schema-validate-search": "warn"
|
|
3815
4118
|
}
|
|
3816
4119
|
},
|
|
3817
4120
|
strict: {
|
|
@@ -3847,7 +4150,11 @@ var plugin = {
|
|
|
3847
4150
|
"@sarj/no-unsafe-cast": "warn",
|
|
3848
4151
|
"@sarj/single-public-export": "error",
|
|
3849
4152
|
// High-volume/stylistic — warn until rollout proves FP rate.
|
|
3850
|
-
"@sarj/prefer-string-literal-union": "warn"
|
|
4153
|
+
"@sarj/prefer-string-literal-union": "warn",
|
|
4154
|
+
// Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
|
|
4155
|
+
"@sarj/require-fetch-timeout": "error",
|
|
4156
|
+
"@sarj/no-silent-promise-catch": "error",
|
|
4157
|
+
"@sarj/require-schema-validate-search": "error"
|
|
3851
4158
|
}
|
|
3852
4159
|
}
|
|
3853
4160
|
}
|