@sarj/eslint-plugin 19.10.0 → 19.14.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/catalog.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  publicDocumentation,
3
3
  ruleSpec,
4
4
  src_default
5
- } from "./chunk-ZWOGM4CV.js";
5
+ } from "./chunk-YWEV6GQM.js";
6
6
 
7
7
  // src/docs/directive-disable-enable-pair.ts
8
8
  var DIRECTIVE_DISABLE_ENABLE_PAIR_DOCUMENTATION = {
@@ -4163,9 +4163,9 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
4163
4163
  "Checks source-resolved local unions with required literal ok/success booleans, or status/state/kind/type/tag values ok/success versus err/error/failure/failed (case insensitive). Conflicting outcome tags abstain. A shared outcome tag does not also count as a domain error code. Named and namespace neverthrow Result/ResultAsync imports establish their generic error position.",
4164
4164
  "Presence-selected unions require an error property on every failure branch and the same single required non-control value property on every success branch. Optional error/value fields, multiple value keys, loading states and error branches carrying the success value abstain. Required errorMessage and Error-suffixed failure channels are also reviewed; multiple custom channels abstain and a primary error payload retains precedence.",
4165
4165
  "Broad string and unknown error channels, unshadowed native Error-family types and open string/unknown code fields receive review. Explicit Any stays with the existing no-explicit-any rule. Finite literal codes need no invented payload. Imported domain errors, validation-library errors, generic error parameters, ordinary formatters, legitimate absence and redacted failure states remain valid.",
4166
- "Local aliases, unmerged interfaces without inheritance and generic substitutions are bounded to 16 resolution levels, 64 union members and 256 traversal steps. Imported local declarations, conditional/intersection/mapped types and unresolved contracts abstain. Optional discriminants, independent success/error payloads and partial-data failures are excluded.",
4166
+ "Local aliases, unmerged interfaces without inheritance and generic substitutions are bounded to 16 resolution levels, 64 union members and 256 traversal steps. Transparent aliases retain the proven neverthrow Result/ResultAsync error position, including renamed/namespace imports, generic defaults and unshadowed native Promise/PromiseLike wrappers. Imported local declarations, conditional/intersection/mapped types and unresolved contracts abstain. Optional discriminants, independent success/error payloads and partial-data failures are excluded.",
4167
4167
  "Required code/kind/type/tag fields of unresolved or imported type defer to the type checker; a finite code does not prove that its cases or payloads are useful. Generic error parameters are retained rather than guessed. Ordinary string formatters, absence, validation-library errors and redacted failure states without a string payload are not flagged.",
4168
- "A directly returned failure object, neverthrow err/errAsync call or new Err with an interpolated template or proven string concatenation receives review. Without a code, the diagnostic asks for a domain contract; with a literal code, it asks for consumer formatting. Literal-only compositions, including earlier stable local constants, remain valid codes. Nested plain error objects, expression-bodied arrows and one direct await are supported. Import identity and value-space shadowing are checked; arbitrary aliases, spreads, accessors, computed keys, helper formatting calls and nonliteral codes abstain. This proves formatting ownership, not which context the domain must expose; presentation adapters remain a reviewed exception.",
4168
+ "A directly returned failure object, neverthrow err/errAsync call or new Err with an interpolated template or proven string concatenation receives review. Without a code, the diagnostic asks for a domain contract; with a literal code, it asks for consumer formatting. Literal-only compositions, including earlier stable local constants, remain valid codes. Nested plain error objects, expression-bodied arrows and awaited returns are supported. Returned conditional branches are followed without visiting their conditions, call arguments or containers; logical/sequence expressions and arbitrary aliases abstain. The entire return expression abstains beyond 16 levels, 64 leaves or 256 traversal steps. Import identity and value-space shadowing are checked; spreads, accessors, computed keys, helper formatting calls and nonliteral codes abstain. This proves formatting ownership, not which context the domain must expose; presentation adapters remain a reviewed exception.",
4169
4169
  "This warning requests domain review: an infrastructure adapter may intentionally retain unknown/Error, and a final presentation boundary may accept every failure and render one message. Neither proves a recoverable domain rejection. No fix invents error codes, fields, handlers or changes serialization, exception unwinding, transactions, retries or logging."
4170
4170
  ],
