@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.d.cts CHANGED
@@ -203,7 +203,7 @@ declare const rules: {
203
203
  "no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
204
204
  name: string;
205
205
  };
206
- "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
206
+ "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
207
207
  name: string;
208
208
  };
209
209
  "no-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"noEnum", readonly [{
@@ -283,6 +283,9 @@ declare const rules: {
283
283
  "single-public-export": _typescript_eslint_utils_ts_eslint.RuleModule<"renameJunkDrawer", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
284
284
  name: string;
285
285
  };
286
+ "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
287
+ name: string;
288
+ };
286
289
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
287
290
  name: string;
288
291
  };
@@ -352,6 +355,9 @@ declare const rules: {
352
355
  "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
353
356
  name: string;
354
357
  };
358
+ "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
359
+ name: string;
360
+ };
355
361
  };
356
362
  declare const plugin: {
357
363
  meta: {
@@ -365,7 +371,7 @@ declare const plugin: {
365
371
  "no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
366
372
  name: string;
367
373
  };
368
- "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
374
+ "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
369
375
  name: string;
370
376
  };
371
377
  "no-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"noEnum", readonly [{
@@ -445,6 +451,9 @@ declare const plugin: {
445
451
  "single-public-export": _typescript_eslint_utils_ts_eslint.RuleModule<"renameJunkDrawer", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
446
452
  name: string;
447
453
  };
454
+ "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
455
+ name: string;
456
+ };
448
457
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
449
458
  name: string;
450
459
  };
@@ -514,6 +523,9 @@ declare const plugin: {
514
523
  "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
515
524
  name: string;
516
525
  };
526
+ "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
527
+ name: string;
528
+ };
517
529
  };
518
530
  configs: {
519
531
  recommended: {
@@ -543,6 +555,7 @@ declare const plugin: {
543
555
  "@sarj/no-secret-in-log": string;
544
556
  "@sarj/no-unsafe-cast": string;
545
557
  "@sarj/single-public-export": string;
558
+ "@sarj/primary-export-file-name": string;
546
559
  "@sarj/prefer-string-literal-union": string;
547
560
  "@sarj/require-fetch-timeout": string;
548
561
  "@sarj/no-silent-promise-catch": string;
@@ -563,6 +576,7 @@ declare const plugin: {
563
576
  "@sarj/no-tautological-expect": string;
564
577
  "@sarj/require-interface-for-injected-service": string;
565
578
  "@sarj/prefer-non-nullable-collection": string;
579
+ "@sarj/no-implicit-attribute-access": string;
566
580
  };
567
581
  };
568
582
  strict: {
@@ -595,6 +609,7 @@ declare const plugin: {
595
609
  "@sarj/no-secret-in-log": string;
596
610
  "@sarj/no-unsafe-cast": string;
597
611
  "@sarj/single-public-export": string;
612
+ "@sarj/primary-export-file-name": string;
598
613
  "@sarj/prefer-string-literal-union": string;
599
614
  "@sarj/require-fetch-timeout": string;
600
615
  "@sarj/no-silent-promise-catch": string;
@@ -617,6 +632,7 @@ declare const plugin: {
617
632
  "@sarj/no-tautological-expect": string;
618
633
  "@sarj/require-interface-for-injected-service": string;
619
634
  "@sarj/prefer-non-nullable-collection": string;
635
+ "@sarj/no-implicit-attribute-access": string;
620
636
  };
621
637
  };
622
638
  };
package/dist/index.d.ts CHANGED
@@ -203,7 +203,7 @@ declare const rules: {
203
203
  "no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
204
204
  name: string;
205
205
  };
206
- "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
206
+ "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
207
207
  name: string;
208
208
  };
209
209
  "no-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"noEnum", readonly [{
@@ -283,6 +283,9 @@ declare const rules: {
283
283
  "single-public-export": _typescript_eslint_utils_ts_eslint.RuleModule<"renameJunkDrawer", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
284
284
  name: string;
285
285
  };
286
+ "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
287
+ name: string;
288
+ };
286
289
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
287
290
  name: string;
288
291
  };
