@sarj/eslint-plugin 2.15.0 → 2.16.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 +232 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +232 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -467,9 +467,9 @@ function restatesStatementHead(body, statement) {
|
|
|
467
467
|
|
|
468
468
|
// src/rules/no-comment-cruft.ts
|
|
469
469
|
var LEADING_PREAMBLE_MIN = 4;
|
|
470
|
-
var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S
|
|
470
|
+
var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S/i;
|
|
471
471
|
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;
|
|
472
|
-
var DIRECTIVE_RE = /^(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|
|
|
472
|
+
var DIRECTIVE_RE = /^(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|hack\b|xxx\b)/i;
|
|
473
473
|
var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
|
|
474
474
|
var ENUMERATED_ITEM_RE = /^(?:\d+[.):]|[-*•])\s+\S/;
|
|
475
475
|
var ENUMERATED_ITEM_MIN_WORDS = 3;
|
|
@@ -535,7 +535,8 @@ function isSectionLabel(text) {
|
|
|
535
535
|
}
|
|
536
536
|
var HELPER_OPENER_RE = /^(?:a\s+)?helper\s+(?:function|method|component|hook|class|type|util(?:ity)?)\b/i;
|
|
537
537
|
var LETS_RE = /^let'?s\s+(?:not\s+|just\s+|now\s+|first\s+)?(?:add|append|assign|await|build|calculate|call|check|clear|close|compute|convert|copy|count|create|declare|decrement|define|delete|extract|fetch|filter|find|format|generate|get|handle|increment|init|initialise|initialize|insert|iterate|join|load|log|loop|map|merge|open|parse|print|process|push|read|remove|render|reset|return|save|send|set|setup|sort|split|start|stop|store|update|validate|wrap|write)(?:s|es|ed|ing)?\b/i;
|
|
538
|
-
var ENUMERATION_RE = /^(?:\d+[.)]\s+\S|phase\s+\d+\b)/i;
|
|
538
|
+
var ENUMERATION_RE = /^(?:\d+[.)]\s+\S|(?:phase|step)\s+\d+\b)/i;
|
|
539
|
+
var DUMMY_TRANSLATION_RE = /^(?:increment|return|returns|get|gets|set\b(?! up\b)|sets\b(?! up\b)|function to|method to)\b/i;
|
|
539
540
|
var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
|
|
540
541
|
var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
|
|
541
542
|
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
@@ -583,6 +584,14 @@ function isRedundantNarration(body, statementBelow, standalone, isolatedEnumerat
|
|
|
583
584
|
if (STEP_NARRATION_RE.test(t)) return true;
|
|
584
585
|
if (META_COMMENTARY_RE.test(t) && !JUSTIFICATION_RE.test(t)) return true;
|
|
585
586
|
if (HELPER_OPENER_RE.test(t) || LETS_RE.test(t)) return true;
|
|
587
|
+
const words = t.split(/\s+/);
|
|
588
|
+
if (words.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
|
|
589
|
+
const lowerT = t.toLowerCase();
|
|
590
|
+
const rationaleWords = ["when", "because", "if", "so that", "due to", "for", "instead of", "to prevent", "to avoid", "only"];
|
|
591
|
+
if (!rationaleWords.some((w) => lowerT.includes(w))) {
|
|
592
|
+
if (words.length > 1) return true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
586
595
|
if (!nested && isSectionLabel(t)) return true;
|
|
587
596
|
if (isolatedEnumeration && ENUMERATION_RE.test(t)) return true;
|
|
588
597
|
}
|
|
@@ -650,7 +659,8 @@ var no_comment_cruft_default = import_utils4.ESLintUtils.RuleCreator(
|
|
|
650
659
|
commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
|
|
651
660
|
sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
|
|
652
661
|
fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
|
|
653
|
-
redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting."
|
|
662
|
+
redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting.",
|
|
663
|
+
untrackedTodo: "Untracked TODO/FIXME marker \u2014 add an issue ticket or context link."
|
|
654
664
|
}
|
|
655
665
|
},
|
|
656
666
|
defaultOptions: [],
|
|
@@ -704,6 +714,16 @@ var no_comment_cruft_default = import_utils4.ESLintUtils.RuleCreator(
|
|
|
704
714
|
if (isJsDoc(comment) || !isStandalone(comment)) continue;
|
|
705
715
|
if (LICENSE_RE.test(comment.value)) continue;
|
|
706
716
|
const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
|
|
717
|
+
const firstText = texts[0];
|
|
718
|
+
if (firstText !== void 0 && /^(?:todo|fixme)\b/i.test(firstText)) {
|
|
719
|
+
if (!runCitesAReference(comments, i)) {
|
|
720
|
+
context.report({
|
|
721
|
+
node: comment,
|
|
722
|
+
messageId: "untrackedTodo"
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
707
727
|
if (texts.some(isBanner)) {
|
|
708
728
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
709
729
|
continue;
|
|
@@ -7495,6 +7515,204 @@ var prefer_non_nullable_collection_default = import_utils52.ESLintUtils.RuleCrea
|
|
|
7495
7515
|
}
|
|
7496
7516
|
});
|
|
7497
7517
|
|
|
7518
|
+
// src/rules/primary-export-file-name.ts
|
|
7519
|
+
var import_utils53 = require("@typescript-eslint/utils");
|
|
7520
|
+
var CONVENTIONAL_BUCKET_EXPORTS2 = /* @__PURE__ */ new Set(["cn"]);
|
|
7521
|
+
var GENERIC_ENTRYPOINTS = /* @__PURE__ */ new Set(["main", "run", "cli", "setup", "teardown", "execute"]);
|
|
7522
|
+
var ACRONYM_OVERRIDES2 = [
|
|
7523
|
+
[/OAuth/g, "Oauth"],
|
|
7524
|
+
[/GraphQL/g, "Graphql"],
|
|
7525
|
+
[/gRPC/g, "Grpc"]
|
|
7526
|
+
];
|
|
7527
|
+
var CAMEL_BOUNDARY_RE2 = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
|
|
7528
|
+
var basename2 = (filename) => filename.split(/[/\\]/).pop() ?? filename;
|
|
7529
|
+
var stemOf2 = (base) => base.replace(/\.(test|spec|config|d|stories)\.[cm]?[jt]sx?$/i, "").replace(/\.[cm]?[jt]sx?$/i, "");
|
|
7530
|
+
var kebabCase3 = (name) => {
|
|
7531
|
+
let normalized = name;
|
|
7532
|
+
for (const [pattern, replacement] of ACRONYM_OVERRIDES2) {
|
|
7533
|
+
normalized = normalized.replace(pattern, replacement);
|
|
7534
|
+
}
|
|
7535
|
+
return normalized.replace(CAMEL_BOUNDARY_RE2, "-").toLowerCase();
|
|
7536
|
+
};
|
|
7537
|
+
var isFunctionOrClass = (node) => node.type === import_utils53.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils53.AST_NODE_TYPES.FunctionExpression;
|
|
7538
|
+
var findPrimaryExport = (body) => {
|
|
7539
|
+
let exportCount = 0;
|
|
7540
|
+
let primaryCandidate = null;
|
|
7541
|
+
for (const statement of body) {
|
|
7542
|
+
if (statement.type === import_utils53.AST_NODE_TYPES.ExportAllDeclaration) {
|
|
7543
|
+
return null;
|
|
7544
|
+
}
|
|
7545
|
+
if (statement.type === import_utils53.AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
7546
|
+
exportCount += 1;
|
|
7547
|
+
const decl = statement.declaration;
|
|
7548
|
+
if ((decl.type === import_utils53.AST_NODE_TYPES.FunctionDeclaration || decl.type === import_utils53.AST_NODE_TYPES.ClassDeclaration) && decl.id !== null) {
|
|
7549
|
+
primaryCandidate = { name: decl.id.name, node: statement, isDefault: true };
|
|
7550
|
+
} else if (decl.type === import_utils53.AST_NODE_TYPES.Identifier) {
|
|
7551
|
+
primaryCandidate = { name: decl.name, node: statement, isDefault: true };
|
|
7552
|
+
}
|
|
7553
|
+
} else if (statement.type === import_utils53.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
7554
|
+
if (statement.source !== null) {
|
|
7555
|
+
return null;
|
|
7556
|
+
}
|
|
7557
|
+
const decl = statement.declaration;
|
|
7558
|
+
if (decl === null) {
|
|
7559
|
+
exportCount += statement.specifiers.length;
|
|
7560
|
+
if (statement.specifiers.length === 1 && primaryCandidate === null) {
|
|
7561
|
+
const spec = statement.specifiers[0];
|
|
7562
|
+
if (spec && spec.exported.type === import_utils53.AST_NODE_TYPES.Identifier) {
|
|
7563
|
+
primaryCandidate = { name: spec.exported.name, node: statement, isDefault: false };
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
} else if (decl.type === import_utils53.AST_NODE_TYPES.FunctionDeclaration || decl.type === import_utils53.AST_NODE_TYPES.ClassDeclaration) {
|
|
7567
|
+
if (decl.id !== null) {
|
|
7568
|
+
exportCount += 1;
|
|
7569
|
+
if (primaryCandidate === null) {
|
|
7570
|
+
primaryCandidate = { name: decl.id.name, node: statement, isDefault: false };
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
} else if (decl.type === import_utils53.AST_NODE_TYPES.VariableDeclaration) {
|
|
7574
|
+
for (const declarator of decl.declarations) {
|
|
7575
|
+
if (declarator.id.type === import_utils53.AST_NODE_TYPES.Identifier) {
|
|
7576
|
+
exportCount += 1;
|
|
7577
|
+
if (primaryCandidate === null && declarator.init && isFunctionOrClass(declarator.init)) {
|
|
7578
|
+
primaryCandidate = { name: declarator.id.name, node: statement, isDefault: false };
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
} else if (decl.type === import_utils53.AST_NODE_TYPES.TSTypeAliasDeclaration || decl.type === import_utils53.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
7583
|
+
exportCount += 1;
|
|
7584
|
+
if (primaryCandidate === null) {
|
|
7585
|
+
primaryCandidate = { name: decl.id.name, node: statement, isDefault: false };
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
if (primaryCandidate === null || exportCount > 2) {
|
|
7591
|
+
return null;
|
|
7592
|
+
}
|
|
7593
|
+
return { ...primaryCandidate, count: exportCount };
|
|
7594
|
+
};
|
|
7595
|
+
var primary_export_file_name_default = import_utils53.ESLintUtils.RuleCreator(
|
|
7596
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
7597
|
+
)({
|
|
7598
|
+
name: "primary-export-file-name",
|
|
7599
|
+
meta: {
|
|
7600
|
+
type: "suggestion",
|
|
7601
|
+
docs: {
|
|
7602
|
+
description: "Enforce semantic naming alignment: a file with a single primary export must be named after that export."
|
|
7603
|
+
},
|
|
7604
|
+
schema: [],
|
|
7605
|
+
messages: {
|
|
7606
|
+
primaryExportMismatch: "Module stem '{{stem}}' does not match its primary export '{{name}}' \u2014 rename the file to '{{expected}}{{ext}}' to describe its responsibility."
|
|
7607
|
+
}
|
|
7608
|
+
},
|
|
7609
|
+
defaultOptions: [],
|
|
7610
|
+
create(context) {
|
|
7611
|
+
const base = basename2(context.filename);
|
|
7612
|
+
if (base.endsWith(".d.ts") || base.startsWith("index.") || base.startsWith("_")) return {};
|
|
7613
|
+
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
|
|
7614
|
+
const stem3 = stemOf2(base);
|
|
7615
|
+
if (stem3 === "page" || stem3 === "layout" || stem3 === "loading" || stem3 === "error" || stem3 === "not-found" || stem3 === "route" || stem3 === "__root" || stem3 === "config" || stem3 === "constants" || stem3 === "types" || stem3.startsWith("$") || stem3.startsWith("[")) {
|
|
7616
|
+
return {};
|
|
7617
|
+
}
|
|
7618
|
+
return {
|
|
7619
|
+
Program(node) {
|
|
7620
|
+
const primary = findPrimaryExport(node.body);
|
|
7621
|
+
if (primary === null) return;
|
|
7622
|
+
if (CONVENTIONAL_BUCKET_EXPORTS2.has(primary.name) || GENERIC_ENTRYPOINTS.has(primary.name)) return;
|
|
7623
|
+
const expectedStem = kebabCase3(primary.name);
|
|
7624
|
+
if (stem3 === expectedStem) return;
|
|
7625
|
+
const ext = base.endsWith(".tsx") ? ".tsx" : ".ts";
|
|
7626
|
+
context.report({
|
|
7627
|
+
node: primary.node,
|
|
7628
|
+
messageId: "primaryExportMismatch",
|
|
7629
|
+
data: { stem: stem3, name: primary.name, expected: expectedStem, ext }
|
|
7630
|
+
});
|
|
7631
|
+
}
|
|
7632
|
+
};
|
|
7633
|
+
}
|
|
7634
|
+
});
|
|
7635
|
+
|
|
7636
|
+
// src/rules/no-implicit-attribute-access.ts
|
|
7637
|
+
var import_utils54 = require("@typescript-eslint/utils");
|
|
7638
|
+
var EXCLUDED_BASES = /* @__PURE__ */ new Set([
|
|
7639
|
+
"environ",
|
|
7640
|
+
"headers",
|
|
7641
|
+
"cookies",
|
|
7642
|
+
"session",
|
|
7643
|
+
"redis",
|
|
7644
|
+
"cache",
|
|
7645
|
+
"state",
|
|
7646
|
+
"config",
|
|
7647
|
+
"env",
|
|
7648
|
+
"process",
|
|
7649
|
+
"localStorage",
|
|
7650
|
+
"sessionStorage"
|
|
7651
|
+
]);
|
|
7652
|
+
function getBaseName(node) {
|
|
7653
|
+
if (node.type === import_utils54.AST_NODE_TYPES.Identifier) {
|
|
7654
|
+
return node.name;
|
|
7655
|
+
}
|
|
7656
|
+
if (node.type === import_utils54.AST_NODE_TYPES.MemberExpression && node.property.type === import_utils54.AST_NODE_TYPES.Identifier) {
|
|
7657
|
+
return node.property.name;
|
|
7658
|
+
}
|
|
7659
|
+
return null;
|
|
7660
|
+
}
|
|
7661
|
+
var no_implicit_attribute_access_default = import_utils54.ESLintUtils.RuleCreator(
|
|
7662
|
+
(name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
|
|
7663
|
+
)({
|
|
7664
|
+
name: "no-implicit-attribute-access",
|
|
7665
|
+
meta: {
|
|
7666
|
+
type: "problem",
|
|
7667
|
+
docs: {
|
|
7668
|
+
description: "Disallow imperative dictionary access with string literals; use declarative Zod schemas."
|
|
7669
|
+
},
|
|
7670
|
+
schema: [],
|
|
7671
|
+
messages: {
|
|
7672
|
+
noImplicitAttributeAccess: "Imperative lookup for '{{key}}' \u2014 if the key is known, parse the object declaratively with a Zod schema instead."
|
|
7673
|
+
}
|
|
7674
|
+
},
|
|
7675
|
+
defaultOptions: [],
|
|
7676
|
+
create(context) {
|
|
7677
|
+
return {
|
|
7678
|
+
MemberExpression(node) {
|
|
7679
|
+
if (!node.computed) {
|
|
7680
|
+
return;
|
|
7681
|
+
}
|
|
7682
|
+
if (node.property.type === import_utils54.AST_NODE_TYPES.Literal && typeof node.property.value === "string") {
|
|
7683
|
+
const baseName = getBaseName(node.object);
|
|
7684
|
+
if (!baseName || !EXCLUDED_BASES.has(baseName)) {
|
|
7685
|
+
context.report({
|
|
7686
|
+
node,
|
|
7687
|
+
messageId: "noImplicitAttributeAccess",
|
|
7688
|
+
data: { key: node.property.value }
|
|
7689
|
+
});
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
},
|
|
7693
|
+
CallExpression(node) {
|
|
7694
|
+
const callee = node.callee;
|
|
7695
|
+
if (callee.type === import_utils54.AST_NODE_TYPES.MemberExpression) {
|
|
7696
|
+
const method = callee.property.type === import_utils54.AST_NODE_TYPES.Identifier ? callee.property.name : null;
|
|
7697
|
+
if (method === "get") {
|
|
7698
|
+
const arg = node.arguments[0];
|
|
7699
|
+
if (arg && arg.type === import_utils54.AST_NODE_TYPES.Literal && typeof arg.value === "string") {
|
|
7700
|
+
const baseName = getBaseName(callee.object);
|
|
7701
|
+
if (!baseName || !EXCLUDED_BASES.has(baseName)) {
|
|
7702
|
+
context.report({
|
|
7703
|
+
node,
|
|
7704
|
+
messageId: "noImplicitAttributeAccess",
|
|
7705
|
+
data: { key: arg.value }
|
|
7706
|
+
});
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7711
|
+
}
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
});
|
|
7715
|
+
|
|
7498
7716
|
// src/index.ts
|
|
7499
7717
|
var rules = {
|
|
7500
7718
|
"enforce-file-structure": enforce_file_structure_default,
|
|
@@ -7523,6 +7741,7 @@ var rules = {
|
|
|
7523
7741
|
"no-unsafe-cast": no_unsafe_cast_default,
|
|
7524
7742
|
"prefer-string-literal-union": prefer_string_literal_union_default,
|
|
7525
7743
|
"single-public-export": single_public_export_default,
|
|
7744
|
+
"primary-export-file-name": primary_export_file_name_default,
|
|
7526
7745
|
"no-silent-promise-catch": no_silent_promise_catch_default,
|
|
7527
7746
|
"require-fetch-timeout": require_fetch_timeout_default,
|
|
7528
7747
|
"require-schema-validate-search": require_schema_validate_search_default,
|
|
@@ -7543,12 +7762,13 @@ var rules = {
|
|
|
7543
7762
|
"trailing-value-narration": trailing_value_narration_default,
|
|
7544
7763
|
"no-tautological-expect": no_tautological_expect_default,
|
|
7545
7764
|
"require-interface-for-injected-service": require_interface_for_injected_service_default,
|
|
7546
|
-
"prefer-non-nullable-collection": prefer_non_nullable_collection_default
|
|
7765
|
+
"prefer-non-nullable-collection": prefer_non_nullable_collection_default,
|
|
7766
|
+
"no-implicit-attribute-access": no_implicit_attribute_access_default
|
|
7547
7767
|
};
|
|
7548
7768
|
var plugin = {
|
|
7549
7769
|
meta: {
|
|
7550
7770
|
name: "@sarj/eslint-plugin",
|
|
7551
|
-
version: "2.
|
|
7771
|
+
version: "2.16.0"
|
|
7552
7772
|
},
|
|
7553
7773
|
rules,
|
|
7554
7774
|
configs: {
|
|
@@ -7583,6 +7803,7 @@ var plugin = {
|
|
|
7583
7803
|
"@sarj/no-secret-in-log": "warn",
|
|
7584
7804
|
"@sarj/no-unsafe-cast": "warn",
|
|
7585
7805
|
"@sarj/single-public-export": "warn",
|
|
7806
|
+
"@sarj/primary-export-file-name": "warn",
|
|
7586
7807
|
"@sarj/prefer-string-literal-union": "warn",
|
|
7587
7808
|
// Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
|
|
7588
7809
|
"@sarj/require-fetch-timeout": "warn",
|
|
@@ -7632,7 +7853,8 @@ var plugin = {
|
|
|
7632
7853
|
// mocking. 11-repo sweep: 229 exported classes, 82% already carry a
|
|
7633
7854
|
// port, 29 fire, 28 of them true positives.
|
|
7634
7855
|
"@sarj/require-interface-for-injected-service": "warn",
|
|
7635
|
-
"@sarj/prefer-non-nullable-collection": "warn"
|
|
7856
|
+
"@sarj/prefer-non-nullable-collection": "warn",
|
|
7857
|
+
"@sarj/no-implicit-attribute-access": "warn"
|
|
7636
7858
|
}
|
|
7637
7859
|
},
|
|
7638
7860
|
strict: {
|
|
@@ -7670,6 +7892,7 @@ var plugin = {
|
|
|
7670
7892
|
"@sarj/no-secret-in-log": "error",
|
|
7671
7893
|
"@sarj/no-unsafe-cast": "error",
|
|
7672
7894
|
"@sarj/single-public-export": "error",
|
|
7895
|
+
"@sarj/primary-export-file-name": "error",
|
|
7673
7896
|
// Promoted to error 2026-07-25 — strict means strict (user directive).
|
|
7674
7897
|
"@sarj/prefer-string-literal-union": "error",
|
|
7675
7898
|
// Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
|
|
@@ -7710,7 +7933,8 @@ var plugin = {
|
|
|
7710
7933
|
// / `prefer-library-fake` wave. The convention already exists in the
|
|
7711
7934
|
// corpus (175 `implements` clauses vs 29 hits), so strict enforces it.
|
|
7712
7935
|
"@sarj/require-interface-for-injected-service": "error",
|
|
7713
|
-
"@sarj/prefer-non-nullable-collection": "error"
|
|
7936
|
+
"@sarj/prefer-non-nullable-collection": "error",
|
|
7937
|
+
"@sarj/no-implicit-attribute-access": "error"
|
|
7714
7938
|
}
|
|
7715
7939
|
}
|
|
7716
7940
|
}
|