@sarj/eslint-plugin 15.6.8 → 15.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 +329 -195
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +329 -195
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4253,6 +4253,87 @@ var no_long_comment_default = createRule({
|
|
|
4253
4253
|
}
|
|
4254
4254
|
});
|
|
4255
4255
|
|
|
4256
|
+
// src/rules/no-vague-suppression-description.ts
|
|
4257
|
+
var DIRECTIVE_RE3 = /(?:eslint-(?:disable|disable-next-line|disable-line)|@ts-(?:expect-error|ignore))\b/iu;
|
|
4258
|
+
var DESCRIPTION_RE = /(?::|--)\s*(.+?)\s*$/u;
|
|
4259
|
+
var VAGUE_RE = /^(?:needed|required|intentional(?:ly)?|ignore(?:d)?|false positive|type error|typescript|to satisfy (?:the )?(?:linter|typescript|type checker))\.?$/iu;
|
|
4260
|
+
var noVagueSuppressionDescriptionDocumentation = {
|
|
4261
|
+
summary: "Require suppression descriptions to name the concrete mismatch or invariant instead of a generic non-reason.",
|
|
4262
|
+
rationale: "Generic phrases satisfy require-description mechanically while leaving reviewers unable to audit the risk or remove stale debt.",
|
|
4263
|
+
remediation: "Name the exact type/runtime mismatch, external contract, or safety invariant that makes this suppression acceptable.",
|
|
4264
|
+
category: "maintainability",
|
|
4265
|
+
limitations: [
|
|
4266
|
+
"Only ESLint disable comments and TypeScript expect-error/ignore directives are checked.",
|
|
4267
|
+
"The rule uses a small anchored vocabulary and does not score prose quality generally.",
|
|
4268
|
+
"Generated files and descriptions containing any concrete context are excluded."
|
|
4269
|
+
],
|
|
4270
|
+
examples: [
|
|
4271
|
+
{
|
|
4272
|
+
id: "concrete-runtime-mismatch",
|
|
4273
|
+
title: "Explain the concrete runtime contract",
|
|
4274
|
+
outcome: "no-match",
|
|
4275
|
+
files: [
|
|
4276
|
+
{
|
|
4277
|
+
path: "src/adapter.ts",
|
|
4278
|
+
source: "// @ts-expect-error -- vendor types omit the runtime requestId field\nreturn response.requestId;"
|
|
4279
|
+
}
|
|
4280
|
+
],
|
|
4281
|
+
focusPath: "src/adapter.ts",
|
|
4282
|
+
expectedCount: 0,
|
|
4283
|
+
public: true
|
|
4284
|
+
},
|
|
4285
|
+
{
|
|
4286
|
+
id: "generic-suppression-reason",
|
|
4287
|
+
title: "Reject a suppression with no auditable reason",
|
|
4288
|
+
outcome: "match",
|
|
4289
|
+
files: [
|
|
4290
|
+
{
|
|
4291
|
+
path: "src/adapter.ts",
|
|
4292
|
+
source: "// @ts-expect-error -- false positive\nreturn response.requestId;"
|
|
4293
|
+
}
|
|
4294
|
+
],
|
|
4295
|
+
focusPath: "src/adapter.ts",
|
|
4296
|
+
expectedCount: 1,
|
|
4297
|
+
public: true
|
|
4298
|
+
}
|
|
4299
|
+
]
|
|
4300
|
+
};
|
|
4301
|
+
var no_vague_suppression_description_default = createRule({
|
|
4302
|
+
name: "no-vague-suppression-description",
|
|
4303
|
+
documentation: noVagueSuppressionDescriptionDocumentation,
|
|
4304
|
+
meta: {
|
|
4305
|
+
type: "suggestion",
|
|
4306
|
+
docs: {
|
|
4307
|
+
description: "Require suppression descriptions to name the concrete mismatch or invariant instead of a generic non-reason."
|
|
4308
|
+
},
|
|
4309
|
+
schema: [],
|
|
4310
|
+
messages: {
|
|
4311
|
+
vagueDescription: "Suppression description `{{description}}` does not explain why the suppressed diagnostic is safe here. Name the concrete type/runtime mismatch, external contract, or invariant."
|
|
4312
|
+
}
|
|
4313
|
+
},
|
|
4314
|
+
defaultOptions: [],
|
|
4315
|
+
create(context) {
|
|
4316
|
+
if (isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
4317
|
+
return {};
|
|
4318
|
+
}
|
|
4319
|
+
return {
|
|
4320
|
+
Program() {
|
|
4321
|
+
for (const comment of context.sourceCode.getAllComments()) {
|
|
4322
|
+
const text = comment.value.trim();
|
|
4323
|
+
if (!DIRECTIVE_RE3.test(text)) continue;
|
|
4324
|
+
const description = DESCRIPTION_RE.exec(text)?.[1]?.trim();
|
|
4325
|
+
if (description === void 0 || !VAGUE_RE.test(description)) continue;
|
|
4326
|
+
context.report({
|
|
4327
|
+
loc: comment.loc,
|
|
4328
|
+
messageId: "vagueDescription",
|
|
4329
|
+
data: { description }
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
});
|
|
4336
|
+
|
|
4256
4337
|
// src/rules/no-generic-single-export-module.ts
|
|
4257
4338
|
import { AST_NODE_TYPES as AST_NODE_TYPES16, ASTUtils as ASTUtils4 } from "@typescript-eslint/utils";
|
|
4258
4339
|
var noGenericSingleExportModuleDocumentation = {
|
|
@@ -5392,7 +5473,7 @@ var no_repeated_string_literal_default = createRule({
|
|
|
5392
5473
|
import { AST_NODE_TYPES as AST_NODE_TYPES21 } from "@typescript-eslint/utils";
|
|
5393
5474
|
var MAX_WORDS = 8;
|
|
5394
5475
|
var MIN_CONTENT_TOKENS = 2;
|
|
5395
|
-
var
|
|
5476
|
+
var DIRECTIVE_RE4 = /^(eslint\b|eslint-|sarj-noqa\b|@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;
|
|
5396
5477
|
var CODEY_RE = /^[\w.$[\]'"]+\s*[:=]\s*\S|^[\w.$]+\s*\(|^(?:return|throw|await|import|export|const|let|var)\b.*[=()[\]{}]/;
|
|
5397
5478
|
var BANNERISH_RE = /[=\-─-╿*#~_.]{3,}|^[A-Z0-9 _:-]+$/;
|
|
5398
5479
|
var MODALITY_RE = /\b(?:can|could|should|shall|may|might|must|will|would|cannot)\b/i;
|
|
@@ -5491,7 +5572,7 @@ var no_restated_comment_default = createRule({
|
|
|
5491
5572
|
}
|
|
5492
5573
|
const body2 = comment.value.replace(/^\/*/, "").trim();
|
|
5493
5574
|
if (body2.length === 0 || body2.endsWith("?")) continue;
|
|
5494
|
-
if (
|
|
5575
|
+
if (DIRECTIVE_RE4.test(body2) || CODEY_RE.test(body2) || BANNERISH_RE.test(body2)) continue;
|
|
5495
5576
|
if (NON_ASCII_LETTER_RE.test(body2) || isProtected(body2)) continue;
|
|
5496
5577
|
if (MODALITY_RE.test(body2) || LEAD_IN_RE.test(body2) || EMPHASIS_RE.test(body2)) continue;
|
|
5497
5578
|
if (NEGATION_WORD_RE.test(body2)) continue;
|
|
@@ -5539,7 +5620,7 @@ var MODELLED_TAGS = /* @__PURE__ */ new Set([
|
|
|
5539
5620
|
]);
|
|
5540
5621
|
var PARAM_TAGS = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
|
|
5541
5622
|
var RETURN_TAGS = /* @__PURE__ */ new Set(["return", "returns"]);
|
|
5542
|
-
var
|
|
5623
|
+
var DIRECTIVE_RE5 = /^\s*(?:eslint\b|eslint-|@ts-|prettier|biome-|c8\b|v8\b|istanbul\b|@vite|webpack|@jsx|@jest-environment|@vitest-environment|#__)/i;
|
|
5543
5624
|
var STOPWORDS2 = new Set(
|
|
5544
5625
|
`the a an of to for in on with and or as at by is are was be been being
|
|
5545
5626
|
this that it its if whether when where which what will would can could should
|
|
@@ -5655,7 +5736,7 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5655
5736
|
description,
|
|
5656
5737
|
...tags.filter((tag) => tag.name === "description").map((tag) => tag.text)
|
|
5657
5738
|
].filter((text) => text.length > 0).join("\n");
|
|
5658
|
-
if (
|
|
5739
|
+
if (DIRECTIVE_RE5.test(describedText)) continue;
|
|
5659
5740
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
5660
5741
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
5661
5742
|
if (isProtected(describedText)) continue;
|
|
@@ -7617,10 +7698,10 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
|
|
|
7617
7698
|
"we",
|
|
7618
7699
|
"with"
|
|
7619
7700
|
]);
|
|
7620
|
-
var
|
|
7701
|
+
var DIRECTIVE_RE6 = /^\s*(?:eslint\b|eslint-|sarj-noqa\b|@ts-|prettier|biome-|c8\b|v8\b|istanbul\b|todo\b|fixme\b|hack\b|xxx\b)/i;
|
|
7621
7702
|
var UNIT_NAME_SUFFIX_RE = /(?:_(?:NS|US|MS|S|SEC|SECS|SECOND|SECONDS|MIN|MINS|MINUTE|MINUTES|HOUR|HOURS|DAY|DAYS|BYTE|BYTES|KB|MB|GB|HZ|KHZ|MHZ|PX)|(?:Ns|Us|Ms|Sec|Secs|Second|Seconds|Min|Mins|Minute|Minutes|Hour|Hours|Day|Days|Byte|Bytes|Kb|Mb|Gb|Hz|Khz|Mhz|Px))$/u;
|
|
7622
7703
|
function narratesValue(body2, code) {
|
|
7623
|
-
if (body2.length === 0 ||
|
|
7704
|
+
if (body2.length === 0 || DIRECTIVE_RE6.test(body2) || hasExternalReference(body2)) return false;
|
|
7624
7705
|
const codeNumbers = numbersIn(code);
|
|
7625
7706
|
if (codeNumbers.size === 0) return false;
|
|
7626
7707
|
const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
|
|
@@ -14764,7 +14845,7 @@ var stepdown_default = createRule({
|
|
|
14764
14845
|
|
|
14765
14846
|
// src/rules/source-coupled-test.ts
|
|
14766
14847
|
import { AST_NODE_TYPES as AST_NODE_TYPES56 } from "@typescript-eslint/utils";
|
|
14767
|
-
var
|
|
14848
|
+
var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|py|[cm]?[jt]s)$/iu;
|
|
14768
14849
|
var FS_MODULES = /* @__PURE__ */ new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
|
|
14769
14850
|
var FS_READERS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
|
|
14770
14851
|
var TEXT_TRANSFORMS = /* @__PURE__ */ new Set([
|
|
@@ -14778,7 +14859,8 @@ var TEXT_TRANSFORMS = /* @__PURE__ */ new Set([
|
|
|
14778
14859
|
"trimEnd",
|
|
14779
14860
|
"trimStart",
|
|
14780
14861
|
"replace",
|
|
14781
|
-
"replaceAll"
|
|
14862
|
+
"replaceAll",
|
|
14863
|
+
"split"
|
|
14782
14864
|
]);
|
|
14783
14865
|
var TEXT_PREDICATES = /* @__PURE__ */ new Set(["endsWith", "includes", "indexOf", "lastIndexOf", "match", "matchAll", "search", "startsWith"]);
|
|
14784
14866
|
var REGEXP_PREDICATES = /* @__PURE__ */ new Set(["exec", "test"]);
|
|
@@ -14803,7 +14885,7 @@ var ASSERT_MATCHERS = /* @__PURE__ */ new Set(["deepEqual", "doesNotMatch", "equ
|
|
|
14803
14885
|
var sourceCoupledTestDocumentation = {
|
|
14804
14886
|
summary: "Disallow raw repository source text as a test oracle; parse or execute the artifact instead.",
|
|
14805
14887
|
rationale: "Substring and regex checks can pass on comments or unreachable configuration and fail after behavior-preserving formatting changes.",
|
|
14806
|
-
remediation: "Parse the artifact, execute its validator, or assert on
|
|
14888
|
+
remediation: "Parse the artifact, execute its validator, or assert on another runtime contract.",
|
|
14807
14889
|
category: "testing",
|
|
14808
14890
|
limitations: [
|
|
14809
14891
|
"The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
|
|
@@ -14820,10 +14902,10 @@ var sourceCoupledTestDocumentation = {
|
|
|
14820
14902
|
public: true
|
|
14821
14903
|
},
|
|
14822
14904
|
{
|
|
14823
|
-
id: "
|
|
14824
|
-
title: "Do not prove
|
|
14905
|
+
id: "workflow-substring-contract",
|
|
14906
|
+
title: "Do not prove workflow behavior with a regex",
|
|
14825
14907
|
outcome: "match",
|
|
14826
|
-
files: [{ path: "src/policy.test.ts", source: "import { readFileSync } from 'node:fs'; test('policy', () => { const source = readFileSync('
|
|
14908
|
+
files: [{ path: "src/policy.test.ts", source: "import { readFileSync } from 'node:fs'; test('policy', () => { const source = readFileSync('workflow.yml', 'utf8'); expect(source).toMatch(/permissions/); });" }],
|
|
14827
14909
|
focusPath: "src/policy.test.ts",
|
|
14828
14910
|
expectedCount: 1,
|
|
14829
14911
|
public: true
|
|
@@ -14858,191 +14940,237 @@ function requireSource(node) {
|
|
|
14858
14940
|
function newScope() {
|
|
14859
14941
|
return { collections: /* @__PURE__ */ new Set(), declared: /* @__PURE__ */ new Set(), fsObjects: /* @__PURE__ */ new Set(), fsReaders: /* @__PURE__ */ new Set(), paths: /* @__PURE__ */ new Set(), rawOrigins: /* @__PURE__ */ new Map() };
|
|
14860
14942
|
}
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
return false;
|
|
14882
|
-
};
|
|
14883
|
-
const visibleRawOrigins = (name) => {
|
|
14884
|
-
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
14885
|
-
const scope = scopes[index];
|
|
14886
|
-
if (scope.declared.has(name)) return scope.rawOrigins.get(name) ?? /* @__PURE__ */ new Set();
|
|
14887
|
-
}
|
|
14888
|
-
return /* @__PURE__ */ new Set();
|
|
14889
|
-
};
|
|
14890
|
-
const sourcePath = (node) => {
|
|
14891
|
-
const current = unwrap5(node);
|
|
14892
|
-
const value = stringValue(current);
|
|
14893
|
-
if (value !== null) return SOURCE_SUFFIX_RE.test(value);
|
|
14894
|
-
if (current.type === AST_NODE_TYPES56.Identifier) return visible("paths", current.name);
|
|
14895
|
-
if (current.type === AST_NODE_TYPES56.BinaryExpression && current.operator === "+") {
|
|
14896
|
-
return sourcePath(current.left) || sourcePath(current.right);
|
|
14897
|
-
}
|
|
14898
|
-
if (current.type === AST_NODE_TYPES56.TemplateLiteral) return current.expressions.some(sourcePath);
|
|
14899
|
-
if (current.type === AST_NODE_TYPES56.CallExpression || current.type === AST_NODE_TYPES56.NewExpression) {
|
|
14900
|
-
return current.arguments.some((argument) => argument.type !== AST_NODE_TYPES56.SpreadElement && sourcePath(argument));
|
|
14901
|
-
}
|
|
14902
|
-
if (current.type === AST_NODE_TYPES56.MemberExpression) return sourcePath(current.object);
|
|
14903
|
-
return false;
|
|
14904
|
-
};
|
|
14905
|
-
const rawRead = (node) => {
|
|
14906
|
-
const current = unwrap5(node);
|
|
14907
|
-
if (current.type !== AST_NODE_TYPES56.CallExpression || current.arguments.length === 0) return false;
|
|
14908
|
-
const callee = unwrap5(current.callee);
|
|
14909
|
-
if (callee.type === AST_NODE_TYPES56.Identifier) {
|
|
14910
|
-
return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
|
|
14911
|
-
}
|
|
14912
|
-
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return false;
|
|
14913
|
-
const name = staticMemberName5(callee);
|
|
14914
|
-
const object = unwrap5(callee.object);
|
|
14915
|
-
return name !== null && FS_READERS.has(name) && object.type === AST_NODE_TYPES56.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
|
|
14916
|
-
};
|
|
14917
|
-
const rawOrigins = (node) => {
|
|
14918
|
-
const current = unwrap5(node);
|
|
14919
|
-
if (current.type === AST_NODE_TYPES56.Identifier) return visibleRawOrigins(current.name);
|
|
14920
|
-
if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
|
|
14921
|
-
if (current.type === AST_NODE_TYPES56.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
|
|
14922
|
-
if (current.type !== AST_NODE_TYPES56.CallExpression) return /* @__PURE__ */ new Set();
|
|
14923
|
-
const callee = unwrap5(current.callee);
|
|
14924
|
-
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return /* @__PURE__ */ new Set();
|
|
14925
|
-
const name = staticMemberName5(callee);
|
|
14926
|
-
return name !== null && TEXT_TRANSFORMS.has(name) ? rawOrigins(callee.object) : /* @__PURE__ */ new Set();
|
|
14927
|
-
};
|
|
14928
|
-
const evidenceOrigins = (node) => {
|
|
14929
|
-
const current = unwrap5(node);
|
|
14930
|
-
const direct = rawOrigins(current);
|
|
14931
|
-
if (direct.size > 0) return direct;
|
|
14932
|
-
if (current.type === AST_NODE_TYPES56.BinaryExpression || current.type === AST_NODE_TYPES56.LogicalExpression) return /* @__PURE__ */ new Set([...evidenceOrigins(current.left), ...evidenceOrigins(current.right)]);
|
|
14933
|
-
if (current.type === AST_NODE_TYPES56.UnaryExpression) return evidenceOrigins(current.argument);
|
|
14934
|
-
if (current.type !== AST_NODE_TYPES56.CallExpression) return /* @__PURE__ */ new Set();
|
|
14935
|
-
const callee = unwrap5(current.callee);
|
|
14936
|
-
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return /* @__PURE__ */ new Set();
|
|
14937
|
-
const name = staticMemberName5(callee);
|
|
14938
|
-
if (name !== null && TEXT_PREDICATES.has(name)) return rawOrigins(callee.object);
|
|
14939
|
-
if (name !== null && REGEXP_PREDICATES.has(name)) return new Set(current.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...rawOrigins(argument)]));
|
|
14940
|
-
return /* @__PURE__ */ new Set();
|
|
14941
|
-
};
|
|
14942
|
-
const rawAssertionOrigins = (node) => {
|
|
14943
|
-
const callee = unwrap5(node.callee);
|
|
14944
|
-
if (callee.type === AST_NODE_TYPES56.Identifier && callee.name === "assert") {
|
|
14945
|
-
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
14946
|
-
}
|
|
14947
|
-
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return /* @__PURE__ */ new Set();
|
|
14948
|
-
const matcher = staticMemberName5(callee);
|
|
14949
|
-
if (matcher === null) return /* @__PURE__ */ new Set();
|
|
14950
|
-
let receiver = unwrap5(callee.object);
|
|
14951
|
-
while (receiver.type === AST_NODE_TYPES56.MemberExpression && EXPECT_MODIFIERS.has(staticMemberName5(receiver) ?? "")) receiver = unwrap5(receiver.object);
|
|
14952
|
-
if (receiver.type === AST_NODE_TYPES56.CallExpression && receiver.callee.type === AST_NODE_TYPES56.Identifier && receiver.callee.name === "expect") {
|
|
14953
|
-
if (!EXPECT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
|
|
14954
|
-
return new Set([...receiver.arguments, ...node.arguments].flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
14955
|
-
}
|
|
14956
|
-
if (receiver.type !== AST_NODE_TYPES56.Identifier || receiver.name !== "assert" || !ASSERT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
|
|
14957
|
-
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
14958
|
-
};
|
|
14959
|
-
const declare = (name, state) => {
|
|
14960
|
-
const scope = currentScope();
|
|
14961
|
-
scope.declared.add(name);
|
|
14962
|
-
scope.collections.delete(name);
|
|
14963
|
-
scope.fsObjects.delete(name);
|
|
14964
|
-
scope.fsReaders.delete(name);
|
|
14965
|
-
scope.paths.delete(name);
|
|
14966
|
-
scope.rawOrigins.delete(name);
|
|
14967
|
-
if (state.collection === true) scope.collections.add(name);
|
|
14968
|
-
if (state.fsObject === true) scope.fsObjects.add(name);
|
|
14969
|
-
if (state.fsReader === true) scope.fsReaders.add(name);
|
|
14970
|
-
if (state.path === true) scope.paths.add(name);
|
|
14971
|
-
if (state.rawOrigins !== void 0 && state.rawOrigins.size > 0) {
|
|
14972
|
-
scope.rawOrigins.set(name, state.rawOrigins);
|
|
14973
|
-
}
|
|
14974
|
-
};
|
|
14975
|
-
const sourceCollection = (node) => {
|
|
14976
|
-
const current = unwrap5(node);
|
|
14977
|
-
return current.type === AST_NODE_TYPES56.ArrayExpression && current.elements.length > 0 && current.elements.every((element) => element !== null && element.type !== AST_NODE_TYPES56.SpreadElement && sourcePath(element));
|
|
14978
|
-
};
|
|
14979
|
-
const declaredNames2 = (node) => {
|
|
14980
|
-
const current = unwrap5(node);
|
|
14981
|
-
if (current.type === AST_NODE_TYPES56.Identifier) return [current.name];
|
|
14982
|
-
if (current.type === AST_NODE_TYPES56.AssignmentPattern) return declaredNames2(current.left);
|
|
14983
|
-
if (current.type === AST_NODE_TYPES56.RestElement) return declaredNames2(current.argument);
|
|
14984
|
-
if (current.type === AST_NODE_TYPES56.ArrayPattern) return current.elements.flatMap((element) => element === null ? [] : declaredNames2(element));
|
|
14985
|
-
if (current.type === AST_NODE_TYPES56.ObjectPattern) return current.properties.flatMap((property) => property.type === AST_NODE_TYPES56.RestElement ? declaredNames2(property.argument) : declaredNames2(property.value));
|
|
14986
|
-
return [];
|
|
14987
|
-
};
|
|
14988
|
-
const enterFunction = (node) => {
|
|
14989
|
-
scopes.push(newScope());
|
|
14990
|
-
for (const parameter of node.params) for (const name of declaredNames2(parameter)) declare(name, {});
|
|
14991
|
-
};
|
|
14992
|
-
const exitFunction = () => {
|
|
14993
|
-
scopes.pop();
|
|
14994
|
-
};
|
|
14995
|
-
return {
|
|
14996
|
-
ImportDeclaration(node) {
|
|
14997
|
-
const source = importSource(node);
|
|
14998
|
-
if (source === null || !FS_MODULES.has(source)) return;
|
|
14999
|
-
for (const specifier of node.specifiers) {
|
|
15000
|
-
if (specifier.type === AST_NODE_TYPES56.ImportSpecifier) {
|
|
15001
|
-
const imported = specifier.imported.type === AST_NODE_TYPES56.Identifier ? specifier.imported.name : String(specifier.imported.value);
|
|
15002
|
-
if (FS_READERS.has(imported)) declare(specifier.local.name, { fsReader: true });
|
|
15003
|
-
} else {
|
|
15004
|
-
declare(specifier.local.name, { fsObject: true });
|
|
15005
|
-
}
|
|
14943
|
+
function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
|
|
14944
|
+
return createRule({
|
|
14945
|
+
name,
|
|
14946
|
+
documentation,
|
|
14947
|
+
meta: {
|
|
14948
|
+
type: "suggestion",
|
|
14949
|
+
docs: { description: documentation.summary },
|
|
14950
|
+
schema: [],
|
|
14951
|
+
messages: { rawSourceOracle: "Raw repository source text is the oracle. Parse or execute the artifact so comments, formatting, and unreachable blocks cannot satisfy the contract." }
|
|
14952
|
+
},
|
|
14953
|
+
defaultOptions: [],
|
|
14954
|
+
create(context) {
|
|
14955
|
+
if (!isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
|
|
14956
|
+
const scopes = [newScope()];
|
|
14957
|
+
const reportedOrigins = /* @__PURE__ */ new Set();
|
|
14958
|
+
const currentScope = () => scopes.at(-1) ?? scopes[0];
|
|
14959
|
+
const visible = (kind, name2) => {
|
|
14960
|
+
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
14961
|
+
const scope = scopes[index];
|
|
14962
|
+
if (scope.declared.has(name2)) return scope[kind].has(name2);
|
|
15006
14963
|
}
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
14964
|
+
return false;
|
|
14965
|
+
};
|
|
14966
|
+
const visibleRawOrigins = (name2) => {
|
|
14967
|
+
for (let index = scopes.length - 1; index >= 0; index--) {
|
|
14968
|
+
const scope = scopes[index];
|
|
14969
|
+
if (scope.declared.has(name2)) return scope.rawOrigins.get(name2) ?? /* @__PURE__ */ new Set();
|
|
14970
|
+
}
|
|
14971
|
+
return /* @__PURE__ */ new Set();
|
|
14972
|
+
};
|
|
14973
|
+
const sourcePath = (node) => {
|
|
14974
|
+
const current = unwrap5(node);
|
|
14975
|
+
const value = stringValue(current);
|
|
14976
|
+
if (value !== null) return sourceSuffixRe.test(value);
|
|
14977
|
+
if (current.type === AST_NODE_TYPES56.Identifier) return visible("paths", current.name);
|
|
14978
|
+
if (current.type === AST_NODE_TYPES56.BinaryExpression && current.operator === "+") {
|
|
14979
|
+
return sourcePath(current.left) || sourcePath(current.right);
|
|
14980
|
+
}
|
|
14981
|
+
if (current.type === AST_NODE_TYPES56.TemplateLiteral) return current.expressions.some(sourcePath);
|
|
14982
|
+
if (current.type === AST_NODE_TYPES56.CallExpression || current.type === AST_NODE_TYPES56.NewExpression) {
|
|
14983
|
+
return current.arguments.some((argument) => argument.type !== AST_NODE_TYPES56.SpreadElement && sourcePath(argument));
|
|
14984
|
+
}
|
|
14985
|
+
if (current.type === AST_NODE_TYPES56.MemberExpression) return sourcePath(current.object);
|
|
14986
|
+
return false;
|
|
14987
|
+
};
|
|
14988
|
+
const rawRead = (node) => {
|
|
14989
|
+
const current = unwrap5(node);
|
|
14990
|
+
if (current.type !== AST_NODE_TYPES56.CallExpression || current.arguments.length === 0) return false;
|
|
14991
|
+
const callee = unwrap5(current.callee);
|
|
14992
|
+
if (callee.type === AST_NODE_TYPES56.Identifier) {
|
|
14993
|
+
return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
|
|
14994
|
+
}
|
|
14995
|
+
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return false;
|
|
14996
|
+
const name2 = staticMemberName5(callee);
|
|
14997
|
+
const object = unwrap5(callee.object);
|
|
14998
|
+
return name2 !== null && FS_READERS.has(name2) && object.type === AST_NODE_TYPES56.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
|
|
14999
|
+
};
|
|
15000
|
+
const rawOrigins = (node) => {
|
|
15001
|
+
const current = unwrap5(node);
|
|
15002
|
+
if (current.type === AST_NODE_TYPES56.Identifier) return visibleRawOrigins(current.name);
|
|
15003
|
+
if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
|
|
15004
|
+
if (current.type === AST_NODE_TYPES56.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
|
|
15005
|
+
if (current.type === AST_NODE_TYPES56.MemberExpression && staticMemberName5(current) === "length") return rawOrigins(current.object);
|
|
15006
|
+
if (current.type !== AST_NODE_TYPES56.CallExpression) return /* @__PURE__ */ new Set();
|
|
15007
|
+
const callee = unwrap5(current.callee);
|
|
15008
|
+
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return /* @__PURE__ */ new Set();
|
|
15009
|
+
const name2 = staticMemberName5(callee);
|
|
15010
|
+
return name2 !== null && TEXT_TRANSFORMS.has(name2) ? rawOrigins(callee.object) : /* @__PURE__ */ new Set();
|
|
15011
|
+
};
|
|
15012
|
+
const evidenceOrigins = (node) => {
|
|
15013
|
+
const current = unwrap5(node);
|
|
15014
|
+
const direct = rawOrigins(current);
|
|
15015
|
+
if (direct.size > 0) return direct;
|
|
15016
|
+
if (current.type === AST_NODE_TYPES56.BinaryExpression || current.type === AST_NODE_TYPES56.LogicalExpression) return /* @__PURE__ */ new Set([...evidenceOrigins(current.left), ...evidenceOrigins(current.right)]);
|
|
15017
|
+
if (current.type === AST_NODE_TYPES56.UnaryExpression) return evidenceOrigins(current.argument);
|
|
15018
|
+
if (current.type !== AST_NODE_TYPES56.CallExpression) return /* @__PURE__ */ new Set();
|
|
15019
|
+
const callee = unwrap5(current.callee);
|
|
15020
|
+
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return /* @__PURE__ */ new Set();
|
|
15021
|
+
const name2 = staticMemberName5(callee);
|
|
15022
|
+
if (name2 !== null && TEXT_PREDICATES.has(name2)) return rawOrigins(callee.object);
|
|
15023
|
+
if (name2 !== null && REGEXP_PREDICATES.has(name2)) return new Set(current.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...rawOrigins(argument)]));
|
|
15024
|
+
return /* @__PURE__ */ new Set();
|
|
15025
|
+
};
|
|
15026
|
+
const rawAssertionOrigins = (node) => {
|
|
15027
|
+
const callee = unwrap5(node.callee);
|
|
15028
|
+
if (callee.type === AST_NODE_TYPES56.Identifier && callee.name === "assert") {
|
|
15029
|
+
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
15030
|
+
}
|
|
15031
|
+
if (callee.type !== AST_NODE_TYPES56.MemberExpression) return /* @__PURE__ */ new Set();
|
|
15032
|
+
const matcher = staticMemberName5(callee);
|
|
15033
|
+
if (matcher === null) return /* @__PURE__ */ new Set();
|
|
15034
|
+
let receiver = unwrap5(callee.object);
|
|
15035
|
+
while (receiver.type === AST_NODE_TYPES56.MemberExpression && EXPECT_MODIFIERS.has(staticMemberName5(receiver) ?? "")) receiver = unwrap5(receiver.object);
|
|
15036
|
+
if (receiver.type === AST_NODE_TYPES56.CallExpression && receiver.callee.type === AST_NODE_TYPES56.Identifier && receiver.callee.name === "expect") {
|
|
15037
|
+
if (!EXPECT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
|
|
15038
|
+
return new Set([...receiver.arguments, ...node.arguments].flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
15039
|
+
}
|
|
15040
|
+
if (receiver.type !== AST_NODE_TYPES56.Identifier || receiver.name !== "assert" || !ASSERT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
|
|
15041
|
+
return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES56.SpreadElement ? [] : [...evidenceOrigins(argument)]));
|
|
15042
|
+
};
|
|
15043
|
+
const declare = (name2, state) => {
|
|
15044
|
+
const scope = currentScope();
|
|
15045
|
+
scope.declared.add(name2);
|
|
15046
|
+
scope.collections.delete(name2);
|
|
15047
|
+
scope.fsObjects.delete(name2);
|
|
15048
|
+
scope.fsReaders.delete(name2);
|
|
15049
|
+
scope.paths.delete(name2);
|
|
15050
|
+
scope.rawOrigins.delete(name2);
|
|
15051
|
+
if (state.collection === true) scope.collections.add(name2);
|
|
15052
|
+
if (state.fsObject === true) scope.fsObjects.add(name2);
|
|
15053
|
+
if (state.fsReader === true) scope.fsReaders.add(name2);
|
|
15054
|
+
if (state.path === true) scope.paths.add(name2);
|
|
15055
|
+
if (state.rawOrigins !== void 0 && state.rawOrigins.size > 0) {
|
|
15056
|
+
scope.rawOrigins.set(name2, state.rawOrigins);
|
|
15016
15057
|
}
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15058
|
+
};
|
|
15059
|
+
const sourceCollection = (node) => {
|
|
15060
|
+
const current = unwrap5(node);
|
|
15061
|
+
return current.type === AST_NODE_TYPES56.ArrayExpression && current.elements.length > 0 && current.elements.every((element) => element !== null && element.type !== AST_NODE_TYPES56.SpreadElement && sourcePath(element));
|
|
15062
|
+
};
|
|
15063
|
+
const declaredNames2 = (node) => {
|
|
15064
|
+
const current = unwrap5(node);
|
|
15065
|
+
if (current.type === AST_NODE_TYPES56.Identifier) return [current.name];
|
|
15066
|
+
if (current.type === AST_NODE_TYPES56.AssignmentPattern) return declaredNames2(current.left);
|
|
15067
|
+
if (current.type === AST_NODE_TYPES56.RestElement) return declaredNames2(current.argument);
|
|
15068
|
+
if (current.type === AST_NODE_TYPES56.ArrayPattern) return current.elements.flatMap((element) => element === null ? [] : declaredNames2(element));
|
|
15069
|
+
if (current.type === AST_NODE_TYPES56.ObjectPattern) return current.properties.flatMap((property) => property.type === AST_NODE_TYPES56.RestElement ? declaredNames2(property.argument) : declaredNames2(property.value));
|
|
15070
|
+
return [];
|
|
15071
|
+
};
|
|
15072
|
+
const enterFunction = (node) => {
|
|
15073
|
+
scopes.push(newScope());
|
|
15074
|
+
for (const parameter of node.params) for (const name2 of declaredNames2(parameter)) declare(name2, {});
|
|
15075
|
+
};
|
|
15076
|
+
const exitFunction = () => {
|
|
15077
|
+
scopes.pop();
|
|
15078
|
+
};
|
|
15079
|
+
return {
|
|
15080
|
+
ImportDeclaration(node) {
|
|
15081
|
+
const source = importSource(node);
|
|
15082
|
+
if (source === null || !FS_MODULES.has(source)) return;
|
|
15083
|
+
for (const specifier of node.specifiers) {
|
|
15084
|
+
if (specifier.type === AST_NODE_TYPES56.ImportSpecifier) {
|
|
15085
|
+
const imported = specifier.imported.type === AST_NODE_TYPES56.Identifier ? specifier.imported.name : String(specifier.imported.value);
|
|
15086
|
+
if (FS_READERS.has(imported)) declare(specifier.local.name, { fsReader: true });
|
|
15087
|
+
} else {
|
|
15088
|
+
declare(specifier.local.name, { fsObject: true });
|
|
15089
|
+
}
|
|
15022
15090
|
}
|
|
15023
|
-
|
|
15091
|
+
},
|
|
15092
|
+
":function": enterFunction,
|
|
15093
|
+
":function:exit": exitFunction,
|
|
15094
|
+
VariableDeclarator(node) {
|
|
15095
|
+
if (node.init === null) return;
|
|
15096
|
+
const required = requireSource(node.init);
|
|
15097
|
+
if (required !== null && FS_MODULES.has(required) && node.id.type === AST_NODE_TYPES56.Identifier) {
|
|
15098
|
+
declare(node.id.name, { fsObject: true });
|
|
15099
|
+
return;
|
|
15100
|
+
}
|
|
15101
|
+
if (node.id.type === AST_NODE_TYPES56.ObjectPattern && required !== null && FS_MODULES.has(required)) {
|
|
15102
|
+
for (const property of node.id.properties) {
|
|
15103
|
+
if (property.type !== AST_NODE_TYPES56.Property || property.value.type !== AST_NODE_TYPES56.Identifier) continue;
|
|
15104
|
+
const key = property.key.type === AST_NODE_TYPES56.Identifier ? property.key.name : property.key.type === AST_NODE_TYPES56.Literal ? String(property.key.value) : "";
|
|
15105
|
+
if (FS_READERS.has(key)) declare(property.value.name, { fsReader: true });
|
|
15106
|
+
}
|
|
15107
|
+
return;
|
|
15108
|
+
}
|
|
15109
|
+
if (node.id.type !== AST_NODE_TYPES56.Identifier) return;
|
|
15110
|
+
declare(node.id.name, { collection: sourceCollection(node.init), path: sourcePath(node.init), rawOrigins: rawOrigins(node.init) });
|
|
15111
|
+
},
|
|
15112
|
+
AssignmentExpression(node) {
|
|
15113
|
+
if (node.left.type === AST_NODE_TYPES56.Identifier) declare(node.left.name, { path: sourcePath(node.right), rawOrigins: rawOrigins(node.right) });
|
|
15114
|
+
},
|
|
15115
|
+
ForOfStatement(node) {
|
|
15116
|
+
const right = unwrap5(node.right);
|
|
15117
|
+
const collection = right.type === AST_NODE_TYPES56.Identifier && visible("collections", right.name);
|
|
15118
|
+
const left = node.left.type === AST_NODE_TYPES56.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
|
|
15119
|
+
if (collection && left?.type === AST_NODE_TYPES56.Identifier) declare(left.name, { path: true });
|
|
15120
|
+
},
|
|
15121
|
+
CallExpression(node) {
|
|
15122
|
+
const origins = rawAssertionOrigins(node);
|
|
15123
|
+
if (origins.size === 0 || [...origins].every((origin) => reportedOrigins.has(origin))) return;
|
|
15124
|
+
for (const origin of origins) reportedOrigins.add(origin);
|
|
15125
|
+
context.report({ node, messageId: "rawSourceOracle" });
|
|
15024
15126
|
}
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15127
|
+
};
|
|
15128
|
+
}
|
|
15129
|
+
});
|
|
15130
|
+
}
|
|
15131
|
+
var source_coupled_test_default = createSourceCoupledRule(
|
|
15132
|
+
"source-coupled-test",
|
|
15133
|
+
sourceCoupledTestDocumentation,
|
|
15134
|
+
GENERAL_SOURCE_SUFFIX_RE
|
|
15135
|
+
);
|
|
15136
|
+
|
|
15137
|
+
// src/rules/iac-source-coupled-test.ts
|
|
15138
|
+
var IAC_SOURCE_SUFFIX_RE = /(?:\.tf\.json|\.tftest\.(?:hcl|json)|\.(?:hcl|tf|tfvars))$/iu;
|
|
15139
|
+
var iacSourceCoupledTestDocumentation = {
|
|
15140
|
+
summary: "Disallow raw IaC source text as a test oracle; inspect a rendered plan, provider state, or runtime behavior.",
|
|
15141
|
+
rationale: "Substring and regex checks can pass on comments, formatting, or unreachable Terraform configuration while clients fail silently.",
|
|
15142
|
+
remediation: "Parse rendered plan JSON, query the provider, or exercise the deployed runtime contract.",
|
|
15143
|
+
category: "testing",
|
|
15144
|
+
limitations: [
|
|
15145
|
+
"The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
|
|
15146
|
+
"The warning-stage rule remains suppressible for calibration; promotion may make the locked policy non-suppressible."
|
|
15147
|
+
],
|
|
15148
|
+
examples: [
|
|
15149
|
+
{
|
|
15150
|
+
id: "rendered-plan-contract",
|
|
15151
|
+
title: "Assert on rendered plan behavior",
|
|
15152
|
+
outcome: "no-match",
|
|
15153
|
+
files: [{ path: "src/policy.test.ts", source: "import { readFileSync } from 'node:fs'; test('policy', () => { const plan = JSON.parse(readFileSync('plan.json', 'utf8')); expect(validate(plan)).toEqual([]); });" }],
|
|
15154
|
+
focusPath: "src/policy.test.ts",
|
|
15155
|
+
expectedCount: 0,
|
|
15156
|
+
public: true
|
|
15157
|
+
},
|
|
15158
|
+
{
|
|
15159
|
+
id: "terraform-substring-contract",
|
|
15160
|
+
title: "Do not prove Terraform behavior with a regex",
|
|
15161
|
+
outcome: "match",
|
|
15162
|
+
files: [{ path: "src/policy.test.ts", source: "import { readFileSync } from 'node:fs'; test('policy', () => { const source = readFileSync('main.tf', 'utf8'); expect(source).toMatch(/prevent_destroy/); });" }],
|
|
15163
|
+
focusPath: "src/policy.test.ts",
|
|
15164
|
+
expectedCount: 1,
|
|
15165
|
+
public: true
|
|
15166
|
+
}
|
|
15167
|
+
]
|
|
15168
|
+
};
|
|
15169
|
+
var iac_source_coupled_test_default = createSourceCoupledRule(
|
|
15170
|
+
"iac-source-coupled-test",
|
|
15171
|
+
iacSourceCoupledTestDocumentation,
|
|
15172
|
+
IAC_SOURCE_SUFFIX_RE
|
|
15173
|
+
);
|
|
15046
15174
|
|
|
15047
15175
|
// src/rules/zod-naming-convention.ts
|
|
15048
15176
|
import {
|
|
@@ -15270,6 +15398,7 @@ var retiredRules = {
|
|
|
15270
15398
|
|
|
15271
15399
|
// src/index.ts
|
|
15272
15400
|
var rules = {
|
|
15401
|
+
"iac-source-coupled-test": iac_source_coupled_test_default,
|
|
15273
15402
|
"duplicate-test-body": duplicate_test_body_default,
|
|
15274
15403
|
"enforce-file-structure": enforce_file_structure_default,
|
|
15275
15404
|
"no-client-side-data-fetching": no_client_side_data_fetching_default,
|
|
@@ -15285,6 +15414,7 @@ var rules = {
|
|
|
15285
15414
|
"no-impossible-zod-literal-bounds": no_impossible_zod_literal_bounds_default,
|
|
15286
15415
|
"no-log-only-catch": no_log_only_catch_default,
|
|
15287
15416
|
"no-long-comment": no_long_comment_default,
|
|
15417
|
+
"no-vague-suppression-description": no_vague_suppression_description_default,
|
|
15288
15418
|
"no-generic-single-export-module": no_generic_single_export_module_default,
|
|
15289
15419
|
"no-offset-pagination": no_offset_pagination_default,
|
|
15290
15420
|
"no-positional-tuple-return": no_positional_tuple_return_default,
|
|
@@ -15338,15 +15468,16 @@ var rules = {
|
|
|
15338
15468
|
};
|
|
15339
15469
|
var meta = {
|
|
15340
15470
|
name: "@sarj/eslint-plugin",
|
|
15341
|
-
version: "15.
|
|
15471
|
+
version: "15.7.0"
|
|
15342
15472
|
};
|
|
15343
15473
|
var applicationOnlyRules = [
|
|
15344
15474
|
"no-restricted-library-load",
|
|
15345
15475
|
"prefer-native-random-uuid",
|
|
15346
15476
|
"prefer-shadcn-primitives"
|
|
15347
15477
|
];
|
|
15348
|
-
var advisoryRules = ["source-coupled-test"];
|
|
15478
|
+
var advisoryRules = ["no-vague-suppression-description", "iac-source-coupled-test", "source-coupled-test"];
|
|
15349
15479
|
var recommendedRules = {
|
|
15480
|
+
"@sarj/iac-source-coupled-test": "warn",
|
|
15350
15481
|
"@sarj/duplicate-test-body": "error",
|
|
15351
15482
|
"@sarj/enforce-file-structure": "error",
|
|
15352
15483
|
"@sarj/no-client-side-data-fetching": "error",
|
|
@@ -15361,6 +15492,7 @@ var recommendedRules = {
|
|
|
15361
15492
|
"@sarj/no-impossible-zod-literal-bounds": "error",
|
|
15362
15493
|
"@sarj/no-log-only-catch": "error",
|
|
15363
15494
|
"@sarj/no-long-comment": "error",
|
|
15495
|
+
"@sarj/no-vague-suppression-description": "warn",
|
|
15364
15496
|
"@sarj/no-generic-single-export-module": "error",
|
|
15365
15497
|
"@sarj/no-offset-pagination": "error",
|
|
15366
15498
|
"@sarj/no-positional-tuple-return": "error",
|
|
@@ -15407,6 +15539,7 @@ var recommendedRules = {
|
|
|
15407
15539
|
"@sarj/zod-naming-convention": "error"
|
|
15408
15540
|
};
|
|
15409
15541
|
var strictRules = {
|
|
15542
|
+
"@sarj/iac-source-coupled-test": "warn",
|
|
15410
15543
|
"@sarj/duplicate-test-body": "error",
|
|
15411
15544
|
"@sarj/enforce-file-structure": "error",
|
|
15412
15545
|
"@sarj/no-client-side-data-fetching": "error",
|
|
@@ -15422,6 +15555,7 @@ var strictRules = {
|
|
|
15422
15555
|
"@sarj/no-impossible-zod-literal-bounds": "error",
|
|
15423
15556
|
"@sarj/no-log-only-catch": "error",
|
|
15424
15557
|
"@sarj/no-long-comment": "error",
|
|
15558
|
+
"@sarj/no-vague-suppression-description": "warn",
|
|
15425
15559
|
"@sarj/no-generic-single-export-module": "error",
|
|
15426
15560
|
"@sarj/no-offset-pagination": "error",
|
|
15427
15561
|
"@sarj/no-positional-tuple-return": "error",
|