@@ -352,6 +355,9 @@ declare const rules: {
352
355
  "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
353
356
  name: string;
354
357
  };
358
+ "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
359
+ name: string;
360
+ };
355
361
  };
356
362
  declare const plugin: {
357
363
  meta: {
@@ -365,7 +371,7 @@ declare const plugin: {
365
371
  "no-client-side-data-fetching": _typescript_eslint_utils_ts_eslint.RuleModule<"noClientFetch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
366
372
  name: string;
367
373
  };
368
- "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
374
+ "no-comment-cruft": _typescript_eslint_utils_ts_eslint.RuleModule<"commentedOutCode" | "sectionBanner" | "fileHeaderPreamble" | "redundantNarration" | "untrackedTodo", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
369
375
  name: string;
370
376
  };
371
377
  "no-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"noEnum", readonly [{
@@ -445,6 +451,9 @@ declare const plugin: {
445
451
  "single-public-export": _typescript_eslint_utils_ts_eslint.RuleModule<"renameJunkDrawer", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
446
452
  name: string;
447
453
  };
454
+ "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
455
+ name: string;
456
+ };
448
457
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
449
458
  name: string;
450
459
  };
@@ -514,6 +523,9 @@ declare const plugin: {
514
523
  "prefer-non-nullable-collection": _typescript_eslint_utils_ts_eslint.RuleModule<"preferNonNullableCollection", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
515
524
  name: string;
516
525
  };
526
+ "no-implicit-attribute-access": _typescript_eslint_utils_ts_eslint.RuleModule<"noImplicitAttributeAccess", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
527
+ name: string;
528
+ };
517
529
  };
518
530
  configs: {
519
531
  recommended: {
@@ -543,6 +555,7 @@ declare const plugin: {
543
555
  "@sarj/no-secret-in-log": string;
544
556
  "@sarj/no-unsafe-cast": string;
545
557
  "@sarj/single-public-export": string;
558
+ "@sarj/primary-export-file-name": string;
546
559
  "@sarj/prefer-string-literal-union": string;
547
560
  "@sarj/require-fetch-timeout": string;
548
561
  "@sarj/no-silent-promise-catch": string;
@@ -563,6 +576,7 @@ declare const plugin: {
563
576
  "@sarj/no-tautological-expect": string;
564
577
  "@sarj/require-interface-for-injected-service": string;
565
578
  "@sarj/prefer-non-nullable-collection": string;
579
+ "@sarj/no-implicit-attribute-access": string;
566
580
  };
567
581
  };
568
582
  strict: {
@@ -595,6 +609,7 @@ declare const plugin: {
595
609
  "@sarj/no-secret-in-log": string;
596
610
  "@sarj/no-unsafe-cast": string;
597
611
  "@sarj/single-public-export": string;
612
+ "@sarj/primary-export-file-name": string;
598
613
  "@sarj/prefer-string-literal-union": string;
599
614
  "@sarj/require-fetch-timeout": string;
600
615
  "@sarj/no-silent-promise-catch": string;
@@ -617,6 +632,7 @@ declare const plugin: {
617
632
  "@sarj/no-tautological-expect": string;
618
633
  "@sarj/require-interface-for-injected-service": string;
619
634
  "@sarj/prefer-non-nullable-collection": string;
635
+ "@sarj/no-implicit-attribute-access": string;
620
636
  };
621
637
  };
622
638
  };
package/dist/index.js CHANGED
@@ -433,9 +433,9 @@ function restatesStatementHead(body, statement) {
433
433
 
434
434
  // src/rules/no-comment-cruft.ts
435
435
  var LEADING_PREAMBLE_MIN = 4;
436
- 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;
436
+ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S/i;
437
437
  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;
438
- 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|todo\b|fixme\b|hack\b|xxx\b)/i;
438
+ 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;
439
439
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
440
440
  var ENUMERATED_ITEM_RE = /^(?:\d+[.):]|[-*•])\s+\S/;
441
441
  var ENUMERATED_ITEM_MIN_WORDS = 3;
@@ -501,7 +501,8 @@ function isSectionLabel(text) {
501
501
  }
502
502
  var HELPER_OPENER_RE = /^(?:a\s+)?helper\s+(?:function|method|component|hook|class|type|util(?:ity)?)\b/i;
503
503
  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;
504
- var ENUMERATION_RE = /^(?:\d+[.)]\s+\S|phase\s+\d+\b)/i;
504
+ var ENUMERATION_RE = /^(?:\d+[.)]\s+\S|(?:phase|step)\s+\d+\b)/i;
505
+ var DUMMY_TRANSLATION_RE = /^(?:increment|return|returns|get|gets|set\b(?! up\b)|sets\b(?! up\b)|function to|method to)\b/i;
505
506
  var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