4171
4171
  references: [
@@ -4173,6 +4173,66 @@ var PREFER_CODED_ERROR_RESULT_DOCUMENTATION = {
4173
4173
  "https://github.com/supermacro/neverthrow"
4174
4174
  ],
4175
4175
  examples: [
4176
+ {
4177
+ id: "conditional-message-failure",
4178
+ scenarioId: "conditional-result-producer",
4179
+ title: "A conditional return still needs a useful error contract",
4180
+ outcome: "match",
4181
+ files: [
4182
+ {
4183
+ path: "app/result.ts",
4184
+ source: "import { err, ok } from 'neverthrow'; export const save = (name: string, allowed: boolean) => allowed ? ok(name) : err(`Denied ${name}`);"
4185
+ }
4186
+ ],
4187
+ focusPath: "app/result.ts",
4188
+ expectedCount: 1,
4189
+ public: true
4190
+ },
4191
+ {
4192
+ id: "conditional-coded-failure",
4193
+ scenarioId: "conditional-result-producer",
4194
+ title: "Preserve the code and context and let the consumer format",
4195
+ outcome: "no-match",
4196
+ files: [
4197
+ {
4198
+ path: "app/result.ts",
4199
+ source: "import { err, ok, type Result } from 'neverthrow'; type Denied = { code: 'denied'; name: string }; export function save(name: string, allowed: boolean): Result<string, Denied> { return allowed ? ok(name) : err({ code: 'denied', name }); } export function format(error: Denied): string { return `Denied ${error.name}`; }"
4200
+ }
4201
+ ],
4202
+ focusPath: "app/result.ts",
4203
+ expectedCount: 0,
4204
+ public: true
4205
+ },
4206
+ {
4207
+ id: "aliased-library-message",
4208
+ scenarioId: "aliased-library-error",
4209
+ title: "A transparent alias must preserve the library error contract",
4210
+ outcome: "match",
4211
+ files: [
4212
+ {
4213
+ path: "app/result.ts",
4214
+ source: "import type { Result as NativeResult } from 'neverthrow'; type Saved<T, E> = Promise<NativeResult<T, E>>; export type Output = Saved<number, string>;"
4215
+ }
4216
+ ],
4217
+ focusPath: "app/result.ts",
4218
+ expectedCount: 1,
4219
+ public: true
4220
+ },
4221
+ {
4222
+ id: "aliased-library-domain-error",
4223
+ scenarioId: "aliased-library-error",
4224
+ title: "Keep a finite error code and only the context its consumer needs",
4225
+ outcome: "no-match",
4226
+ files: [
4227
+ {
4228
+ path: "app/result.ts",
4229
+ source: "import type { Result as NativeResult } from 'neverthrow'; type Saved<T, E> = Promise<NativeResult<T, E>>; export type Output = Saved<number, { code: 'duplicate'; name: string } | { code: 'denied' }>;"
4230
+ }
4231
+ ],
4232
+ focusPath: "app/result.ts",
4233
+ expectedCount: 0,
4234
+ public: true
4235
+ },
4176
4236
  {
4177
4237
  id: "presence-selected-message",
4178
4238
  scenarioId: "presence-selected-result",
@@ -21588,14 +21588,15 @@ function resolve2(value, source, depth = 0) {
21588
21588
  if (depth >= MAX_DEPTH) return null;
21589
21589
  const node = value.node;
21590
21590
  if (node.type !== "TSTypeReference") return value;
21591
- if (node.typeName.type !== "Identifier") return null;
21591
+ if (node.typeName.type !== "Identifier")
21592
+ return libraryResult(node, source) ? value : null;
21592
21593
  const variable = typeBinding(source, node.typeName);
21593
21594
  if (variable === null || variable.defs.length === 0) return value;
21594
21595
  const bound = value.bindings.get(variable);
21595
21596
  if (bound !== void 0) return resolve2(bound, source, depth + 1);
21596
21597
  const declaration = variable.defs.length === 1 ? variable.defs[0]?.node : void 0;
21597
21598
  if (declaration?.type !== "TSTypeAliasDeclaration" && declaration?.type !== "TSInterfaceDeclaration")
21598
- return null;
21599
+ return libraryResult(node, source) ? value : null;
21599
21600
  if (declaration.type === "TSInterfaceDeclaration" && declaration.extends.length !== 0)
21600
21601
  return null;
21601
21602
  const bindings = bindArguments(declaration, node, value, source);
@@ -21761,6 +21762,26 @@ function openStringField(fields2, owner, source, names, budget) {
21761
21762
  }
21762
21763
  return null;
21763
21764
  }
21765
+ function outcomeFailures(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
21766
+ if (depth >= MAX_DEPTH || --budget.remaining < 0) return [];
21767
+ const resolved = resolve2(value, source);
21768
+ if (resolved === null) return [];
21769
+ const structural = failureMessages(resolved, source);
21770
+ if (structural.length !== 0) return structural;
21771
+ const library = libraryError(resolved, source);
21772
+ if (library !== null) return [library];
21773
+ const node = resolved.node;
21774
+ if (node.type !== "TSTypeReference") return [];
21775
+ if (node.typeName.type !== "Identifier" || !["Promise", "PromiseLike"].includes(node.typeName.name) || (typeBinding(source, node.typeName)?.defs.length ?? 0) !== 0 || node.typeArguments?.params.length !== 1)
21776
+ return [];
21777
+ const argument = node.typeArguments.params[0];
21778
+ return argument === void 0 ? [] : outcomeFailures(
21779
+ { ...resolved, node: argument, origin: argument },
21780
+ source,
21781
+ depth + 1,
21782
+ budget
21783
+ );
21784
+ }
21764
21785
  function failureMessages(value, source) {
21765
21786
  const members2 = variants(value, source);
21766
21787
  if (members2 === null || members2.length < 2) return [];
@@ -21853,14 +21874,18 @@ function variants(value, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS
21853
21874
  }
21854
21875
  return result;
21855
21876
  }
21856
- function libraryError(reference, source) {
21857
- if (reference.typeArguments?.params.length !== 2) return null;
21858
- if (!["Result", "ResultAsync"].includes(
21859
- libraryName(reference.typeName, source, true) ?? ""
21860
- ))
21877
+ function libraryError(value, source) {
21878
+ const reference = value.node;
21879
+ if (reference.type !== "TSTypeReference" || !libraryResult(reference, source))
21861
21880
  return null;
21881
+ if (reference.typeArguments?.params.length !== 2) return null;
21862
21882
  const error = reference.typeArguments.params[1];
21863
- return error === void 0 ? null : weakError(view(error), source);
21883
+ return error === void 0 ? null : weakError({ ...value, node: error, origin: error }, source);
21884
+ }
21885
+ function libraryResult(reference, source) {
21886
+ return ["Result", "ResultAsync"].includes(
21887
+ libraryName(reference.typeName, source, true) ?? ""
21888
+ );
21864
21889
  }
21865
21890
  function libraryName(node, source, typeSpace) {
21866
21891
  const reference = libraryReference(node);
@@ -21886,15 +21911,18 @@ function libraryReference(node) {
21886
21911
  return { root: node.object, member: node.property.name };
21887
21912
  return null;
21888
21913
  }
21889
- function weakAnnotation(node, source, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
21890
- if (depth >= MAX_DEPTH || --budget.remaining < 0) return false;
21891
- if (failureMessages(view(node), source).length !== 0) return true;
21892
- if (node.type !== "TSTypeReference") return false;
21893
- if (libraryError(node, source) !== null) return true;
21894
- if (node.typeName.type !== "Identifier" || !["Promise", "PromiseLike"].includes(node.typeName.name) || typeBinding(source, node.typeName) !== null || node.typeArguments?.params.length !== 1)
21895
- return false;
21896
- const argument = node.typeArguments.params[0];
21897
- return argument !== void 0 && weakAnnotation(argument, source, depth + 1, budget);
21914
+ function returnedValues(node, depth = 0, budget = { remaining: MAX_TYPE_STEPS }) {
21915
+ if (depth >= MAX_DEPTH || --budget.remaining < 0) return null;
21916
+ const value = unwrapExpression(node);
21917
+ if (value.type === "AwaitExpression")
21918
+ return returnedValues(value.argument, depth + 1, budget);
21919
+ if (value.type !== "ConditionalExpression") return [value];
21920
+ const consequent = returnedValues(value.consequent, depth + 1, budget);
21921
+ if (consequent === null) return null;
21922
+ const alternate = returnedValues(value.alternate, depth + 1, budget);
21923
+ if (alternate === null || consequent.length + alternate.length > MAX_VARIANTS)
21924
+ return null;
21925
+ return [...consequent, ...alternate];
21898
21926
  }
21899
21927
  function renderedFailure(node, source) {
21900
21928
  let value = unwrapExpression(node);
@@ -22017,9 +22045,7 @@ var prefer_coded_error_result_default = defineRule({
22017
22045
  createOnce(context) {
22018
22046
  const reported = /* @__PURE__ */ new Set();
22019
22047
  function check(node) {
22020
- const candidates2 = failureMessages(view(node), context.sourceCode);
22021
- const library = node.type === "TSTypeReference" ? libraryError(node, context.sourceCode) : null;
22022
- for (const candidate2 of library === null ? candidates2 : [...candidates2, library]) {
22048
+ for (const candidate2 of outcomeFailures(view(node), context.sourceCode)) {
22023
22049
  if (reported.has(candidate2)) continue;
22024
22050
  reported.add(candidate2);
22025
22051
  context.report({
@@ -22030,13 +22056,19 @@ var prefer_coded_error_result_default = defineRule({
22030
22056
  }
22031
22057
  function checkProducer(owner, value) {
22032
22058
  if (owner === null) return;
22033
- const rendered = renderedFailure(value, context.sourceCode);
22034
- if (rendered === null || reported.has(rendered.node)) return;
22059
+ const issues = (returnedValues(value) ?? []).flatMap((leaf) => {
22060
+ const issue = renderedFailure(leaf, context.sourceCode);
22061
+ return issue === null ? [] : [issue];
22062
+ });
22063
+ if (issues.length === 0) return;
22035
22064
  const contract = owner.returnType?.typeAnnotation;
22036
- if (contract !== void 0 && weakAnnotation(contract, context.sourceCode))
22065
+ if (contract !== void 0 && outcomeFailures(view(contract), context.sourceCode).length !== 0)
22037
22066
  return;
22038
- reported.add(rendered.node);
22039
- context.report(rendered);
22067
+ for (const issue of issues) {
22068
+ if (reported.has(issue.node)) continue;
22069
+ reported.add(issue.node);
22070
+ context.report(issue);
22071
+ }
22040
22072
  }
22041
22073
  return {
22042
22074
  before() {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  src_default
3
- } from "./chunk-ZWOGM4CV.js";
3
+ } from "./chunk-YWEV6GQM.js";
4
4
  export {
5
5
  src_default as default
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "19.10.0",
3
+ "version": "19.14.0",
4
4
  "packageManager": "npm@12.2.1",
5
5
  "description": "Custom Oxlint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",