@sarj/eslint-plugin 2.4.1 → 2.6.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 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);
@@ -3246,8 +3263,54 @@ var no_secret_in_log_default = import_utils25.ESLintUtils.RuleCreator(
3246
3263
  }
3247
3264
  });
3248
3265
 
3249
- // src/rules/prefer-string-literal-union.ts
3266
+ // src/rules/no-unsafe-cast.ts
3250
3267
  var import_utils26 = require("@typescript-eslint/utils");
3268
+ var import_utils27 = require("@typescript-eslint/utils");
3269
+ function isAnyAnnotation(node) {
3270
+ return node.type === import_utils27.AST_NODE_TYPES.TSAnyKeyword;
3271
+ }
3272
+ function isConstAssertion(typeAnnotation) {
3273
+ return typeAnnotation.type === import_utils27.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === import_utils27.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
3274
+ }
3275
+ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3276
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3277
+ )({
3278
+ name: "no-unsafe-cast",
3279
+ meta: {
3280
+ type: "problem",
3281
+ docs: {
3282
+ description: "Disallow `as any`/`<any>` casts and `as unknown as T` double-casts, which fully disable type checking."
3283
+ },
3284
+ schema: [],
3285
+ messages: {
3286
+ asAny: "`as any` disables type checking on this value. Narrow with a type guard, model the shape, or cast to a specific type instead.",
3287
+ doubleCast: "`as unknown as T` double-cast bypasses the compiler's structural check. Prefer a runtime validator (e.g. a zod schema) or an accurate type at the boundary."
3288
+ }
3289
+ },
3290
+ defaultOptions: [],
3291
+ create(context) {
3292
+ function checkAssertion(node) {
3293
+ if (isConstAssertion(node.typeAnnotation)) {
3294
+ return;
3295
+ }
3296
+ if (isAnyAnnotation(node.typeAnnotation)) {
3297
+ context.report({ node, messageId: "asAny" });
3298
+ return;
3299
+ }
3300
+ const inner = node.expression;
3301
+ if (inner.type === import_utils27.AST_NODE_TYPES.TSAsExpression || inner.type === import_utils27.AST_NODE_TYPES.TSTypeAssertion) {
3302
+ context.report({ node, messageId: "doubleCast" });
3303
+ }
3304
+ }
3305
+ return {
3306
+ TSAsExpression: checkAssertion,
3307
+ TSTypeAssertion: checkAssertion
3308
+ };
3309
+ }
3310
+ });
3311
+
3312
+ // src/rules/prefer-string-literal-union.ts
3313
+ var import_utils28 = require("@typescript-eslint/utils");
3251
3314
  var ts = __toESM(require("typescript"), 1);
3252
3315
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
3253
3316
  "status",
@@ -3290,19 +3353,19 @@ function isChoiceLikeName(name) {
3290
3353
  return CHOICE_TOKENS.has(lastWord(name));
3291
3354
  }
