@sarj/eslint-plugin 3.0.0 → 4.1.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
@@ -280,9 +280,6 @@ declare const rules: {
280
280
  "prefer-zod-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"preferEnum", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
281
281
  name: string;
282
282
  };
283
- "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
284
- name: string;
285
- };
286
283
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
287
284
  name: string;
288
285
  };
@@ -306,6 +303,12 @@ declare const rules: {
306
303
  "no-sleep-in-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"noSleepInTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
307
304
  name: string;
308
305
  };
306
+ "no-hand-rolled-sleep": _typescript_eslint_utils_ts_eslint.RuleModule<"handRolledSleep" | "handRolledTimeoutRace", readonly [{
307
+ allowIn?: readonly string[];
308
+ checkClientModules?: boolean;
309
+ }?], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
310
+ name: string;
311
+ };
309
312
  "no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
310
313
  name: string;
311
314
  };
@@ -452,9 +455,6 @@ declare const plugin: {
452
455
  "prefer-zod-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"preferEnum", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
453
456
  name: string;
454
457
  };
455
- "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
456
- name: string;
457
- };
458
458
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
459
459
  name: string;
460
460
  };
@@ -478,6 +478,12 @@ declare const plugin: {
478
478
  "no-sleep-in-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"noSleepInTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
479
479
  name: string;
480
480
  };
481
+ "no-hand-rolled-sleep": _typescript_eslint_utils_ts_eslint.RuleModule<"handRolledSleep" | "handRolledTimeoutRace", readonly [{
482
+ allowIn?: readonly string[];
483
+ checkClientModules?: boolean;
484
+ }?], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
485
+ name: string;
486
+ };
481
487
  "no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
482
488
  name: string;
483
489
  };
@@ -562,7 +568,6 @@ declare const plugin: {
562
568
  "@sarj/no-cors-wildcard-with-credentials": string;
563
569
  "@sarj/no-secret-in-log": string;
564
570
  "@sarj/no-unsafe-mock-casting": string;
565
- "@sarj/primary-export-file-name": string;
566
571
  "@sarj/prefer-string-literal-union": string;
567
572
  "@sarj/prefer-zod-enum": string;
568
573
  "@sarj/require-fetch-timeout": string;
@@ -571,6 +576,7 @@ declare const plugin: {
571
576
  "@sarj/store-insert-requires-on-conflict": string;
572
577
  "@sarj/no-offset-pagination": string;
573
578
  "@sarj/no-select-star": string;
579
+ "@sarj/no-hand-rolled-sleep": string;
574
580
  "@sarj/no-sleep-in-test-body": string;
575
581
  "@sarj/no-conditional-in-test": string;
576
582
  "@sarj/no-repeated-string-literal": string;
@@ -617,7 +623,6 @@ declare const plugin: {
617
623
  "@sarj/no-cors-wildcard-with-credentials": string;
618
624
  "@sarj/no-secret-in-log": string;
619
625
  "@sarj/no-unsafe-mock-casting": string;
620
- "@sarj/primary-export-file-name": string;
621
626
  "@sarj/prefer-string-literal-union": string;
622
627
  "@sarj/prefer-zod-enum": string;
623
628
  "@sarj/require-fetch-timeout": string;
@@ -626,6 +631,7 @@ declare const plugin: {
626
631
  "@sarj/store-insert-requires-on-conflict": string;
627
632
  "@sarj/no-offset-pagination": string;
628
633
  "@sarj/no-select-star": string;
634
+ "@sarj/no-hand-rolled-sleep": string;
629
635
  "@sarj/no-sleep-in-test-body": string;
630
636
  "@sarj/no-conditional-in-test": string;
631
637
  "@sarj/no-repeated-string-literal": string;
package/dist/index.d.ts CHANGED
@@ -280,9 +280,6 @@ declare const rules: {
280
280
  "prefer-zod-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"preferEnum", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
281
281
  name: string;
282
282
  };
283
- "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
284
- name: string;
285
- };
286
283
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
287
284
  name: string;
288
285
  };
@@ -306,6 +303,12 @@ declare const rules: {
306
303
  "no-sleep-in-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"noSleepInTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
307
304
  name: string;
308
305
  };
306
+ "no-hand-rolled-sleep": _typescript_eslint_utils_ts_eslint.RuleModule<"handRolledSleep" | "handRolledTimeoutRace", readonly [{
307
+ allowIn?: readonly string[];
308
+ checkClientModules?: boolean;
309
+ }?], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
310
+ name: string;
311
+ };
309
312
  "no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
310
313
  name: string;
311
314
  };
@@ -452,9 +455,6 @@ declare const plugin: {
452
455
  "prefer-zod-enum": _typescript_eslint_utils_ts_eslint.RuleModule<"preferEnum", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
453
456
  name: string;
454
457
  };
455
- "primary-export-file-name": _typescript_eslint_utils_ts_eslint.RuleModule<"primaryExportMismatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
456
- name: string;
457
- };
458
458
  "no-silent-promise-catch": _typescript_eslint_utils_ts_eslint.RuleModule<"silentCatch", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