506
507
  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\.)/;
507
508
  var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
@@ -549,6 +550,14 @@ function isRedundantNarration(body, statementBelow, standalone, isolatedEnumerat
549
550
  if (STEP_NARRATION_RE.test(t)) return true;
550
551
  if (META_COMMENTARY_RE.test(t) && !JUSTIFICATION_RE.test(t)) return true;
551
552
  if (HELPER_OPENER_RE.test(t) || LETS_RE.test(t)) return true;
553
+ const words = t.split(/\s+/);
554
+ if (words.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
555
+ const lowerT = t.toLowerCase();
556
+ const rationaleWords = ["when", "because", "if", "so that", "due to", "for", "instead of", "to prevent", "to avoid", "only"];
557
+ if (!rationaleWords.some((w) => lowerT.includes(w))) {
558
+ if (words.length > 1) return true;
559
+ }
560
+ }
552
561
  if (!nested && isSectionLabel(t)) return true;
553
562
  if (isolatedEnumeration && ENUMERATION_RE.test(t)) return true;
554
563
  }
@@ -616,7 +625,8 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
616
625
  commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
617
626
  sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
618
627
  fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
619
- redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting."
628
+ redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting.",
629
+ untrackedTodo: "Untracked TODO/FIXME marker \u2014 add an issue ticket or context link."
620
630
  }
621
631
  },
622
632
  defaultOptions: [],
@@ -670,6 +680,16 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
670
680
  if (isJsDoc(comment) || !isStandalone(comment)) continue;
671
681
  if (LICENSE_RE.test(comment.value)) continue;
672
682
  const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
683
+ const firstText = texts[0];
684
+ if (firstText !== void 0 && /^(?:todo|fixme)\b/i.test(firstText)) {
685
+ if (!runCitesAReference(comments, i)) {
686
+ context.report({
687
+ node: comment,
688
+ messageId: "untrackedTodo"
689
+ });
690
+ }
691
+ continue;
692
+ }
673
693
  if (texts.some(isBanner)) {
674
694
  context.report({ node: comment, messageId: "sectionBanner" });
675
695
  continue;
@@ -7492,6 +7512,204 @@ var prefer_non_nullable_collection_default = ESLintUtils47.RuleCreator(
7492
7512
  }
7493
7513
  });
7494
7514
 
