@sarj/eslint-plugin 16.2.4 → 16.2.5

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
@@ -17622,15 +17622,16 @@ var prefer_nullish_filter_predicate_default = createRule({
17622
17622
  var import_utils103 = require("@typescript-eslint/utils");
17623
17623
  var ts11 = __toESM(require("typescript"), 1);
17624
17624
  var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
17625
- summary: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform.",
17626
- rationale: "Mixing a directly returned Promise callback into otherwise async control flow makes sequencing and failures harder to read.",
17625
+ summary: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns.",
17626
+ rationale: "Promise callback chains mix explicit async control flow with nested callbacks, making sequencing and failure handling harder to read.",
17627
17627
  remediation: "Consider awaiting the Promise and returning the transformed value with ordinary async statements. Preserve catch boundaries, callback behavior, and observable scheduling when rewriting manually.",
17628
17628
  category: "maintainability",
17629
17629
  since: "15.6.3",
17630
17630
  limitations: [
17631
- "Only a single directly returned `.then` call with an inline callback is checked.",
17632
- "The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver. Untyped receivers, rejection handlers and named callbacks are excluded. This is not the upstream return-await policy and no scheduling equivalence is promised.",
17633
- "Direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports are excluded because returning the module Promise is their framework contract."
17631
+ "The default async-return scope checks one directly returned `.then` call with an inline callback. The all-promise-calls scope checks direct calls to built-in Promise/PromiseLike.then, including optional and literal-computed calls, without automatic rewrites.",
17632
+ "The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver. The default async-return scope excludes rejection handlers and named callbacks. This is not the upstream return-await policy and no scheduling equivalence is promised.",
17633
+ "The default async-return scope excludes direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports because returning the module Promise is their framework contract.",
17634
+ "All-promise-calls requires type information and excludes unproven/custom thenables, dynamic property names, property reads, destructuring, and extracted method aliases. Catch and finally remain available. Framework callback exclusions apply only to the default async-return scope."
17634
17635
  ],
17635
17636
  examples: [
17636
17637
  {
@@ -17687,15 +17688,28 @@ function promiseThenReceiver(node) {
17687
17688
  }
17688
17689
  return callee.object;
17689
17690
  }
17691
+ function directThenReceiver(node) {
17692
+ const callee = node.callee;
17693
+ if (callee.type !== import_utils103.AST_NODE_TYPES.MemberExpression) return null;
17694
+ const property = callee.property;
17695
+ const isThen = callee.computed ? property.type === import_utils103.AST_NODE_TYPES.Literal && property.value === "then" || property.type === import_utils103.AST_NODE_TYPES.TemplateLiteral && property.expressions.length === 0 && property.quasis[0]?.value.cooked === "then" : property.type === import_utils103.AST_NODE_TYPES.Identifier && property.name === "then";
17696
+ return isThen ? callee.object : null;
17697
+ }
17690
17698
  function isProvenPromiseLike(node, services) {
17691
17699
  const checker = services.program.getTypeChecker();
17692
17700
  const tsNode = services.esTreeNodeToTSNodeMap.get(node);
17693
- const receiverType = checker.getTypeAtLocation(tsNode);
17694
- if ((receiverType.flags & (ts11.TypeFlags.Any | ts11.TypeFlags.Unknown | ts11.TypeFlags.Never)) !== 0) {
17701
+ const receiverType = checker.getNonNullableType(checker.getTypeAtLocation(tsNode));
17702
+ const members = receiverType.isUnion() ? receiverType.types : [receiverType];
17703
+ return members.every((member) => isBuiltInPromiseType(member, services));
17704
+ }
17705
+ function isBuiltInPromiseType(type, services) {
17706
+ if ((type.flags & (ts11.TypeFlags.Any | ts11.TypeFlags.Unknown | ts11.TypeFlags.Never)) !== 0) {
17695
17707
  return false;
17696
17708
  }
17697
- const thenSymbol = checker.getPropertyOfType(receiverType, "then");
17698
- const hasBuiltInPromiseDeclaration = thenSymbol?.declarations?.some(
17709
+ const thenSymbol = services.program.getTypeChecker().getPropertyOfType(type, "then");
17710
+ const declarations = thenSymbol?.declarations;
17711
+ if (declarations === void 0 || declarations.length === 0) return false;
17712
+ return declarations.every(
17699
17713
  (declaration) => {
17700
17714
  let owner = declaration.parent;
17701
17715
  while (owner !== void 0 && !ts11.isInterfaceDeclaration(owner)) {
@@ -17703,8 +17717,7 @@ function isProvenPromiseLike(node, services) {
17703
17717
  }
17704
17718
  return owner !== void 0 && (owner.name.text === "Promise" || owner.name.text === "PromiseLike") && services.program.isSourceFileDefaultLibrary(owner.getSourceFile());
17705
17719
  }
17706
- ) ?? false;
17707
- return hasBuiltInPromiseDeclaration;
17720
+ );
17708
17721
  }
17709
17722
  var prefer_await_in_async_return_default = createRule({
17710
17723
  name: "prefer-await-in-async-return",
@@ -17712,15 +17725,20 @@ var prefer_await_in_async_return_default = createRule({
17712
17725
  meta: {
17713
17726
  type: "suggestion",
17714
17727
  docs: {
17715
- description: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform."
17728
+ description: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns."
17716
17729
  },
17717
- schema: [],
17730
+ schema: [{
17731
+ type: "object",
17732
+ properties: { scope: { type: "string", enum: ["async-return", "all-promise-calls"] } },
17733
+ additionalProperties: false
17734
+ }],
17718
17735
  messages: {
17719
- preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow."
17736
+ preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow.",
17737
+ preferAwaitCall: "This call uses built-in Promise.then(). Use async/await, preserving rejection handling and observable scheduling when rewriting."
17720
17738
  }
17721
17739
  },
17722
- defaultOptions: [],
17723
- create(context) {
17740
+ defaultOptions: [{ scope: "async-return" }],
17741
+ create(context, [options]) {
17724
17742
  let services;
17725
17743
  try {
17726
17744
  services = import_utils103.ESLintUtils.getParserServices(context);
@@ -17753,6 +17771,13 @@ var prefer_await_in_async_return_default = createRule({
17753
17771
  }
17754
17772
  },
17755
17773
  CallExpression(node) {
17774
+ if (options.scope === "all-promise-calls") {
17775
+ const receiver2 = directThenReceiver(node);
17776
+ if (receiver2 !== null && isProvenPromiseLike(receiver2, services)) {
17777
+ context.report({ node, messageId: "preferAwaitCall" });
17778
+ }
17779
+ return;
17780
+ }
17756
17781
  const owner = directAsyncReturnOwner(node);
17757
17782
  if (owner === null || isFrameworkLoaderCallback(owner)) return;
17758
17783
  const receiver = promiseThenReceiver(node);
@@ -23808,7 +23833,7 @@ var RULES = {
23808
23833
  };
23809
23834
  var meta = {
23810
23835
  name: "@sarj/eslint-plugin",
23811
- version: "16.2.4"
23836
+ version: "16.2.5"
23812
23837
  };
23813
23838
  var APPLICATION_ONLY_RULES = [];
23814
23839
  var LIBRARY_IMPORT_POLICY = ["error", {
package/dist/index.d.cts CHANGED
@@ -253,7 +253,9 @@ declare const RULES: {
253
253
  readonly "prefer-node-fs-promises": DocumentedRule<[], "preferAsyncFs">;
254
254
  readonly "prefer-non-nullable-collection": DocumentedRule<readonly [], "preferNonNullableCollection">;
255
255
  readonly "prefer-nullish-filter-predicate": DocumentedRule<readonly [], "preferNullishPredicate" | "replaceBoolean">;
256
- readonly "prefer-await-in-async-return": DocumentedRule<readonly [], "preferAwait">;
256
+ readonly "prefer-await-in-async-return": DocumentedRule<readonly [{
257
+ readonly scope: "async-return" | "all-promise-calls";
258
+ }], "preferAwait" | "preferAwaitCall">;
257
259
  readonly "prefer-schema-for-api-payload": DocumentedRule<readonly [], "unparsedJsonAccess">;
258
260
  readonly "prefer-shared-zod-enum": DocumentedRule<[], "shareEnumDomain">;
259
261
  readonly "prefer-switch-for-repeated-equality": DocumentedRule<[], "preferSwitch">;
@@ -922,7 +924,7 @@ type FlatPreset = {
922
924
  declare const PLUGIN: {
923
925
  readonly meta: {
924
926
  readonly name: "@sarj/eslint-plugin";
925
- readonly version: "16.2.4";
927
+ readonly version: "16.2.5";
926
928
  };
927
929
  readonly rules: {
928
930
  readonly "require-explicit-contract-implementation": DocumentedRule<readonly [], "declareActualContract">;
@@ -1052,7 +1054,9 @@ declare const PLUGIN: {
1052
1054
  readonly "prefer-node-fs-promises": DocumentedRule<[], "preferAsyncFs">;
1053
1055
  readonly "prefer-non-nullable-collection": DocumentedRule<readonly [], "preferNonNullableCollection">;
1054
1056
  readonly "prefer-nullish-filter-predicate": DocumentedRule<readonly [], "preferNullishPredicate" | "replaceBoolean">;
1055
- readonly "prefer-await-in-async-return": DocumentedRule<readonly [], "preferAwait">;
1057
+ readonly "prefer-await-in-async-return": DocumentedRule<readonly [{
1058
+ readonly scope: "async-return" | "all-promise-calls";
1059
+ }], "preferAwait" | "preferAwaitCall">;
1056
1060
  readonly "prefer-schema-for-api-payload": DocumentedRule<readonly [], "unparsedJsonAccess">;
1057
1061
  readonly "prefer-shared-zod-enum": DocumentedRule<[], "shareEnumDomain">;
1058
1062
  readonly "prefer-switch-for-repeated-equality": DocumentedRule<[], "preferSwitch">;
package/dist/index.d.ts CHANGED
@@ -253,7 +253,9 @@ declare const RULES: {
253
253
  readonly "prefer-node-fs-promises": DocumentedRule<[], "preferAsyncFs">;
254
254
  readonly "prefer-non-nullable-collection": DocumentedRule<readonly [], "preferNonNullableCollection">;
255
255
  readonly "prefer-nullish-filter-predicate": DocumentedRule<readonly [], "preferNullishPredicate" | "replaceBoolean">;
256
- readonly "prefer-await-in-async-return": DocumentedRule<readonly [], "preferAwait">;
256
+ readonly "prefer-await-in-async-return": DocumentedRule<readonly [{
257
+ readonly scope: "async-return" | "all-promise-calls";
258
+ }], "preferAwait" | "preferAwaitCall">;
257
259
  readonly "prefer-schema-for-api-payload": DocumentedRule<readonly [], "unparsedJsonAccess">;
258
260
  readonly "prefer-shared-zod-enum": DocumentedRule<[], "shareEnumDomain">;
259
261
  readonly "prefer-switch-for-repeated-equality": DocumentedRule<[], "preferSwitch">;
@@ -922,7 +924,7 @@ type FlatPreset = {
922
924
  declare const PLUGIN: {
923
925
  readonly meta: {
924
926
  readonly name: "@sarj/eslint-plugin";
925
- readonly version: "16.2.4";
927
+ readonly version: "16.2.5";
926
928
  };
927
929
  readonly rules: {
928
930
  readonly "require-explicit-contract-implementation": DocumentedRule<readonly [], "declareActualContract">;
@@ -1052,7 +1054,9 @@ declare const PLUGIN: {
1052
1054
  readonly "prefer-node-fs-promises": DocumentedRule<[], "preferAsyncFs">;
1053
1055
  readonly "prefer-non-nullable-collection": DocumentedRule<readonly [], "preferNonNullableCollection">;
1054
1056
  readonly "prefer-nullish-filter-predicate": DocumentedRule<readonly [], "preferNullishPredicate" | "replaceBoolean">;
1055
- readonly "prefer-await-in-async-return": DocumentedRule<readonly [], "preferAwait">;
1057
+ readonly "prefer-await-in-async-return": DocumentedRule<readonly [{
1058
+ readonly scope: "async-return" | "all-promise-calls";
1059
+ }], "preferAwait" | "preferAwaitCall">;
1056
1060
  readonly "prefer-schema-for-api-payload": DocumentedRule<readonly [], "unparsedJsonAccess">;
1057
1061
  readonly "prefer-shared-zod-enum": DocumentedRule<[], "shareEnumDomain">;
1058
1062
  readonly "prefer-switch-for-repeated-equality": DocumentedRule<[], "preferSwitch">;
package/dist/index.js CHANGED
@@ -17631,15 +17631,16 @@ import {
17631
17631
  } from "@typescript-eslint/utils";
17632
17632
  import * as ts11 from "typescript";
17633
17633
  var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
17634
- summary: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform.",
17635
- rationale: "Mixing a directly returned Promise callback into otherwise async control flow makes sequencing and failures harder to read.",
17634
+ summary: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns.",
17635
+ rationale: "Promise callback chains mix explicit async control flow with nested callbacks, making sequencing and failure handling harder to read.",
17636
17636
  remediation: "Consider awaiting the Promise and returning the transformed value with ordinary async statements. Preserve catch boundaries, callback behavior, and observable scheduling when rewriting manually.",
17637
17637
  category: "maintainability",
17638
17638
  since: "15.6.3",
17639
17639
  limitations: [
17640
- "Only a single directly returned `.then` call with an inline callback is checked.",
17641
- "The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver. Untyped receivers, rejection handlers and named callbacks are excluded. This is not the upstream return-await policy and no scheduling equivalence is promised.",
17642
- "Direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports are excluded because returning the module Promise is their framework contract."
17640
+ "The default async-return scope checks one directly returned `.then` call with an inline callback. The all-promise-calls scope checks direct calls to built-in Promise/PromiseLike.then, including optional and literal-computed calls, without automatic rewrites.",
17641
+ "The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver. The default async-return scope excludes rejection handlers and named callbacks. This is not the upstream return-await policy and no scheduling equivalence is promised.",
17642
+ "The default async-return scope excludes direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports because returning the module Promise is their framework contract.",
17643
+ "All-promise-calls requires type information and excludes unproven/custom thenables, dynamic property names, property reads, destructuring, and extracted method aliases. Catch and finally remain available. Framework callback exclusions apply only to the default async-return scope."
17643
17644
  ],
17644
17645
  examples: [
17645
17646
  {
@@ -17696,15 +17697,28 @@ function promiseThenReceiver(node) {
17696
17697
  }
17697
17698
  return callee.object;
17698
17699
  }
17700
+ function directThenReceiver(node) {
17701
+ const callee = node.callee;
17702
+ if (callee.type !== AST_NODE_TYPES86.MemberExpression) return null;
17703
+ const property = callee.property;
17704
+ const isThen = callee.computed ? property.type === AST_NODE_TYPES86.Literal && property.value === "then" || property.type === AST_NODE_TYPES86.TemplateLiteral && property.expressions.length === 0 && property.quasis[0]?.value.cooked === "then" : property.type === AST_NODE_TYPES86.Identifier && property.name === "then";
17705
+ return isThen ? callee.object : null;
17706
+ }
17699
17707
  function isProvenPromiseLike(node, services) {
17700
17708
  const checker = services.program.getTypeChecker();
17701
17709
  const tsNode = services.esTreeNodeToTSNodeMap.get(node);
17702
- const receiverType = checker.getTypeAtLocation(tsNode);
17703
- if ((receiverType.flags & (ts11.TypeFlags.Any | ts11.TypeFlags.Unknown | ts11.TypeFlags.Never)) !== 0) {
17710
+ const receiverType = checker.getNonNullableType(checker.getTypeAtLocation(tsNode));
17711
+ const members = receiverType.isUnion() ? receiverType.types : [receiverType];
17712
+ return members.every((member) => isBuiltInPromiseType(member, services));
17713
+ }
17714
+ function isBuiltInPromiseType(type, services) {
17715
+ if ((type.flags & (ts11.TypeFlags.Any | ts11.TypeFlags.Unknown | ts11.TypeFlags.Never)) !== 0) {
17704
17716
  return false;
17705
17717
  }
17706
- const thenSymbol = checker.getPropertyOfType(receiverType, "then");
17707
- const hasBuiltInPromiseDeclaration = thenSymbol?.declarations?.some(
17718
+ const thenSymbol = services.program.getTypeChecker().getPropertyOfType(type, "then");
17719
+ const declarations = thenSymbol?.declarations;
17720
+ if (declarations === void 0 || declarations.length === 0) return false;
17721
+ return declarations.every(
17708
17722
  (declaration) => {
17709
17723
  let owner = declaration.parent;
17710
17724
  while (owner !== void 0 && !ts11.isInterfaceDeclaration(owner)) {
@@ -17712,8 +17726,7 @@ function isProvenPromiseLike(node, services) {
17712
17726
  }
17713
17727
  return owner !== void 0 && (owner.name.text === "Promise" || owner.name.text === "PromiseLike") && services.program.isSourceFileDefaultLibrary(owner.getSourceFile());
17714
17728
  }
17715
- ) ?? false;
17716
- return hasBuiltInPromiseDeclaration;
17729
+ );
17717
17730
  }
17718
17731
  var prefer_await_in_async_return_default = createRule({
17719
17732
  name: "prefer-await-in-async-return",
@@ -17721,15 +17734,20 @@ var prefer_await_in_async_return_default = createRule({
17721
17734
  meta: {
17722
17735
  type: "suggestion",
17723
17736
  docs: {
17724
- description: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform."
17737
+ description: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns."
17725
17738
  },
17726
- schema: [],
17739
+ schema: [{
17740
+ type: "object",
17741
+ properties: { scope: { type: "string", enum: ["async-return", "all-promise-calls"] } },
17742
+ additionalProperties: false
17743
+ }],
17727
17744
  messages: {
17728
- preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow."
17745
+ preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow.",
17746
+ preferAwaitCall: "This call uses built-in Promise.then(). Use async/await, preserving rejection handling and observable scheduling when rewriting."
17729
17747
  }
17730
17748
  },
17731
- defaultOptions: [],
17732
- create(context) {
17749
+ defaultOptions: [{ scope: "async-return" }],
17750
+ create(context, [options]) {
17733
17751
  let services;
17734
17752
  try {
17735
17753
  services = ESLintUtils13.getParserServices(context);
@@ -17762,6 +17780,13 @@ var prefer_await_in_async_return_default = createRule({
17762
17780
  }
17763
17781
  },
17764
17782
  CallExpression(node) {
17783
+ if (options.scope === "all-promise-calls") {
17784
+ const receiver2 = directThenReceiver(node);
17785
+ if (receiver2 !== null && isProvenPromiseLike(receiver2, services)) {
17786
+ context.report({ node, messageId: "preferAwaitCall" });
17787
+ }
17788
+ return;
17789
+ }
17765
17790
  const owner = directAsyncReturnOwner(node);
17766
17791
  if (owner === null || isFrameworkLoaderCallback(owner)) return;
17767
17792
  const receiver = promiseThenReceiver(node);
@@ -23829,7 +23854,7 @@ var RULES = {
23829
23854
  };
23830
23855
  var meta = {
23831
23856
  name: "@sarj/eslint-plugin",
23832
- version: "16.2.4"
23857
+ version: "16.2.5"
23833
23858
  };
23834
23859
  var APPLICATION_ONLY_RULES = [];
23835
23860
  var LIBRARY_IMPORT_POLICY = ["error", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "16.2.4",
3
+ "version": "16.2.5",
4
4
  "packageManager": "npm@12.1.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",