3292
3355
  function keyName(key) {
3293
- if (key.type === import_utils26.AST_NODE_TYPES.Identifier) {
3356
+ if (key.type === import_utils28.AST_NODE_TYPES.Identifier) {
3294
3357
  return key.name;
3295
3358
  }
3296
- if (key.type === import_utils26.AST_NODE_TYPES.Literal && typeof key.value === "string") {
3359
+ if (key.type === import_utils28.AST_NODE_TYPES.Literal && typeof key.value === "string") {
3297
3360
  return key.value;
3298
3361
  }
3299
3362
  return null;
3300
3363
  }
3301
3364
  function isStringLiteralMember(t) {
3302
- return t.type === import_utils26.AST_NODE_TYPES.TSLiteralType && t.literal.type === import_utils26.AST_NODE_TYPES.Literal && typeof t.literal.value === "string";
3365
+ return t.type === import_utils28.AST_NODE_TYPES.TSLiteralType && t.literal.type === import_utils28.AST_NODE_TYPES.Literal && typeof t.literal.value === "string";
3303
3366
  }
3304
3367
  function isStringLiteralUnion(node) {
3305
- if (node?.type !== import_utils26.AST_NODE_TYPES.TSUnionType) {
3368
+ if (node?.type !== import_utils28.AST_NODE_TYPES.TSUnionType) {
3306
3369
  return false;
3307
3370
  }
3308
3371
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -3331,12 +3394,12 @@ function bindingSourceExpression(decl) {
3331
3394
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
3332
3395
  }
3333
3396
  function refKey(node) {
3334
- if (node.type === import_utils26.AST_NODE_TYPES.Identifier) {
3397
+ if (node.type === import_utils28.AST_NODE_TYPES.Identifier) {
3335
3398
  return node.name;
3336
3399
  }
3337
- if (node.type === import_utils26.AST_NODE_TYPES.MemberExpression && !node.computed) {
3400
+ if (node.type === import_utils28.AST_NODE_TYPES.MemberExpression && !node.computed) {
3338
3401
  const inner = refKey(node.object);
3339
- if (inner === null || node.property.type !== import_utils26.AST_NODE_TYPES.Identifier) {
3402
+ if (inner === null || node.property.type !== import_utils28.AST_NODE_TYPES.Identifier) {
3340
3403
  return null;
3341
3404
  }
3342
3405
  return `${inner}.${node.property.name}`;
@@ -3344,12 +3407,12 @@ function refKey(node) {
3344
3407
  return null;
3345
3408
  }
3346
3409
  function strLiteral(node) {
3347
- if (node.type === import_utils26.AST_NODE_TYPES.Literal && typeof node.value === "string") {
3410
+ if (node.type === import_utils28.AST_NODE_TYPES.Literal && typeof node.value === "string") {
3348
3411
  return node.value;
3349
3412
  }
3350
3413
  return null;
3351
3414
  }
3352
- var prefer_string_literal_union_default = import_utils26.ESLintUtils.RuleCreator(
3415
+ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator(
3353
3416
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3354
3417
  )({
3355
3418
  name: "prefer-string-literal-union",
@@ -3373,7 +3436,7 @@ var prefer_string_literal_union_default = import_utils26.ESLintUtils.RuleCreator
3373
3436
  }
3374
3437
  let services;
3375
3438
  try {
3376
- services = import_utils26.ESLintUtils.getParserServices(context);
3439
+ services = import_utils28.ESLintUtils.getParserServices(context);
3377
3440
  } catch {
3378
3441
  services = null;
3379
3442
  }
@@ -3457,7 +3520,7 @@ var prefer_string_literal_union_default = import_utils26.ESLintUtils.RuleCreator
3457
3520
  containersWithUnion.add(container);
3458
3521
  return;
3459
3522
  }
3460
- if (typeNode?.type !== import_utils26.AST_NODE_TYPES.TSStringKeyword) {
3523
+ if (typeNode?.type !== import_utils28.AST_NODE_TYPES.TSStringKeyword) {
3461
3524
  return;
3462
3525
  }
3463
3526
  const name = keyName(key);
@@ -3545,10 +3608,10 @@ var prefer_string_literal_union_default = import_utils26.ESLintUtils.RuleCreator
3545
3608
  }
3546
3609
  };
3547
3610
  function refKeyText(node) {
3548
- if (node.type === import_utils26.AST_NODE_TYPES.BinaryExpression) {
3611
+ if (node.type === import_utils28.AST_NODE_TYPES.BinaryExpression) {
3549
3612
  return refKey(node.left) ?? refKey(node.right) ?? "value";
3550
3613
  }
3551
- if (node.type === import_utils26.AST_NODE_TYPES.SwitchStatement) {
3614
+ if (node.type === import_utils28.AST_NODE_TYPES.SwitchStatement) {
3552
3615
  return refKey(node.discriminant) ?? "value";
3553
3616
  }
3554
3617
  return "value";
@@ -3557,7 +3620,7 @@ var prefer_string_literal_union_default = import_utils26.ESLintUtils.RuleCreator
3557
3620
  });
3558
3621
 
3559
3622
  // src/rules/single-public-export.ts
3560
- var import_utils27 = require("@typescript-eslint/utils");
3623
+ var import_utils29 = require("@typescript-eslint/utils");
3561
3624
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
3562
3625
  "util",
3563
3626
  "utils",
@@ -3591,12 +3654,12 @@ var kebabCase2 = (name) => {
3591
3654
  }
3592
3655
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
3593
3656
  };
3594
- var isFunctionExpression = (node) => node !== null && (node.type === import_utils27.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils27.AST_NODE_TYPES.FunctionExpression);
3657
+ var isFunctionExpression = (node) => node !== null && (node.type === import_utils29.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils29.AST_NODE_TYPES.FunctionExpression);
3595
3658
  var functionConstName = (decl) => {
3596
3659
  if (decl.declarations.length !== 1) return null;
3597
3660
  const [declarator] = decl.declarations;
3598
3661
  if (declarator === void 0) return null;
3599
- if (declarator.id.type !== import_utils27.AST_NODE_TYPES.Identifier) return null;
3662
+ if (declarator.id.type !== import_utils29.AST_NODE_TYPES.Identifier) return null;
3600
3663
  if (!isFunctionExpression(declarator.init)) return null;
3601
3664
  return declarator.id.name;
3602
3665
  };
@@ -3610,20 +3673,20 @@ var summarizeExports = (body) => {
3610
3673
  };
3611
3674
  for (const statement of body) {
3612
3675
  switch (statement.type) {
3613
- case import_utils27.AST_NODE_TYPES.ExportAllDeclaration:
3676
+ case import_utils29.AST_NODE_TYPES.ExportAllDeclaration:
3614
3677
  hasReExport = true;
3615
3678
  break;
3616
- case import_utils27.AST_NODE_TYPES.ExportDefaultDeclaration: {
3679
+ case import_utils29.AST_NODE_TYPES.ExportDefaultDeclaration: {
3617
3680
  names += 1;
3618
3681
  const decl = statement.declaration;
3619
- if (decl.type === import_utils27.AST_NODE_TYPES.FunctionDeclaration && decl.id !== null) {
3682
+ if (decl.type === import_utils29.AST_NODE_TYPES.FunctionDeclaration && decl.id !== null) {
3620
3683
  candidate = { name: decl.id.name, node: statement };
3621
- } else if (decl.type === import_utils27.AST_NODE_TYPES.ClassDeclaration && decl.id !== null) {
3684
+ } else if (decl.type === import_utils29.AST_NODE_TYPES.ClassDeclaration && decl.id !== null) {
3622
3685
  candidate = { name: decl.id.name, node: statement };
3623
3686
  }
3624
3687
  break;
3625
3688
  }
3626
- case import_utils27.AST_NODE_TYPES.ExportNamedDeclaration: {
3689
+ case import_utils29.AST_NODE_TYPES.ExportNamedDeclaration: {
3627
3690
  if (statement.source !== null) {
3628
3691
  hasReExport = true;
3629
3692
  break;
@@ -3634,15 +3697,15 @@ var summarizeExports = (body) => {
3634
3697
  break;
3635
3698
  }
3636
3699
  switch (decl.type) {
3637
- case import_utils27.AST_NODE_TYPES.FunctionDeclaration:
3700
+ case import_utils29.AST_NODE_TYPES.FunctionDeclaration:
3638
3701
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3639
3702
  else names += 1;
3640
3703
  break;
3641
- case import_utils27.AST_NODE_TYPES.ClassDeclaration:
3704
+ case import_utils29.AST_NODE_TYPES.ClassDeclaration:
3642
3705
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3643
3706
  else names += 1;
3644
3707
  break;
3645
- case import_utils27.AST_NODE_TYPES.VariableDeclaration: {
3708
+ case import_utils29.AST_NODE_TYPES.VariableDeclaration: {
3646
3709
  const fnName = functionConstName(decl);
3647
3710
  if (fnName !== null && decl.declarations.length === 1) {
3648
3711
  addCandidate(fnName, statement);
@@ -3662,7 +3725,7 @@ var summarizeExports = (body) => {
3662
3725
  }
3663
3726
  return { names, hasReExport, candidate };
3664
3727
  };
3665
- var single_public_export_default = import_utils27.ESLintUtils.RuleCreator(
3728
+ var single_public_export_default = import_utils29.ESLintUtils.RuleCreator(
3666
3729
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3667
3730
  )({
3668
3731
  name: "single-public-export",
@@ -3726,13 +3789,14 @@ var rules = {
3726
3789
  "no-cors-wildcard-with-credentials": no_cors_wildcard_with_credentials_default,
3727
3790
  "no-fat-try-blocks": no_fat_try_blocks_default,
3728
3791
  "no-secret-in-log": no_secret_in_log_default,
3792
+ "no-unsafe-cast": no_unsafe_cast_default,
3729
3793
  "prefer-string-literal-union": prefer_string_literal_union_default,
3730
3794
  "single-public-export": single_public_export_default
3731
3795
  };
3732
3796
  var plugin = {
3733
3797
  meta: {
3734
3798
  name: "@sarj/eslint-plugin",
3735
- version: "2.4.1"
3799
+ version: "2.6.0"
3736
3800
  },
3737
3801
  rules,
3738
3802
  configs: {
@@ -3762,6 +3826,7 @@ var plugin = {
3762
3826
  "@sarj/no-fat-try-blocks": "warn",
3763
3827
  "@sarj/no-cors-wildcard-with-credentials": "warn",
3764
3828
  "@sarj/no-secret-in-log": "warn",
3829
+ "@sarj/no-unsafe-cast": "warn",
3765
3830
  "@sarj/single-public-export": "warn",
3766
3831
  "@sarj/prefer-string-literal-union": "warn"
3767
3832
  }
@@ -3796,6 +3861,7 @@ var plugin = {
3796
3861
  "@sarj/no-fat-try-blocks": "error",
3797
3862
  "@sarj/no-cors-wildcard-with-credentials": "error",
3798
3863
  "@sarj/no-secret-in-log": "error",
3864
+ "@sarj/no-unsafe-cast": "warn",
3799
3865
  "@sarj/single-public-export": "error",
3800
3866
  // High-volume/stylistic — warn until rollout proves FP rate.
3801
3867
  "@sarj/prefer-string-literal-union": "warn"