7515
+ // src/rules/primary-export-file-name.ts
7516
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, ESLintUtils as ESLintUtils48 } from "@typescript-eslint/utils";
7517
+ var CONVENTIONAL_BUCKET_EXPORTS2 = /* @__PURE__ */ new Set(["cn"]);
7518
+ var GENERIC_ENTRYPOINTS = /* @__PURE__ */ new Set(["main", "run", "cli", "setup", "teardown", "execute"]);
7519
+ var ACRONYM_OVERRIDES2 = [
7520
+ [/OAuth/g, "Oauth"],
7521
+ [/GraphQL/g, "Graphql"],
7522
+ [/gRPC/g, "Grpc"]
7523
+ ];
7524
+ var CAMEL_BOUNDARY_RE2 = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
7525
+ var basename2 = (filename) => filename.split(/[/\\]/).pop() ?? filename;
7526
+ var stemOf2 = (base) => base.replace(/\.(test|spec|config|d|stories)\.[cm]?[jt]sx?$/i, "").replace(/\.[cm]?[jt]sx?$/i, "");
7527
+ var kebabCase3 = (name) => {
7528
+ let normalized = name;
7529
+ for (const [pattern, replacement] of ACRONYM_OVERRIDES2) {
7530
+ normalized = normalized.replace(pattern, replacement);
7531
+ }
7532
+ return normalized.replace(CAMEL_BOUNDARY_RE2, "-").toLowerCase();
7533
+ };
7534
+ var isFunctionOrClass = (node) => node.type === AST_NODE_TYPES35.ArrowFunctionExpression || node.type === AST_NODE_TYPES35.FunctionExpression;
7535
+ var findPrimaryExport = (body) => {
7536
+ let exportCount = 0;
7537
+ let primaryCandidate = null;
7538
+ for (const statement of body) {
7539
+ if (statement.type === AST_NODE_TYPES35.ExportAllDeclaration) {
7540
+ return null;
7541
+ }
7542
+ if (statement.type === AST_NODE_TYPES35.ExportDefaultDeclaration) {
7543
+ exportCount += 1;
7544
+ const decl = statement.declaration;
7545
+ if ((decl.type === AST_NODE_TYPES35.FunctionDeclaration || decl.type === AST_NODE_TYPES35.ClassDeclaration) && decl.id !== null) {
7546
+ primaryCandidate = { name: decl.id.name, node: statement, isDefault: true };
7547
+ } else if (decl.type === AST_NODE_TYPES35.Identifier) {
7548
+ primaryCandidate = { name: decl.name, node: statement, isDefault: true };
7549
+ }
7550
+ } else if (statement.type === AST_NODE_TYPES35.ExportNamedDeclaration) {
7551
+ if (statement.source !== null) {
7552
+ return null;
7553
+ }
7554
+ const decl = statement.declaration;
7555
+ if (decl === null) {
7556
+ exportCount += statement.specifiers.length;
7557
+ if (statement.specifiers.length === 1 && primaryCandidate === null) {
7558
+ const spec = statement.specifiers[0];
7559
+ if (spec && spec.exported.type === AST_NODE_TYPES35.Identifier) {
7560
+ primaryCandidate = { name: spec.exported.name, node: statement, isDefault: false };
7561
+ }
7562
+ }
7563
+ } else if (decl.type === AST_NODE_TYPES35.FunctionDeclaration || decl.type === AST_NODE_TYPES35.ClassDeclaration) {
7564
+ if (decl.id !== null) {
7565
+ exportCount += 1;
7566
+ if (primaryCandidate === null) {
7567
+ primaryCandidate = { name: decl.id.name, node: statement, isDefault: false };
7568
+ }
7569
+ }
7570
+ } else if (decl.type === AST_NODE_TYPES35.VariableDeclaration) {
7571
+ for (const declarator of decl.declarations) {
7572
+ if (declarator.id.type === AST_NODE_TYPES35.Identifier) {
7573
+ exportCount += 1;
7574
+ if (primaryCandidate === null && declarator.init && isFunctionOrClass(declarator.init)) {
7575
+ primaryCandidate = { name: declarator.id.name, node: statement, isDefault: false };
7576
+ }
7577
+ }
7578
+ }
7579
+ } else if (decl.type === AST_NODE_TYPES35.TSTypeAliasDeclaration || decl.type === AST_NODE_TYPES35.TSInterfaceDeclaration) {
7580
+ exportCount += 1;
7581
+ if (primaryCandidate === null) {
7582
+ primaryCandidate = { name: decl.id.name, node: statement, isDefault: false };
7583
+ }
7584
+ }
7585
+ }
7586
+ }
7587
+ if (primaryCandidate === null || exportCount > 2) {
7588
+ return null;
7589
+ }
7590
+ return { ...primaryCandidate, count: exportCount };
7591
+ };
7592
+ var primary_export_file_name_default = ESLintUtils48.RuleCreator(
7593
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7594
+ )({
7595
+ name: "primary-export-file-name",
7596
+ meta: {
7597
+ type: "suggestion",
7598
+ docs: {
7599
+ description: "Enforce semantic naming alignment: a file with a single primary export must be named after that export."
7600
+ },
7601
+ schema: [],
7602
+ messages: {
7603
+ primaryExportMismatch: "Module stem '{{stem}}' does not match its primary export '{{name}}' \u2014 rename the file to '{{expected}}{{ext}}' to describe its responsibility."
7604
+ }
7605
+ },
7606
+ defaultOptions: [],
7607
+ create(context) {
7608
+ const base = basename2(context.filename);
7609
+ if (base.endsWith(".d.ts") || base.startsWith("index.") || base.startsWith("_")) return {};
7610
+ if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
7611
+ const stem3 = stemOf2(base);
7612
+ 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("[")) {
7613
+ return {};
7614
+ }
7615
+ return {
7616
+ Program(node) {
7617
+ const primary = findPrimaryExport(node.body);
7618
+ if (primary === null) return;
7619
+ if (CONVENTIONAL_BUCKET_EXPORTS2.has(primary.name) || GENERIC_ENTRYPOINTS.has(primary.name)) return;
7620
+ const expectedStem = kebabCase3(primary.name);
7621
+ if (stem3 === expectedStem) return;
7622
+ const ext = base.endsWith(".tsx") ? ".tsx" : ".ts";
7623
+ context.report({
7624
+ node: primary.node,
7625
+ messageId: "primaryExportMismatch",
7626
+ data: { stem: stem3, name: primary.name, expected: expectedStem, ext }
7627
+ });
7628
+ }
7629
+ };
7630
+ }
7631
+ });
7632
+
7633
+ // src/rules/no-implicit-attribute-access.ts
7634
+ import { AST_NODE_TYPES as AST_NODE_TYPES36, ESLintUtils as ESLintUtils49 } from "@typescript-eslint/utils";
7635
+ var EXCLUDED_BASES = /* @__PURE__ */ new Set([
7636
+ "environ",
7637
+ "headers",
7638
+ "cookies",
7639
+ "session",
7640
+ "redis",
7641
+ "cache",
7642
+ "state",
7643
+ "config",
7644
+ "env",
7645
+ "process",
7646
+ "localStorage",
7647
+ "sessionStorage"
7648
+ ]);
7649
+ function getBaseName(node) {
7650
+ if (node.type === AST_NODE_TYPES36.Identifier) {
7651
+ return node.name;
7652
+ }
7653
+ if (node.type === AST_NODE_TYPES36.MemberExpression && node.property.type === AST_NODE_TYPES36.Identifier) {
7654
+ return node.property.name;
7655
+ }
7656
+ return null;
7657
+ }
7658
+ var no_implicit_attribute_access_default = ESLintUtils49.RuleCreator(
7659
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7660
+ )({
7661
+ name: "no-implicit-attribute-access",
7662
+ meta: {
7663
+ type: "problem",
7664
+ docs: {
7665
+ description: "Disallow imperative dictionary access with string literals; use declarative Zod schemas."
7666
+ },
7667
+ schema: [],
7668
+ messages: {
7669
+ noImplicitAttributeAccess: "Imperative lookup for '{{key}}' \u2014 if the key is known, parse the object declaratively with a Zod schema instead."
7670
+ }
7671
+ },
7672
+ defaultOptions: [],
7673
+ create(context) {
7674
+ return {
7675
+ MemberExpression(node) {
7676
+ if (!node.computed) {
7677
+ return;
7678
+ }
7679
+ if (node.property.type === AST_NODE_TYPES36.Literal && typeof node.property.value === "string") {
7680
+ const baseName = getBaseName(node.object);
7681
+ if (!baseName || !EXCLUDED_BASES.has(baseName)) {
7682
+ context.report({
7683
+ node,
7684
+ messageId: "noImplicitAttributeAccess",
7685
+ data: { key: node.property.value }
7686
+ });
7687
+ }
7688
+ }
7689
+ },
7690
+ CallExpression(node) {
7691
+ const callee = node.callee;
7692
+ if (callee.type === AST_NODE_TYPES36.MemberExpression) {
7693
+ const method = callee.property.type === AST_NODE_TYPES36.Identifier ? callee.property.name : null;
7694
+ if (method === "get") {
7695
+ const arg = node.arguments[0];
7696
+ if (arg && arg.type === AST_NODE_TYPES36.Literal && typeof arg.value === "string") {
7697
+ const baseName = getBaseName(callee.object);
7698
+ if (!baseName || !EXCLUDED_BASES.has(baseName)) {
7699
+ context.report({
7700
+ node,
7701
+ messageId: "noImplicitAttributeAccess",
7702
+ data: { key: arg.value }
7703
+ });
7704
+ }
7705
+ }
7706
+ }
7707
+ }
7708
+ }
7709
+ };
7710
+ }
7711
+ });
7712
+
7495
7713
  // src/index.ts