459
459
  name: string;
460
460
  };
@@ -478,6 +478,12 @@ declare const plugin: {
478
478
  "no-sleep-in-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"noSleepInTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
479
479
  name: string;
480
480
  };
481
+ "no-hand-rolled-sleep": _typescript_eslint_utils_ts_eslint.RuleModule<"handRolledSleep" | "handRolledTimeoutRace", readonly [{
482
+ allowIn?: readonly string[];
483
+ checkClientModules?: boolean;
484
+ }?], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
485
+ name: string;
486
+ };
481
487
  "no-conditional-in-test": _typescript_eslint_utils_ts_eslint.RuleModule<"noConditionalInTest", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
482
488
  name: string;
483
489
  };
@@ -562,7 +568,6 @@ declare const plugin: {
562
568
  "@sarj/no-cors-wildcard-with-credentials": string;
563
569
  "@sarj/no-secret-in-log": string;
564
570
  "@sarj/no-unsafe-mock-casting": string;
565
- "@sarj/primary-export-file-name": string;
566
571
  "@sarj/prefer-string-literal-union": string;
567
572
  "@sarj/prefer-zod-enum": string;
568
573
  "@sarj/require-fetch-timeout": string;
@@ -571,6 +576,7 @@ declare const plugin: {
571
576
  "@sarj/store-insert-requires-on-conflict": string;
572
577
  "@sarj/no-offset-pagination": string;
573
578
  "@sarj/no-select-star": string;
579
+ "@sarj/no-hand-rolled-sleep": string;
574
580
  "@sarj/no-sleep-in-test-body": string;
575
581
  "@sarj/no-conditional-in-test": string;
576
582
  "@sarj/no-repeated-string-literal": string;
@@ -617,7 +623,6 @@ declare const plugin: {
617
623
  "@sarj/no-cors-wildcard-with-credentials": string;
618
624
  "@sarj/no-secret-in-log": string;
619
625
  "@sarj/no-unsafe-mock-casting": string;
620
- "@sarj/primary-export-file-name": string;
621
626
  "@sarj/prefer-string-literal-union": string;
622
627
  "@sarj/prefer-zod-enum": string;
623
628
  "@sarj/require-fetch-timeout": string;
@@ -626,6 +631,7 @@ declare const plugin: {
626
631
  "@sarj/store-insert-requires-on-conflict": string;
627
632
  "@sarj/no-offset-pagination": string;
628
633
  "@sarj/no-select-star": string;
634
+ "@sarj/no-hand-rolled-sleep": string;
629
635
  "@sarj/no-sleep-in-test-body": string;
630
636
  "@sarj/no-conditional-in-test": string;
631
637
  "@sarj/no-repeated-string-literal": string;
package/dist/index.js CHANGED
@@ -7174,126 +7174,8 @@ var prefer_non_nullable_collection_default = ESLintUtils46.RuleCreator(
7174
7174
  }
7175
7175
  });
7176
7176
 
7177
- // src/rules/primary-export-file-name.ts
7178
- import { AST_NODE_TYPES as AST_NODE_TYPES34, ESLintUtils as ESLintUtils47 } from "@typescript-eslint/utils";
7179
- var CONVENTIONAL_BUCKET_EXPORTS = /* @__PURE__ */ new Set(["cn"]);
7180
- var GENERIC_ENTRYPOINTS = /* @__PURE__ */ new Set(["main", "run", "cli", "setup", "teardown", "execute"]);
7181
- var ACRONYM_OVERRIDES = [
7182
- [/OAuth/g, "Oauth"],
7183
- [/GraphQL/g, "Graphql"],
7184
- [/gRPC/g, "Grpc"]
7185
- ];
7186
- var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
7187
- var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
7188
- var stemOf = (base) => base.replace(/\.(test|spec|config|d|stories)\.[cm]?[jt]sx?$/i, "").replace(/\.[cm]?[jt]sx?$/i, "");
7189
- var kebabCase = (name) => {
7190
- let normalized = name;
7191
- for (const [pattern, replacement] of ACRONYM_OVERRIDES) {
7192
- normalized = normalized.replace(pattern, replacement);
7193
- }
7194
- return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
7195
- };
7196
- var isFunctionOrClass = (node) => node.type === AST_NODE_TYPES34.ArrowFunctionExpression || node.type === AST_NODE_TYPES34.FunctionExpression;
7197
- var findPrimaryExport = (body) => {
7198
- let exportCount = 0;
7199
- let primaryCandidate = null;
7200
- for (const statement of body) {
7201
- if (statement.type === AST_NODE_TYPES34.ExportAllDeclaration) {
7202
- return null;
7203
- }
7204
- if (statement.type === AST_NODE_TYPES34.ExportDefaultDeclaration) {
7205
- exportCount += 1;
7206
- const decl = statement.declaration;
7207
- if ((decl.type === AST_NODE_TYPES34.FunctionDeclaration || decl.type === AST_NODE_TYPES34.ClassDeclaration) && decl.id !== null) {
7208
- primaryCandidate = { name: decl.id.name, node: statement, isDefault: true };
7209
- } else if (decl.type === AST_NODE_TYPES34.Identifier) {
7210
- primaryCandidate = { name: decl.name, node: statement, isDefault: true };
7211
- }
7212
- } else if (statement.type === AST_NODE_TYPES34.ExportNamedDeclaration) {
7213
- if (statement.source !== null) {
7214
- return null;
7215
- }
7216
- const decl = statement.declaration;
7217
- if (decl === null) {
7218
- exportCount += statement.specifiers.length;
7219
- if (statement.specifiers.length === 1 && primaryCandidate === null) {
7220
- const spec = statement.specifiers[0];
7221
- if (spec && spec.exported.type === AST_NODE_TYPES34.Identifier) {
7222
- primaryCandidate = { name: spec.exported.name, node: statement, isDefault: false };
7223
- }
7224
- }
7225
- } else if (decl.type === AST_NODE_TYPES34.FunctionDeclaration || decl.type === AST_NODE_TYPES34.ClassDeclaration) {
7226
- if (decl.id !== null) {
7227
- exportCount += 1;
7228
- if (primaryCandidate === null) {
7229
- primaryCandidate = { name: decl.id.name, node: statement, isDefault: false };
7230
- }
7231
- }
7232
- } else if (decl.type === AST_NODE_TYPES34.VariableDeclaration) {
7233
- for (const declarator of decl.declarations) {
7234
- if (declarator.id.type === AST_NODE_TYPES34.Identifier) {
7235
- exportCount += 1;
7236
- if (primaryCandidate === null && declarator.init && isFunctionOrClass(declarator.init)) {
7237
- primaryCandidate = { name: declarator.id.name, node: statement, isDefault: false };
7238
- }
7239
- }
7240
- }
7241
- } else if (decl.type === AST_NODE_TYPES34.TSTypeAliasDeclaration || decl.type === AST_NODE_TYPES34.TSInterfaceDeclaration) {
7242
- exportCount += 1;
7243
- if (primaryCandidate === null) {
7244
- primaryCandidate = { name: decl.id.name, node: statement, isDefault: false };
7245
- }
7246
- }
7247
- }
7248
- }
7249
- if (primaryCandidate === null || exportCount > 2) {
7250
- return null;
7251
- }
7252
- return { ...primaryCandidate, count: exportCount };
7253
- };
7254
- var primary_export_file_name_default = ESLintUtils47.RuleCreator(
7255
- (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7256
- )({
7257
- name: "primary-export-file-name",
7258
- meta: {
7259
- type: "suggestion",
7260
- docs: {
7261
- description: "Enforce semantic naming alignment: a file with a single primary export must be named after that export."
7262
- },
7263
- schema: [],
7264
- messages: {
7265
- primaryExportMismatch: "Module stem '{{stem}}' does not match its primary export '{{name}}' \u2014 rename the file to '{{expected}}{{ext}}' to describe its responsibility."
7266
- }
7267
- },
7268
- defaultOptions: [],
7269
- create(context) {
7270
- const base = basename(context.filename);
7271
- if (base.endsWith(".d.ts") || base.startsWith("index.") || base.startsWith("_")) return {};
7272
- if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
7273
- const stem3 = stemOf(base);
7274
- 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("[")) {
7275
- return {};
7276
- }
7277
- return {
7278
- Program(node) {
7279
- const primary = findPrimaryExport(node.body);
7280
- if (primary === null) return;
7281
- if (CONVENTIONAL_BUCKET_EXPORTS.has(primary.name) || GENERIC_ENTRYPOINTS.has(primary.name)) return;
7282
- const expectedStem = kebabCase(primary.name);
7283
- if (stem3 === expectedStem) return;
7284
- const ext = base.endsWith(".tsx") ? ".tsx" : ".ts";
7285
- context.report({
7286
- node: primary.node,
7287
- messageId: "primaryExportMismatch",
7288
- data: { stem: stem3, name: primary.name, expected: expectedStem, ext }
7289
- });
7290
- }
7291
- };
7292
- }
7293
- });
7294
-
7295
7177
  // src/rules/no-implicit-attribute-access.ts
7296
- import { AST_NODE_TYPES as AST_NODE_TYPES35, ESLintUtils as ESLintUtils48 } from "@typescript-eslint/utils";
7178
+ import { AST_NODE_TYPES as AST_NODE_TYPES34, ESLintUtils as ESLintUtils47 } from "@typescript-eslint/utils";
7297
7179
  var EXCLUDED_BASES = /* @__PURE__ */ new Set([
7298
7180
  "environ",
7299
7181
  "headers",
@@ -7309,15 +7191,15 @@ var EXCLUDED_BASES = /* @__PURE__ */ new Set([
7309
7191
  "sessionStorage"
7310
7192
  ]);
7311
7193
  function getBaseName(node) {
7312
- if (node.type === AST_NODE_TYPES35.Identifier) {
7194
+ if (node.type === AST_NODE_TYPES34.Identifier) {
7313
7195
  return node.name;
7314
7196
  }
7315
- if (node.type === AST_NODE_TYPES35.MemberExpression && node.property.type === AST_NODE_TYPES35.Identifier) {
7197
+ if (node.type === AST_NODE_TYPES34.MemberExpression && node.property.type === AST_NODE_TYPES34.Identifier) {
7316
7198
  return node.property.name;
7317
7199
  }
7318
7200
  return null;
7319
7201
  }
7320
- var no_implicit_attribute_access_default = ESLintUtils48.RuleCreator(
7202
+ var no_implicit_attribute_access_default = ESLintUtils47.RuleCreator(
7321
7203
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7322
7204
  )({
7323
7205
  name: "no-implicit-attribute-access",
@@ -7338,7 +7220,7 @@ var no_implicit_attribute_access_default = ESLintUtils48.RuleCreator(
7338
7220
  if (!node.computed) {
7339
7221
  return;
7340
7222
  }
7341
- if (node.property.type === AST_NODE_TYPES35.Literal && typeof node.property.value === "string") {
7223
+ if (node.property.type === AST_NODE_TYPES34.Literal && typeof node.property.value === "string") {
7342
7224
  const baseName = getBaseName(node.object);
7343
7225
  if (!baseName || !EXCLUDED_BASES.has(baseName)) {
7344
7226
  context.report({
@@ -7351,11 +7233,11 @@ var no_implicit_attribute_access_default = ESLintUtils48.RuleCreator(
7351
7233
  },
7352
7234
  CallExpression(node) {
7353
7235
  const callee = node.callee;
7354
- if (callee.type === AST_NODE_TYPES35.MemberExpression) {
7355
- const method = callee.property.type === AST_NODE_TYPES35.Identifier ? callee.property.name : null;
7236
+ if (callee.type === AST_NODE_TYPES34.MemberExpression) {
7237
+ const method = callee.property.type === AST_NODE_TYPES34.Identifier ? callee.property.name : null;
7356
7238
  if (method === "get") {
7357
7239
  const arg = node.arguments[0];
7358
- if (arg && arg.type === AST_NODE_TYPES35.Literal && typeof arg.value === "string") {
7240
+ if (arg && arg.type === AST_NODE_TYPES34.Literal && typeof arg.value === "string") {
7359
7241
  const baseName = getBaseName(callee.object);
7360
7242
  if (!baseName || !EXCLUDED_BASES.has(baseName)) {
7361
7243
  context.report({
@@ -7373,8 +7255,8 @@ var no_implicit_attribute_access_default = ESLintUtils48.RuleCreator(
7373
7255
  });
7374
7256
 
7375
7257
  // src/rules/strict-test-assertions.ts
7376
- import { AST_NODE_TYPES as AST_NODE_TYPES36, ESLintUtils as ESLintUtils49 } from "@typescript-eslint/utils";
7377
- var strict_test_assertions_default = ESLintUtils49.RuleCreator.withoutDocs({
7258
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, ESLintUtils as ESLintUtils48 } from "@typescript-eslint/utils";
7259
+ var strict_test_assertions_default = ESLintUtils48.RuleCreator.withoutDocs({
7378
7260
  meta: {
7379
7261
  type: "suggestion",
7380
7262
  docs: {
@@ -7392,9 +7274,9 @@ var strict_test_assertions_default = ESLintUtils49.RuleCreator.withoutDocs({
7392
7274
  let currentObject = null;
7393
7275
  let sequence = [];
7394
7276
  function getExpectObject(expr) {
7395
- if (expr.type === AST_NODE_TYPES36.CallExpression && expr.callee.type === AST_NODE_TYPES36.MemberExpression && expr.callee.object.type === AST_NODE_TYPES36.CallExpression && expr.callee.object.callee.type === AST_NODE_TYPES36.Identifier && expr.callee.object.callee.name === "expect" && expr.callee.object.arguments.length === 1) {
7277
+ if (expr.type === AST_NODE_TYPES35.CallExpression && expr.callee.type === AST_NODE_TYPES35.MemberExpression && expr.callee.object.type === AST_NODE_TYPES35.CallExpression && expr.callee.object.callee.type === AST_NODE_TYPES35.Identifier && expr.callee.object.callee.name === "expect" && expr.callee.object.arguments.length === 1) {
7396
7278
  const arg = expr.callee.object.arguments.at(0);
7397
- if (arg?.type === AST_NODE_TYPES36.MemberExpression) {
7279
+ if (arg?.type === AST_NODE_TYPES35.MemberExpression) {
7398
7280
  return arg.object;
7399
7281
  }
7400
7282
  }
@@ -7410,12 +7292,12 @@ var strict_test_assertions_default = ESLintUtils49.RuleCreator.withoutDocs({
7410
7292
  if (!currentObject) return null;
7411
7293
  const objectText = context.sourceCode.getText(currentObject);
7412
7294
  const props = sequence.map((stmt) => {
7413
- if (stmt.expression.type !== AST_NODE_TYPES36.CallExpression || stmt.expression.callee.type !== AST_NODE_TYPES36.MemberExpression || stmt.expression.callee.object.type !== AST_NODE_TYPES36.CallExpression) {
7295
+ if (stmt.expression.type !== AST_NODE_TYPES35.CallExpression || stmt.expression.callee.type !== AST_NODE_TYPES35.MemberExpression || stmt.expression.callee.object.type !== AST_NODE_TYPES35.CallExpression) {
7414
7296
  return null;
7415
7297
  }
7416
7298
  const actual = stmt.expression.callee.object.arguments.at(0);
7417
7299
  const expected = stmt.expression.arguments.at(0);
7418
- if (actual?.type === AST_NODE_TYPES36.MemberExpression && actual.property.type === AST_NODE_TYPES36.Identifier && expected) {
7300
+ if (actual?.type === AST_NODE_TYPES35.MemberExpression && actual.property.type === AST_NODE_TYPES35.Identifier && expected) {
7419
7301
  const propName2 = actual.property.name;
7420
7302
  const valText = context.sourceCode.getText(expected);
7421
7303
  return `${propName2}: ${valText}`;
@@ -7433,7 +7315,7 @@ var strict_test_assertions_default = ESLintUtils49.RuleCreator.withoutDocs({
7433
7315
  }
7434
7316
  }
7435
7317
  for (const statement of body) {
7436
- if (statement.type === AST_NODE_TYPES36.ExpressionStatement) {
7318
+ if (statement.type === AST_NODE_TYPES35.ExpressionStatement) {
7437
7319
  const obj = getExpectObject(statement.expression);
7438
7320
  if (obj && currentObject && context.sourceCode.getText(obj) === context.sourceCode.getText(currentObject)) {
7439
7321
  sequence.push(statement);
@@ -7467,8 +7349,8 @@ var strict_test_assertions_default = ESLintUtils49.RuleCreator.withoutDocs({
7467
7349
  });
7468
7350
 
7469
7351
  // src/rules/no-async-callback-in-waitfor.ts
7470
- import { AST_NODE_TYPES as AST_NODE_TYPES37, ESLintUtils as ESLintUtils50 } from "@typescript-eslint/utils";
7471
- var no_async_callback_in_waitfor_default = ESLintUtils50.RuleCreator(
7352
+ import { AST_NODE_TYPES as AST_NODE_TYPES36, ESLintUtils as ESLintUtils49 } from "@typescript-eslint/utils";
7353
+ var no_async_callback_in_waitfor_default = ESLintUtils49.RuleCreator(
7472
7354
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7473
7355
  )({
7474
7356
  name: "no-async-callback-in-waitfor",
@@ -7489,9 +7371,9 @@ var no_async_callback_in_waitfor_default = ESLintUtils50.RuleCreator(
7489
7371
  }
7490
7372
  return {
7491
7373
  CallExpression(node) {
7492
- if (node.callee.type === AST_NODE_TYPES37.Identifier && node.callee.name === "waitFor") {
7374
+ if (node.callee.type === AST_NODE_TYPES36.Identifier && node.callee.name === "waitFor") {
7493
7375
  const callback = node.arguments[0];
7494
- if (callback && (callback.type === AST_NODE_TYPES37.ArrowFunctionExpression || callback.type === AST_NODE_TYPES37.FunctionExpression) && callback.async) {
7376
+ if (callback && (callback.type === AST_NODE_TYPES36.ArrowFunctionExpression || callback.type === AST_NODE_TYPES36.FunctionExpression) && callback.async) {
7495
7377
  context.report({
7496
7378
  node: callback,
7497
7379
  messageId: "noAsyncCallbackInWaitFor"
@@ -7503,6 +7385,183 @@ var no_async_callback_in_waitfor_default = ESLintUtils50.RuleCreator(
7503
7385
  }
7504
7386
  });
7505
7387
 
7388
+ // src/rules/no-hand-rolled-sleep.ts
7389
+ import { AST_NODE_TYPES as AST_NODE_TYPES37, ESLintUtils as ESLintUtils50 } from "@typescript-eslint/utils";
7390
+ var GLOBAL_OBJECTS2 = /* @__PURE__ */ new Set([
7391
+ "globalThis",
7392
+ "window",
7393
+ "self",
7394
+ "global"
7395
+ ]);
7396
+ var CLIENT_ONLY_MODULES = /^(react|react-dom|react-native|svelte|vue|preact|solid-js)(\/|$)|^next\/(navigation|router|link|image)$/;
7397
+ var RACE_METHODS = /* @__PURE__ */ new Set(["race", "any"]);
7398
+ function matchesAnyPattern3(filename, patterns) {
7399
+ for (const pattern of patterns) {
7400
+ const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
7401
+ if (new RegExp(`^${regexSource}$`).test(filename)) {
7402
+ return true;
7403
+ }
7404
+ }
7405
+ return false;
7406
+ }
7407
+ function isSetTimeoutCallee(callee) {
7408
+ if (callee.type === AST_NODE_TYPES37.Identifier) {
7409
+ return callee.name === "setTimeout";
7410
+ }
7411
+ return callee.type === AST_NODE_TYPES37.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES37.Identifier && callee.property.name === "setTimeout" && callee.object.type === AST_NODE_TYPES37.Identifier && GLOBAL_OBJECTS2.has(callee.object.name);
7412
+ }
7413
+ function soleCall(fn) {
7414
+ if (fn.body.type !== AST_NODE_TYPES37.BlockStatement) {
7415
+ return fn.body.type === AST_NODE_TYPES37.CallExpression ? fn.body : null;
7416
+ }
7417
+ if (fn.body.body.length !== 1) {
7418
+ return null;
7419
+ }
7420
+ const [only] = fn.body.body;
7421
+ if (only?.type !== AST_NODE_TYPES37.ExpressionStatement) {
7422
+ return null;
7423
+ }
7424
+ return only.expression.type === AST_NODE_TYPES37.CallExpression ? only.expression : null;
7425
+ }
7426
+ function isTimedDelay(delay) {
7427
+ if (delay === void 0) {
7428
+ return false;
7429
+ }
7430
+ if (delay.type === AST_NODE_TYPES37.Literal && typeof delay.value === "number") {
7431
+ return delay.value !== 0;
7432
+ }
7433
+ return true;
7434
+ }
7435
+ function settlesWithoutValue(callback, name) {
7436
+ if (callback.type === AST_NODE_TYPES37.Identifier) {
7437
+ return callback.name === name;
7438
+ }
7439
+ if (callback.type !== AST_NODE_TYPES37.ArrowFunctionExpression && callback.type !== AST_NODE_TYPES37.FunctionExpression) {
7440
+ return false;
7441
+ }
7442
+ const call = soleCall(callback);
7443
+ return call !== null && call.arguments.length === 0 && call.callee.type === AST_NODE_TYPES37.Identifier && call.callee.name === name;
7444
+ }
7445
+ function rejectsInCallback(callback, name) {
7446
+ if (callback.type === AST_NODE_TYPES37.Identifier) {
7447
+ return callback.name === name;
7448
+ }
7449
+ if (callback.type !== AST_NODE_TYPES37.ArrowFunctionExpression && callback.type !== AST_NODE_TYPES37.FunctionExpression) {
7450
+ return false;
7451
+ }
7452
+ const call = soleCall(callback);
7453
+ return call !== null && call.callee.type === AST_NODE_TYPES37.Identifier && call.callee.name === name;
7454
+ }
7455
+ function parameterName(fn, index) {
7456
+ const parameter = fn.params[index];
7457
+ return parameter?.type === AST_NODE_TYPES37.Identifier ? parameter.name : null;
7458
+ }
7459
+ function isRaceArm(node) {
7460
+ const array = node.parent;
7461
+ if (array?.type !== AST_NODE_TYPES37.ArrayExpression) {
7462
+ return false;
7463
+ }
7464
+ const call = array.parent;
7465
+ return call?.type === AST_NODE_TYPES37.CallExpression && call.arguments[0] === array && call.callee.type === AST_NODE_TYPES37.MemberExpression && !call.callee.computed && call.callee.object.type === AST_NODE_TYPES37.Identifier && call.callee.object.name === "Promise" && call.callee.property.type === AST_NODE_TYPES37.Identifier && RACE_METHODS.has(call.callee.property.name);
7466
+ }
7467
+ var no_hand_rolled_sleep_default = ESLintUtils50.RuleCreator(
7468
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
7469
+ )({
7470
+ name: "no-hand-rolled-sleep",
7471
+ meta: {
7472
+ type: "problem",
7473
+ docs: {
7474
+ description: "Disallow hand-rolled promisified timers (`new Promise((r) => setTimeout(r, ms))`) and hand-rolled `Promise.race` timeout arms; the stdlib forms are cancellable, these are not."
7475
+ },
7476
+ schema: [
7477
+ {
7478
+ type: "object",
7479
+ additionalProperties: false,
7480
+ properties: {
7481
+ allowIn: {
7482
+ description: "Glob patterns for modules exempt from the rule (e.g. a single sanctioned `sleep` utility). Matched against the ABSOLUTE file path, so anchor with a `**/` prefix (e.g. `**/lib/sleep.ts`).",
7483
+ type: "array",
7484
+ items: { type: "string" }
7485
+ },
7486
+ checkClientModules: {
7487
+ description: "Also report the sleep form in browser/React Native modules. Off by default: those bundles cannot import `node:timers/promises` and the web platform has no equivalent, so the fix is impossible to follow. Turn on only where every file can resolve `node:` builtins.",
7488
+ type: "boolean"
7489
+ }
7490
+ }
7491
+ }
7492
+ ],
7493
+ messages: {
7494
+ handRolledSleep: 'Hand-rolled sleep: `new Promise((resolve) => setTimeout(resolve, ms))` cannot be cancelled, so an aborted request or a lost race still waits out the full delay. Use `import { setTimeout as sleep } from "node:timers/promises"` and pass `{ signal }`.',
7495
+ handRolledTimeoutRace: "Hand-rolled timeout arm: when the other promise wins, this timer is never cleared and keeps the event loop alive until it fires. Use `AbortSignal.timeout(ms)` and pass the signal to the operation."
7496
+ }
7497
+ },
7498
+ defaultOptions: [{}],
7499
+ create(context, [optionsArg]) {
7500
+ const { filename, sourceCode } = context;
7501
+ if (isTestFile(filename) || isScriptFile(filename) || isGeneratedFile(filename, sourceCode.getText())) {
7502
+ return {};
7503
+ }
7504
+ const allowIn = optionsArg?.allowIn ?? [];
7505
+ if (allowIn.length > 0 && matchesAnyPattern3(filename, allowIn)) {
7506
+ return {};
7507
+ }
7508
+ const checkClientModules = optionsArg?.checkClientModules ?? false;
7509
+ function isClientModule() {
7510
+ if (/\.[cm]?[jt]sx$/.test(filename)) {
7511
+ return true;
7512
+ }
7513
+ const program = sourceCode.ast;
7514
+ for (const statement of program.body) {
7515
+ if (statement.type === AST_NODE_TYPES37.ExpressionStatement && statement.expression.type === AST_NODE_TYPES37.Literal && statement.expression.value === "use client") {
7516
+ return true;
7517
+ }
7518
+ if (statement.type === AST_NODE_TYPES37.ImportDeclaration && typeof statement.source.value === "string" && CLIENT_ONLY_MODULES.test(statement.source.value)) {
7519
+ return true;
7520
+ }
7521
+ }
7522
+ return false;
7523
+ }
7524
+ let clientModule = null;
7525
+ const reportsSleepHere = () => {
7526
+ if (checkClientModules) {
7527
+ return true;
7528
+ }
7529
+ clientModule ??= isClientModule();
7530
+ return !clientModule;
7531
+ };
7532
+ return {
7533
+ NewExpression(node) {
7534
+ if (node.callee.type !== AST_NODE_TYPES37.Identifier || node.callee.name !== "Promise") {
7535
+ return;
7536
+ }
7537
+ const executor = node.arguments[0];
7538
+ if (executor?.type !== AST_NODE_TYPES37.ArrowFunctionExpression && executor?.type !== AST_NODE_TYPES37.FunctionExpression) {
7539
+ return;
7540
+ }
7541
+ const call = soleCall(executor);
7542
+ if (call === null || !isSetTimeoutCallee(call.callee)) {
7543
+ return;
7544
+ }
7545
+ const [callback, delay] = call.arguments;
7546
+ if (callback === void 0 || !isTimedDelay(delay)) {
7547
+ return;
7548
+ }
7549
+ const resolveName = parameterName(executor, 0);
7550
+ if (resolveName !== null && settlesWithoutValue(callback, resolveName)) {
7551
+ if (reportsSleepHere()) {
7552
+ context.report({ node, messageId: "handRolledSleep" });
7553
+ }
7554
+ return;
7555
+ }
7556
+ const rejectName = parameterName(executor, 1);
7557
+ if (rejectName !== null && isRaceArm(node) && rejectsInCallback(callback, rejectName)) {
7558
+ context.report({ node, messageId: "handRolledTimeoutRace" });
7559
+ }
7560
+ }
7561
+ };
7562
+ }
7563
+ });
7564
+
7506
7565
  // src/rules/prefer-setup-file-mocks.ts
7507
7566
  import { AST_NODE_TYPES as AST_NODE_TYPES38, ESLintUtils as ESLintUtils51 } from "@typescript-eslint/utils";
7508
7567
  var prefer_setup_file_mocks_default = ESLintUtils51.RuleCreator(
@@ -7564,7 +7623,6 @@ var rules = {
7564
7623
  "no-unsafe-mock-casting": no_unsafe_mock_casting_default,
7565
7624
  "prefer-string-literal-union": prefer_string_literal_union_default,
7566
7625
  "prefer-zod-enum": prefer_zod_enum_default,
7567
- "primary-export-file-name": primary_export_file_name_default,
7568
7626
  "no-silent-promise-catch": no_silent_promise_catch_default,
7569
7627
  "require-fetch-timeout": require_fetch_timeout_default,
7570
7628
  "no-offset-pagination": no_offset_pagination_default,
@@ -7572,6 +7630,7 @@ var rules = {
7572
7630
  "no-repeated-string-literal": no_repeated_string_literal_default,
7573
7631
  "no-select-star": no_select_star_default,
7574
7632
  "no-sleep-in-test-body": no_sleep_in_test_body_default,
7633
+ "no-hand-rolled-sleep": no_hand_rolled_sleep_default,
7575
7634
  "no-conditional-in-test": no_conditional_in_test_default,
7576
7635
  "prefer-constant-time-secret-compare": prefer_constant_time_secret_compare_default,
7577
7636
  "store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
@@ -7594,7 +7653,7 @@ var rules = {
7594
7653
  var plugin = {
7595
7654
  meta: {
7596
7655
  name: "@sarj/eslint-plugin",
7597
- version: "3.0.0"
7656
+ version: "4.1.0"
7598
7657
  },
7599
7658
  rules,
7600
7659
  configs: {
@@ -7628,7 +7687,6 @@ var plugin = {
7628
7687
  "@sarj/no-cors-wildcard-with-credentials": "warn",
7629
7688
  "@sarj/no-secret-in-log": "warn",
7630
7689
  "@sarj/no-unsafe-mock-casting": "warn",
7631
- "@sarj/primary-export-file-name": "warn",
7632
7690
  "@sarj/prefer-string-literal-union": "warn",
7633
7691
  "@sarj/prefer-zod-enum": "warn",
7634
7692
  // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
@@ -7642,6 +7700,10 @@ var plugin = {
7642
7700
  "@sarj/store-insert-requires-on-conflict": "warn",
7643
7701
  "@sarj/no-offset-pagination": "warn",
7644
7702
  "@sarj/no-select-star": "warn",
7703
+ // Uncancellable hand-rolled timers. Verified against the shipped
7704
+ // strict config (205 enabled rules) that nothing already reports this
7705
+ // position; `unicorn` 72 has no promisified-timer rule at all.
7706
+ "@sarj/no-hand-rolled-sleep": "warn",
7645
7707
  "@sarj/no-sleep-in-test-body": "warn",
7646
7708
  "@sarj/no-conditional-in-test": "warn",
7647
7709
  "@sarj/no-repeated-string-literal": "warn",
@@ -7658,7 +7720,8 @@ var plugin = {
7658
7720
  // Anti-comment-verbosity family (2026-07), from a 37,918-comment,
7659
7721
  // nine-repo measurement study. Each is a deletion-class finding, so each
7660
7722
  // was validated against pydantic / trio / attrs as well as the maintained
7661
- // repos: `no-restated-comment` 0 hits in bulbul and 4 in the three famous
7723
+ // repos: `no-restated-comment` 0 hits in the flagship first-party
7724
+ // repo and 4 in the three famous
7662
7725
  // corpora combined; `trailing-value-narration` 18 hits, 18 true
7663
7726
  // positives; `jsdoc-restates-signature` 36 hits, 0 measured false
7664
7727
  // positives, and it offers a suggestion rather than a `--fix` because a
@@ -7669,7 +7732,7 @@ var plugin = {
7669
7732
  // The TS half of SARJ057 (2026-07). Python has caught the
7670
7733
  // assertion-FREE test since 0.15.0 (SARJ043) and had no TS
7671
7734
  // counterpart, which is how `expect(true).toBe(true); // placeholder`
7672
- // survived in internal-automations: the file HAS an assertion.
7735
+ // survived in a first-party repo: the file HAS an assertion.
7673
7736
  // Measured across 5,819 .ts/.tsx files (1,003 of them test files) in
7674
7737
  // six internal repos plus got / hono / swr / trpc: 3 hits, 3 true
7675
7738
  // positives, 0 false positives.
@@ -7718,7 +7781,6 @@ var plugin = {
7718
7781
  "@sarj/no-cors-wildcard-with-credentials": "error",
7719
7782
  "@sarj/no-secret-in-log": "error",
7720
7783
  "@sarj/no-unsafe-mock-casting": "error",
7721
- "@sarj/primary-export-file-name": "error",
7722
7784
  // Promoted to error 2026-07-25 — strict means strict (user directive).
7723
7785
  "@sarj/prefer-string-literal-union": "error",
7724
7786
  "@sarj/prefer-zod-enum": "error",
@@ -7730,6 +7792,10 @@ var plugin = {
7730
7792
  "@sarj/store-insert-requires-on-conflict": "error",
7731
7793
  "@sarj/no-offset-pagination": "error",
7732
7794
  "@sarj/no-select-star": "error",
7795
+ // Uncancellable hand-rolled timers. Verified against the shipped
7796
+ // strict config (205 enabled rules) that nothing already reports this
7797
+ // position; `unicorn` 72 has no promisified-timer rule at all.
7798
+ "@sarj/no-hand-rolled-sleep": "error",
7733
7799
  "@sarj/no-sleep-in-test-body": "error",
7734
7800
  "@sarj/no-conditional-in-test": "error",
7735
7801
  "@sarj/no-repeated-string-literal": "error",