7496
7714
  var rules = {
7497
7715
  "enforce-file-structure": enforce_file_structure_default,
@@ -7520,6 +7738,7 @@ var rules = {
7520
7738
  "no-unsafe-cast": no_unsafe_cast_default,
7521
7739
  "prefer-string-literal-union": prefer_string_literal_union_default,
7522
7740
  "single-public-export": single_public_export_default,
7741
+ "primary-export-file-name": primary_export_file_name_default,
7523
7742
  "no-silent-promise-catch": no_silent_promise_catch_default,
7524
7743
  "require-fetch-timeout": require_fetch_timeout_default,
7525
7744
  "require-schema-validate-search": require_schema_validate_search_default,
@@ -7540,12 +7759,13 @@ var rules = {
7540
7759
  "trailing-value-narration": trailing_value_narration_default,
7541
7760
  "no-tautological-expect": no_tautological_expect_default,
7542
7761
  "require-interface-for-injected-service": require_interface_for_injected_service_default,
7543
- "prefer-non-nullable-collection": prefer_non_nullable_collection_default
7762
+ "prefer-non-nullable-collection": prefer_non_nullable_collection_default,
7763
+ "no-implicit-attribute-access": no_implicit_attribute_access_default
7544
7764
  };
7545
7765
  var plugin = {
7546
7766
  meta: {
7547
7767
  name: "@sarj/eslint-plugin",
7548
- version: "2.15.0"
7768
+ version: "2.16.0"
7549
7769
  },
7550
7770
  rules,
7551
7771
  configs: {
@@ -7580,6 +7800,7 @@ var plugin = {
7580
7800
  "@sarj/no-secret-in-log": "warn",
7581
7801
  "@sarj/no-unsafe-cast": "warn",
7582
7802
  "@sarj/single-public-export": "warn",
7803
+ "@sarj/primary-export-file-name": "warn",
7583
7804
  "@sarj/prefer-string-literal-union": "warn",
7584
7805
  // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
7585
7806
  "@sarj/require-fetch-timeout": "warn",
@@ -7629,7 +7850,8 @@ var plugin = {
7629
7850
  // mocking. 11-repo sweep: 229 exported classes, 82% already carry a
7630
7851
  // port, 29 fire, 28 of them true positives.
7631
7852
  "@sarj/require-interface-for-injected-service": "warn",
7632
- "@sarj/prefer-non-nullable-collection": "warn"
7853
+ "@sarj/prefer-non-nullable-collection": "warn",
7854
+ "@sarj/no-implicit-attribute-access": "warn"
7633
7855
  }
7634
7856
  },
7635
7857
  strict: {
@@ -7667,6 +7889,7 @@ var plugin = {
7667
7889
  "@sarj/no-secret-in-log": "error",
7668
7890
  "@sarj/no-unsafe-cast": "error",
7669
7891
  "@sarj/single-public-export": "error",
7892
+ "@sarj/primary-export-file-name": "error",
7670
7893
  // Promoted to error 2026-07-25 — strict means strict (user directive).
7671
7894
  "@sarj/prefer-string-literal-union": "error",
7672
7895
  // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
@@ -7707,7 +7930,8 @@ var plugin = {
7707
7930
  // / `prefer-library-fake` wave. The convention already exists in the
7708
7931
  // corpus (175 `implements` clauses vs 29 hits), so strict enforces it.
7709
7932
  "@sarj/require-interface-for-injected-service": "error",
7710
- "@sarj/prefer-non-nullable-collection": "error"
7933
+ "@sarj/prefer-non-nullable-collection": "error",
7934
+ "@sarj/no-implicit-attribute-access": "error"
7711
7935
  }
7712
7936
  }
7713
7937
  }