atom.io 0.44.9 → 0.44.10

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.
@@ -1,13 +1,16 @@
1
1
  import { ESLintUtils } from "@typescript-eslint/utils";
2
2
  import { ESLint } from "eslint";
3
3
 
4
+ //#region src/eslint-plugin/rules/exact-catch-types.d.ts
5
+ declare const exactCatchTypes: ESLintUtils.RuleModule<`extraneousErrorTypes` | `invalidCatchProperty` | `missingCatchProperty`, [], unknown, ESLintUtils.RuleListener>;
6
+ //#endregion
4
7
  //#region src/eslint-plugin/rules/explicit-state-types.d.ts
5
8
  type Options = [{
6
9
  permitAnnotation?: boolean;
7
10
  }];
8
11
  declare const explicitStateTypes: ESLintUtils.RuleModule<`noTypeArgument` | `noTypeArgumentOrAnnotation`, Options, unknown, ESLintUtils.RuleListener>;
9
12
  declare namespace index_d_exports {
10
- export { explicitStateTypes };
13
+ export { exactCatchTypes, explicitStateTypes };
11
14
  }
12
15
  //#endregion
13
16
  //#region src/eslint-plugin/index.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":["explicitStateTypes: ESLintUtils.RuleModule<\n\t`noTypeArgument` | `noTypeArgumentOrAnnotation`,\n\tOptions,\n\tunknown,\n\tESLintUtils.RuleListener\n>","plugin: ESLint.Plugin"],"sources":["../../src/eslint-plugin/rules/explicit-state-types.ts","../../src/eslint-plugin/rules/index.ts","../../src/eslint-plugin/index.ts"],"sourcesContent":[],"mappings":";;;;KAiBK,OAAA;;;cAMQA,oBAAoB,WAAA,CAAY,4DAE5C,kBAEA,WAAA,CAAY;;;;;;cErBPC,QAAQ,MAAA,CAAO"}
1
+ {"version":3,"file":"index.d.ts","names":["exactCatchTypes: ESLintUtils.RuleModule<\n\t`extraneousErrorTypes` | `invalidCatchProperty` | `missingCatchProperty`,\n\t[],\n\tunknown,\n\tESLintUtils.RuleListener\n>","explicitStateTypes: ESLintUtils.RuleModule<\n\t`noTypeArgument` | `noTypeArgumentOrAnnotation`,\n\tOptions,\n\tunknown,\n\tESLintUtils.RuleListener\n>","plugin: ESLint.Plugin"],"sources":["../../src/eslint-plugin/rules/exact-catch-types.ts","../../src/eslint-plugin/rules/explicit-state-types.ts","../../src/eslint-plugin/rules/index.ts","../../src/eslint-plugin/index.ts"],"sourcesContent":[],"mappings":";;;;cAqBaA,iBAAiB,WAAA,CAAY,kGAIzC,WAAA,CAAY;;;KCTR,OAAA;;;cAMQC,oBAAoB,WAAA,CAAY,4DAE5C,kBAEA,WAAA,CAAY;;;;;;cEpBPC,QAAQ,MAAA,CAAO"}
@@ -1,6 +1,133 @@
1
1
  import { t as __export } from "../chunk-D8lmAICg.js";
2
2
  import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
3
3
 
4
+ //#region src/eslint-plugin/rules/exact-catch-types.ts
5
+ const createRule$1 = ESLintUtils.RuleCreator((name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`);
6
+ const STATE_FUNCTIONS_WITH_CATCH = [
7
+ `atom`,
8
+ `atomFamily`,
9
+ `selector`,
10
+ `selectorFamily`
11
+ ];
12
+ const STANDALONE_FUNCTIONS = [`atom`, `selector`];
13
+ const exactCatchTypes = createRule$1({
14
+ name: `catch-constructor-type`,
15
+ meta: {
16
+ type: `problem`,
17
+ docs: { description: `Ensures that when an error type (E) is provided to an atom, the 'catch' property is set and all constructors in it are assignable to E.` },
18
+ messages: {
19
+ missingCatchProperty: "This {{functionName}} was provided the error type `{{errorTypeName}}` but the required 'catch' property is missing from its options. Either remove `{{errorTypeName}}`, or add `catch: [{{errorTypeName}}]` to the options object.",
20
+ invalidCatchProperty: "This {{functionName}} was provided a catch array containing the class `{{constructorName}}`. However, that class is not represented in the {{functionName}}'s error type, `{{errorTypeName}}`. As a result, it might catch errors that the {{functionName}} is not designed to handle. Either include `{{constructorName}}` in the {{functionName}}'s error type, or remove it from the 'catch' array.",
21
+ extraneousErrorTypes: "This {{functionName}} was provided an error type including the class {{errorTypeName}}, but its 'catch' property doesn't include a constructor for that class. Either include a constructor for {{errorTypeName}} in the 'catch' array, or remove {{errorTypeName}} from the error type."
22
+ },
23
+ schema: []
24
+ },
25
+ defaultOptions: [],
26
+ create(context) {
27
+ const parserServices = ESLintUtils.getParserServices(context);
28
+ const checker = parserServices.program.getTypeChecker();
29
+ return { CallExpression(node) {
30
+ const { callee, typeArguments: directTypeArguments, arguments: callArguments } = node;
31
+ let functionName = null;
32
+ if (callee.type === AST_NODE_TYPES.Identifier) {
33
+ if (STATE_FUNCTIONS_WITH_CATCH.includes(callee.name)) functionName = callee.name;
34
+ } else if (callee.type === AST_NODE_TYPES.MemberExpression) {
35
+ if (callee.property.type === AST_NODE_TYPES.Identifier && STATE_FUNCTIONS_WITH_CATCH.includes(callee.property.name)) functionName = callee.property.name;
36
+ }
37
+ if (!functionName) return;
38
+ let typeArguments;
39
+ if (directTypeArguments) typeArguments = directTypeArguments;
40
+ else {
41
+ const parent = node.parent;
42
+ if (parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.init === node) {
43
+ const declaratorId = parent.id;
44
+ if (declaratorId.type === AST_NODE_TYPES.Identifier) {
45
+ const typeAnnotation = declaratorId.typeAnnotation?.typeAnnotation;
46
+ if (typeAnnotation && `typeArguments` in typeAnnotation && typeAnnotation.typeArguments) typeArguments = typeAnnotation.typeArguments;
47
+ }
48
+ }
49
+ }
50
+ const optionsObject = callArguments[0];
51
+ if (optionsObject?.type !== AST_NODE_TYPES.ObjectExpression) return;
52
+ const isStandalone = STANDALONE_FUNCTIONS.includes(functionName);
53
+ const errorTypeNode = typeArguments ? isStandalone ? typeArguments.params[1] : typeArguments.params[2] : void 0;
54
+ let catchProperty;
55
+ optionsObject.properties.forEach((property) => {
56
+ if (property.type === AST_NODE_TYPES.Property) {
57
+ if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === `catch` || property.key.type === AST_NODE_TYPES.Literal && property.key.value === `catch`) catchProperty = property;
58
+ }
59
+ });
60
+ if (!errorTypeNode) return;
61
+ const typeNode = parserServices.esTreeNodeToTSNodeMap.get(errorTypeNode);
62
+ const errorTypeTs = checker.getTypeFromTypeNode(typeNode);
63
+ const errorTypeName = checker.typeToString(errorTypeTs);
64
+ if (!catchProperty) {
65
+ context.report({
66
+ node: optionsObject,
67
+ messageId: `missingCatchProperty`,
68
+ data: {
69
+ functionName,
70
+ errorTypeName
71
+ }
72
+ });
73
+ return;
74
+ }
75
+ const catchArray = catchProperty.value;
76
+ if (catchArray.type !== AST_NODE_TYPES.ArrayExpression) return;
77
+ const acceptableErrorSymbols = [];
78
+ if (errorTypeTs.isUnion()) for (const memberType of errorTypeTs.types) {
79
+ const symbol = memberType.getSymbol();
80
+ if (symbol) acceptableErrorSymbols.push(symbol);
81
+ }
82
+ else {
83
+ const symbol = errorTypeTs.getSymbol();
84
+ if (symbol) acceptableErrorSymbols.push(symbol);
85
+ }
86
+ if (catchArray.elements.length === 0) {
87
+ context.report({
88
+ node: catchProperty,
89
+ messageId: `missingCatchProperty`,
90
+ data: {
91
+ functionName,
92
+ errorTypeName
93
+ }
94
+ });
95
+ return;
96
+ }
97
+ const errorSymbolsToRepresent = new Set(acceptableErrorSymbols);
98
+ for (const element of catchArray.elements) {
99
+ if (!element || element.type !== AST_NODE_TYPES.Identifier) continue;
100
+ const constructorTsNode = parserServices.esTreeNodeToTSNodeMap.get(element);
101
+ const constructorType = checker.getTypeAtLocation(constructorTsNode);
102
+ const constructorName = element.name;
103
+ let instanceType;
104
+ if (constructorType.getConstructSignatures().length > 0) instanceType = constructorType.getConstructSignatures()[0].getReturnType();
105
+ const constructorInstanceSymbol = instanceType?.getSymbol();
106
+ if (!constructorInstanceSymbol) continue;
107
+ if (acceptableErrorSymbols.includes(constructorInstanceSymbol)) errorSymbolsToRepresent.delete(constructorInstanceSymbol);
108
+ else context.report({
109
+ node: element,
110
+ messageId: `invalidCatchProperty`,
111
+ data: {
112
+ functionName,
113
+ constructorName,
114
+ errorTypeName
115
+ }
116
+ });
117
+ }
118
+ for (const errorSymbol of errorSymbolsToRepresent) context.report({
119
+ node: catchProperty,
120
+ messageId: `extraneousErrorTypes`,
121
+ data: {
122
+ errorTypeName: checker.symbolToString(errorSymbol),
123
+ functionName
124
+ }
125
+ });
126
+ } };
127
+ }
128
+ });
129
+
130
+ //#endregion
4
131
  //#region src/eslint-plugin/rules/explicit-state-types.ts
5
132
  const createRule = ESLintUtils.RuleCreator((name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`);
6
133
  const STATE_FUNCTIONS = [
@@ -68,11 +195,17 @@ const explicitStateTypes = createRule({
68
195
 
69
196
  //#endregion
70
197
  //#region src/eslint-plugin/rules/index.ts
71
- var rules_exports = /* @__PURE__ */ __export({ explicitStateTypes: () => explicitStateTypes });
198
+ var rules_exports = /* @__PURE__ */ __export({
199
+ exactCatchTypes: () => exactCatchTypes,
200
+ explicitStateTypes: () => explicitStateTypes
201
+ });
72
202
 
73
203
  //#endregion
74
204
  //#region src/eslint-plugin/index.ts
75
- const plugin = { rules: { "explicit-state-types": explicitStateTypes } };
205
+ const plugin = { rules: {
206
+ "exact-catch-types": exactCatchTypes,
207
+ "explicit-state-types": explicitStateTypes
208
+ } };
76
209
  var eslint_plugin_default = plugin;
77
210
 
78
211
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["explicitStateTypes: ESLintUtils.RuleModule<\n\t`noTypeArgument` | `noTypeArgumentOrAnnotation`,\n\tOptions,\n\tunknown,\n\tESLintUtils.RuleListener\n>","plugin: ESLint.Plugin","Rules.explicitStateTypes"],"sources":["../../src/eslint-plugin/rules/explicit-state-types.ts","../../src/eslint-plugin/rules/index.ts","../../src/eslint-plugin/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/switch-exhaustiveness-check */\nimport type { TSESTree } from \"@typescript-eslint/utils\"\nimport { AST_NODE_TYPES, ESLintUtils } from \"@typescript-eslint/utils\"\n\nconst createRule = ESLintUtils.RuleCreator(\n\t(name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`,\n)\n\nconst STATE_FUNCTIONS = [\n\t`atom`,\n\t`atomFamily`,\n\t`mutableAtom`,\n\t`mutableAtomFamily`,\n\t`selector`,\n\t`selectorFamily`,\n]\n\ntype Options = [\n\t{\n\t\tpermitAnnotation?: boolean\n\t},\n]\n\nexport const explicitStateTypes: ESLintUtils.RuleModule<\n\t`noTypeArgument` | `noTypeArgumentOrAnnotation`,\n\tOptions,\n\tunknown,\n\tESLintUtils.RuleListener\n> = createRule({\n\tname: `explicit-state-types`,\n\tmeta: {\n\t\ttype: `problem`,\n\t\tdocs: {\n\t\t\tdescription: `State declarations must have generic type arguments directly passed to them`,\n\t\t},\n\t\tmessages: {\n\t\t\tnoTypeArgument: `State declarations must have generic type arguments directly passed to them.`,\n\t\t\tnoTypeArgumentOrAnnotation: `State declarations must have generic type arguments directly passed to them, or a top-level type annotation.`,\n\t\t},\n\t\tschema: [\n\t\t\t{\n\t\t\t\ttype: `object`,\n\t\t\t\tproperties: {\n\t\t\t\t\tpermitAnnotation: {\n\t\t\t\t\t\ttype: `boolean`,\n\t\t\t\t\t\tdefault: false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t],\n\t},\n\tdefaultOptions: [\n\t\t{\n\t\t\tpermitAnnotation: false,\n\t\t},\n\t],\n\tcreate(context) {\n\t\tconst options = context.options[0]\n\t\tconst permitAnnotation = options?.permitAnnotation ?? false\n\n\t\treturn {\n\t\t\tCallExpression(node) {\n\t\t\t\tconst callee = node.callee\n\n\t\t\t\tswitch (callee.type) {\n\t\t\t\t\tcase `Identifier`:\n\t\t\t\t\t\tif (STATE_FUNCTIONS.includes(callee.name) === false) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase `MemberExpression`:\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(callee.property.type === `Identifier` &&\n\t\t\t\t\t\t\t\tSTATE_FUNCTIONS.includes(callee.property.name)) === false\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Check for the *required* generic type argument first\n\t\t\t\tif (node.typeArguments) {\n\t\t\t\t\treturn // Generic type argument is present, no error\n\t\t\t\t}\n\n\t\t\t\t// If generic arguments are missing, check if the top-level annotation exception is enabled AND present\n\t\t\t\tif (permitAnnotation) {\n\t\t\t\t\tlet hasAnnotation = false\n\t\t\t\t\t// Check if the CallExpression is the initializer of a variable declarator\n\t\t\t\t\tconst parent = node.parent\n\t\t\t\t\tif (\n\t\t\t\t\t\tparent?.type === AST_NODE_TYPES.VariableDeclarator &&\n\t\t\t\t\t\tparent.init === node\n\t\t\t\t\t) {\n\t\t\t\t\t\t// Check if the VariableDeclarator has an id with a TypeAnnotation\n\t\t\t\t\t\tconst declaratorId = parent.id\n\t\t\t\t\t\tif (declaratorId.type === AST_NODE_TYPES.Identifier) {\n\t\t\t\t\t\t\t// Check for 'const myAtom: AtomToken<string> = ...'\n\t\t\t\t\t\t\thasAnnotation = Boolean(declaratorId.typeAnnotation)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (hasAnnotation) {\n\t\t\t\t\t\treturn // Exception met: type annotation is on the variable declaration\n\t\t\t\t\t}\n\t\t\t\t\tcontext.report({\n\t\t\t\t\t\tnode,\n\t\t\t\t\t\tmessageId: `noTypeArgumentOrAnnotation`,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode,\n\t\t\t\t\tmessageId: `noTypeArgument`,\n\t\t\t\t})\n\t\t\t},\n\t\t}\n\t},\n})\n","export * from \"./explicit-state-types\"\n","import type { ESLint } from \"eslint\"\n\nimport * as Rules from \"./rules\"\n\nexport { Rules }\n\nconst plugin: ESLint.Plugin = {\n\trules: {\n\t\t\"explicit-state-types\": Rules.explicitStateTypes as any,\n\t},\n} satisfies ESLint.Plugin\n\nexport default plugin\n"],"mappings":";;;;AAIA,MAAM,aAAa,YAAY,aAC7B,SAAS,0CAA0C,OACpD;AAED,MAAM,kBAAkB;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;AAQD,MAAaA,qBAKT,WAAW;CACd,MAAM;CACN,MAAM;EACL,MAAM;EACN,MAAM,EACL,aAAa,+EACb;EACD,UAAU;GACT,gBAAgB;GAChB,4BAA4B;GAC5B;EACD,QAAQ,CACP;GACC,MAAM;GACN,YAAY,EACX,kBAAkB;IACjB,MAAM;IACN,SAAS;IACT,EACD;GACD,sBAAsB;GACtB,CACD;EACD;CACD,gBAAgB,CACf,EACC,kBAAkB,OAClB,CACD;CACD,OAAO,SAAS;EAEf,MAAM,mBADU,QAAQ,QAAQ,IACE,oBAAoB;AAEtD,SAAO,EACN,eAAe,MAAM;GACpB,MAAM,SAAS,KAAK;AAEpB,WAAQ,OAAO,MAAf;IACC,KAAK;AACJ,SAAI,gBAAgB,SAAS,OAAO,KAAK,KAAK,MAC7C;AAED;IACD,KAAK;AACJ,UACE,OAAO,SAAS,SAAS,gBACzB,gBAAgB,SAAS,OAAO,SAAS,KAAK,MAAM,MAErD;AAED;IACD,QACC;;AAIF,OAAI,KAAK,cACR;AAID,OAAI,kBAAkB;IACrB,IAAI,gBAAgB;IAEpB,MAAM,SAAS,KAAK;AACpB,QACC,QAAQ,SAAS,eAAe,sBAChC,OAAO,SAAS,MACf;KAED,MAAM,eAAe,OAAO;AAC5B,SAAI,aAAa,SAAS,eAAe,WAExC,iBAAgB,QAAQ,aAAa,eAAe;;AAGtD,QAAI,cACH;AAED,YAAQ,OAAO;KACd;KACA,WAAW;KACX,CAAC;AACF;;AAGD,WAAQ,OAAO;IACd;IACA,WAAW;IACX,CAAC;KAEH;;CAEF,CAAC;;;;;;;;AEnHF,MAAMC,SAAwB,EAC7B,OAAO,EACN,wBAAwBC,oBACxB,EACD;AAED,4BAAe"}
1
+ {"version":3,"file":"index.js","names":["createRule","exactCatchTypes: ESLintUtils.RuleModule<\n\t`extraneousErrorTypes` | `invalidCatchProperty` | `missingCatchProperty`,\n\t[],\n\tunknown,\n\tESLintUtils.RuleListener\n>","functionName: string | null","typeArguments: TSESTree.TSTypeParameterInstantiation | undefined","catchProperty: TSESTree.Property | undefined","acceptableErrorSymbols: TsSymbol[]","instanceType: Type | undefined","explicitStateTypes: ESLintUtils.RuleModule<\n\t`noTypeArgument` | `noTypeArgumentOrAnnotation`,\n\tOptions,\n\tunknown,\n\tESLintUtils.RuleListener\n>","plugin: ESLint.Plugin","Rules.exactCatchTypes","Rules.explicitStateTypes"],"sources":["../../src/eslint-plugin/rules/exact-catch-types.ts","../../src/eslint-plugin/rules/explicit-state-types.ts","../../src/eslint-plugin/rules/index.ts","../../src/eslint-plugin/index.ts"],"sourcesContent":["import type { TSESTree } from \"@typescript-eslint/utils\"\nimport { AST_NODE_TYPES, ESLintUtils } from \"@typescript-eslint/utils\"\nimport type {\n\tInterfaceType,\n\tSymbol as TsSymbol,\n\tType,\n\tTypeNode,\n} from \"typescript\"\n\nconst createRule = ESLintUtils.RuleCreator(\n\t(name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`,\n)\n\nconst STATE_FUNCTIONS_WITH_CATCH = [\n\t`atom`,\n\t`atomFamily`,\n\t`selector`,\n\t`selectorFamily`,\n]\nconst STANDALONE_FUNCTIONS = [`atom`, `selector`]\n\nexport const exactCatchTypes: ESLintUtils.RuleModule<\n\t`extraneousErrorTypes` | `invalidCatchProperty` | `missingCatchProperty`,\n\t[],\n\tunknown,\n\tESLintUtils.RuleListener\n> = createRule({\n\tname: `catch-constructor-type`,\n\tmeta: {\n\t\ttype: `problem`,\n\t\tdocs: {\n\t\t\tdescription: `Ensures that when an error type (E) is provided to an atom, the 'catch' property is set and all constructors in it are assignable to E.`,\n\t\t},\n\t\tmessages: {\n\t\t\tmissingCatchProperty:\n\t\t\t\t`This {{functionName}} was provided the error type \\`{{errorTypeName}}\\` ` +\n\t\t\t\t`but the required 'catch' property is missing from its options. ` +\n\t\t\t\t`Either remove \\`{{errorTypeName}}\\`, or add \\`catch: [{{errorTypeName}}]\\` to the options object.`,\n\t\t\tinvalidCatchProperty:\n\t\t\t\t`This {{functionName}} was provided a catch array containing the class \\`{{constructorName}}\\`. ` +\n\t\t\t\t`However, that class is not represented in the {{functionName}}'s error type, \\`{{errorTypeName}}\\`. ` +\n\t\t\t\t`As a result, it might catch errors that the {{functionName}} is not designed to handle. ` +\n\t\t\t\t`Either include \\`{{constructorName}}\\` in the {{functionName}}'s error type, or remove it from the 'catch' array.`,\n\t\t\textraneousErrorTypes:\n\t\t\t\t`This {{functionName}} was provided an error type including the class {{errorTypeName}}, ` +\n\t\t\t\t`but its 'catch' property doesn't include a constructor for that class. ` +\n\t\t\t\t`Either include a constructor for {{errorTypeName}} in the 'catch' array, or remove {{errorTypeName}} from the error type.`,\n\t\t},\n\t\tschema: [],\n\t},\n\tdefaultOptions: [],\n\tcreate(context) {\n\t\tconst parserServices = ESLintUtils.getParserServices(context)\n\t\tconst checker = parserServices.program.getTypeChecker()\n\n\t\treturn {\n\t\t\tCallExpression(node) {\n\t\t\t\tconst {\n\t\t\t\t\tcallee,\n\t\t\t\t\ttypeArguments: directTypeArguments,\n\t\t\t\t\targuments: callArguments,\n\t\t\t\t} = node\n\n\t\t\t\t// Check if the function call is one of the targeted state functions\n\t\t\t\tlet functionName: string | null = null\n\t\t\t\tif (callee.type === AST_NODE_TYPES.Identifier) {\n\t\t\t\t\tif (STATE_FUNCTIONS_WITH_CATCH.includes(callee.name)) {\n\t\t\t\t\t\tfunctionName = callee.name\n\t\t\t\t\t}\n\t\t\t\t} else if (callee.type === AST_NODE_TYPES.MemberExpression) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcallee.property.type === AST_NODE_TYPES.Identifier &&\n\t\t\t\t\t\tSTATE_FUNCTIONS_WITH_CATCH.includes(callee.property.name)\n\t\t\t\t\t) {\n\t\t\t\t\t\tfunctionName = callee.property.name\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!functionName) return\n\n\t\t\t\t// Where do the type arguments come from?\n\t\t\t\tlet typeArguments: TSESTree.TSTypeParameterInstantiation | undefined\n\t\t\t\tif (directTypeArguments) {\n\t\t\t\t\ttypeArguments = directTypeArguments\n\t\t\t\t} else {\n\t\t\t\t\tconst parent = node.parent\n\t\t\t\t\tif (\n\t\t\t\t\t\tparent?.type === AST_NODE_TYPES.VariableDeclarator &&\n\t\t\t\t\t\tparent.init === node\n\t\t\t\t\t) {\n\t\t\t\t\t\t// Check if the VariableDeclarator has an id with a TypeAnnotation\n\t\t\t\t\t\tconst declaratorId = parent.id\n\t\t\t\t\t\tif (declaratorId.type === AST_NODE_TYPES.Identifier) {\n\t\t\t\t\t\t\t// Check for 'const myAtom: AtomToken<string> = ...'\n\t\t\t\t\t\t\tconst typeAnnotation = declaratorId.typeAnnotation?.typeAnnotation\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\ttypeAnnotation &&\n\t\t\t\t\t\t\t\t`typeArguments` in typeAnnotation &&\n\t\t\t\t\t\t\t\ttypeAnnotation.typeArguments\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\ttypeArguments = typeAnnotation.typeArguments\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst optionsObject = callArguments[0]\n\n\t\t\t\tif (optionsObject?.type !== AST_NODE_TYPES.ObjectExpression) return\n\n\t\t\t\tconst isStandalone = STANDALONE_FUNCTIONS.includes(functionName)\n\n\t\t\t\tconst errorTypeNode = typeArguments\n\t\t\t\t\t? isStandalone\n\t\t\t\t\t\t? typeArguments.params[1]\n\t\t\t\t\t\t: typeArguments.params[2]\n\t\t\t\t\t: undefined\n\n\t\t\t\tlet catchProperty: TSESTree.Property | undefined\n\t\t\t\toptionsObject.properties.forEach((property) => {\n\t\t\t\t\tif (property.type === AST_NODE_TYPES.Property) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(property.key.type === AST_NODE_TYPES.Identifier &&\n\t\t\t\t\t\t\t\tproperty.key.name === `catch`) ||\n\t\t\t\t\t\t\t(property.key.type === AST_NODE_TYPES.Literal &&\n\t\t\t\t\t\t\t\tproperty.key.value === `catch`)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcatchProperty = property\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tif (!errorTypeNode) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst typeNode = parserServices.esTreeNodeToTSNodeMap.get(\n\t\t\t\t\terrorTypeNode,\n\t\t\t\t) as TypeNode\n\t\t\t\t// Get the TypeScript Type object for E\n\t\t\t\tconst errorTypeTs = checker.getTypeFromTypeNode(typeNode)\n\t\t\t\tconst errorTypeName = checker.typeToString(errorTypeTs)\n\n\t\t\t\tif (!catchProperty) {\n\t\t\t\t\tcontext.report({\n\t\t\t\t\t\tnode: optionsObject,\n\t\t\t\t\t\tmessageId: `missingCatchProperty`,\n\t\t\t\t\t\tdata: { functionName, errorTypeName },\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// --- New Validation: Check Constructor Types ---\n\t\t\t\tconst catchArray = catchProperty.value\n\t\t\t\tif (catchArray.type !== AST_NODE_TYPES.ArrayExpression) {\n\t\t\t\t\t// We only check array literals (e.g., [Ctor1, Ctor2])\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// 3. Collect all acceptable nominal symbols from E\n\t\t\t\tconst acceptableErrorSymbols: TsSymbol[] = []\n\n\t\t\t\t// Check if E is a Union Type\n\t\t\t\tif (errorTypeTs.isUnion()) {\n\t\t\t\t\t// Add the symbol of every member of the union (e.g., Symbol(SpecialError), Symbol(FancyError))\n\t\t\t\t\tfor (const memberType of errorTypeTs.types) {\n\t\t\t\t\t\tconst symbol = memberType.getSymbol()\n\t\t\t\t\t\tif (symbol) {\n\t\t\t\t\t\t\tacceptableErrorSymbols.push(symbol)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// E is a single type, add its symbol\n\t\t\t\t\tconst symbol = errorTypeTs.getSymbol()\n\t\t\t\t\tif (symbol) {\n\t\t\t\t\t\tacceptableErrorSymbols.push(symbol)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (catchArray.elements.length === 0) {\n\t\t\t\t\tcontext.report({\n\t\t\t\t\t\tnode: catchProperty,\n\t\t\t\t\t\tmessageId: `missingCatchProperty`,\n\t\t\t\t\t\tdata: { functionName, errorTypeName },\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst errorSymbolsToRepresent = new Set(acceptableErrorSymbols)\n\n\t\t\t\t// Iterate over each constructor reference in the 'catch' array\n\t\t\t\tfor (const element of catchArray.elements) {\n\t\t\t\t\tif (!element || element.type !== AST_NODE_TYPES.Identifier) {\n\t\t\t\t\t\t// Only check simple identifier references (e.g., [ClientError])\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\t// Get the type of the constructor identifier (e.g., the Type of 'Error')\n\t\t\t\t\tconst constructorTsNode =\n\t\t\t\t\t\tparserServices.esTreeNodeToTSNodeMap.get(element)\n\t\t\t\t\tconst constructorType = checker.getTypeAtLocation(constructorTsNode)\n\t\t\t\t\tconst constructorName = element.name\n\n\t\t\t\t\t// console.log(`constructorName`, constructorName)\n\n\t\t\t\t\t// Extract the instance type from the constructor type.\n\t\t\t\t\t// e.g., turn 'typeof ClientError' into 'ClientError'\n\t\t\t\t\tlet instanceType: Type | undefined\n\t\t\t\t\tif (\n\t\t\t\t\t\t(constructorType as InterfaceType).getConstructSignatures().length >\n\t\t\t\t\t\t0\n\t\t\t\t\t) {\n\t\t\t\t\t\t// Get the return type of the constructor signature\n\t\t\t\t\t\tconst signature = (\n\t\t\t\t\t\t\tconstructorType as InterfaceType\n\t\t\t\t\t\t).getConstructSignatures()[0]\n\t\t\t\t\t\tinstanceType = signature.getReturnType()\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we couldn't get the instance type, skip the check\n\n\t\t\t\t\tconst constructorInstanceSymbol = instanceType?.getSymbol()\n\t\t\t\t\tif (!constructorInstanceSymbol) continue\n\n\t\t\t\t\t// Check for symbol identity\n\t\t\t\t\tif (acceptableErrorSymbols.includes(constructorInstanceSymbol)) {\n\t\t\t\t\t\terrorSymbolsToRepresent.delete(constructorInstanceSymbol)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode: element,\n\t\t\t\t\t\t\tmessageId: `invalidCatchProperty`,\n\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\tfunctionName,\n\t\t\t\t\t\t\t\tconstructorName: constructorName,\n\t\t\t\t\t\t\t\terrorTypeName: errorTypeName,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (const errorSymbol of errorSymbolsToRepresent) {\n\t\t\t\t\tcontext.report({\n\t\t\t\t\t\tnode: catchProperty,\n\t\t\t\t\t\tmessageId: `extraneousErrorTypes`,\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\terrorTypeName: checker.symbolToString(errorSymbol),\n\t\t\t\t\t\t\tfunctionName,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t},\n})\n","/* eslint-disable @typescript-eslint/switch-exhaustiveness-check */\nimport { AST_NODE_TYPES, ESLintUtils } from \"@typescript-eslint/utils\"\n\nconst createRule = ESLintUtils.RuleCreator(\n\t(name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`,\n)\n\nconst STATE_FUNCTIONS = [\n\t`atom`,\n\t`atomFamily`,\n\t`mutableAtom`,\n\t`mutableAtomFamily`,\n\t`selector`,\n\t`selectorFamily`,\n]\n\ntype Options = [\n\t{\n\t\tpermitAnnotation?: boolean\n\t},\n]\n\nexport const explicitStateTypes: ESLintUtils.RuleModule<\n\t`noTypeArgument` | `noTypeArgumentOrAnnotation`,\n\tOptions,\n\tunknown,\n\tESLintUtils.RuleListener\n> = createRule({\n\tname: `explicit-state-types`,\n\tmeta: {\n\t\ttype: `problem`,\n\t\tdocs: {\n\t\t\tdescription: `State declarations must have generic type arguments directly passed to them`,\n\t\t},\n\t\tmessages: {\n\t\t\tnoTypeArgument: `State declarations must have generic type arguments directly passed to them.`,\n\t\t\tnoTypeArgumentOrAnnotation: `State declarations must have generic type arguments directly passed to them, or a top-level type annotation.`,\n\t\t},\n\t\tschema: [\n\t\t\t{\n\t\t\t\ttype: `object`,\n\t\t\t\tproperties: {\n\t\t\t\t\tpermitAnnotation: {\n\t\t\t\t\t\ttype: `boolean`,\n\t\t\t\t\t\tdefault: false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t],\n\t},\n\tdefaultOptions: [\n\t\t{\n\t\t\tpermitAnnotation: false,\n\t\t},\n\t],\n\tcreate(context) {\n\t\tconst options = context.options[0]\n\t\tconst permitAnnotation = options?.permitAnnotation ?? false\n\n\t\treturn {\n\t\t\tCallExpression(node) {\n\t\t\t\tconst callee = node.callee\n\n\t\t\t\tswitch (callee.type) {\n\t\t\t\t\tcase `Identifier`:\n\t\t\t\t\t\tif (STATE_FUNCTIONS.includes(callee.name) === false) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase `MemberExpression`:\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(callee.property.type === `Identifier` &&\n\t\t\t\t\t\t\t\tSTATE_FUNCTIONS.includes(callee.property.name)) === false\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Check for the *required* generic type argument first\n\t\t\t\tif (node.typeArguments) {\n\t\t\t\t\treturn // Generic type argument is present, no error\n\t\t\t\t}\n\n\t\t\t\t// If generic arguments are missing, check if the top-level annotation exception is enabled AND present\n\t\t\t\tif (permitAnnotation) {\n\t\t\t\t\tlet hasAnnotation = false\n\t\t\t\t\t// Check if the CallExpression is the initializer of a variable declarator\n\t\t\t\t\tconst parent = node.parent\n\t\t\t\t\tif (\n\t\t\t\t\t\tparent?.type === AST_NODE_TYPES.VariableDeclarator &&\n\t\t\t\t\t\tparent.init === node\n\t\t\t\t\t) {\n\t\t\t\t\t\t// Check if the VariableDeclarator has an id with a TypeAnnotation\n\t\t\t\t\t\tconst declaratorId = parent.id\n\t\t\t\t\t\tif (declaratorId.type === AST_NODE_TYPES.Identifier) {\n\t\t\t\t\t\t\t// Check for 'const myAtom: AtomToken<string> = ...'\n\t\t\t\t\t\t\thasAnnotation = Boolean(declaratorId.typeAnnotation)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (hasAnnotation) {\n\t\t\t\t\t\treturn // Exception met: type annotation is on the variable declaration\n\t\t\t\t\t}\n\t\t\t\t\tcontext.report({\n\t\t\t\t\t\tnode,\n\t\t\t\t\t\tmessageId: `noTypeArgumentOrAnnotation`,\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode,\n\t\t\t\t\tmessageId: `noTypeArgument`,\n\t\t\t\t})\n\t\t\t},\n\t\t}\n\t},\n})\n","export * from \"./exact-catch-types\"\nexport * from \"./explicit-state-types\"\n","import type { ESLint } from \"eslint\"\n\nimport * as Rules from \"./rules\"\n\nexport { Rules }\n\nconst plugin: ESLint.Plugin = {\n\trules: {\n\t\t\"exact-catch-types\": Rules.exactCatchTypes as any,\n\t\t\"explicit-state-types\": Rules.explicitStateTypes as any,\n\t},\n} satisfies ESLint.Plugin\n\nexport default plugin\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atom.io",
3
- "version": "0.44.9",
3
+ "version": "0.44.10",
4
4
  "description": "Composable and testable reactive data library.",
5
5
  "homepage": "https://atom.io.fyi",
6
6
  "sideEffects": false,
@@ -72,7 +72,7 @@
72
72
  "@typescript-eslint/parser": "8.47.0",
73
73
  "@typescript-eslint/rule-tester": "8.47.0",
74
74
  "@typescript-eslint/utils": "8.47.0",
75
- "@typescript/native-preview": "7.0.0-dev.20251122.1",
75
+ "@typescript/native-preview": "7.0.0-dev.20251123.1",
76
76
  "@vitest/coverage-v8": "4.0.13",
77
77
  "@vitest/ui": "4.0.13",
78
78
  "arktype": "2.1.27",
@@ -6,6 +6,7 @@ export { Rules }
6
6
 
7
7
  const plugin: ESLint.Plugin = {
8
8
  rules: {
9
+ "exact-catch-types": Rules.exactCatchTypes as any,
9
10
  "explicit-state-types": Rules.explicitStateTypes as any,
10
11
  },
11
12
  } satisfies ESLint.Plugin
@@ -0,0 +1,253 @@
1
+ import type { TSESTree } from "@typescript-eslint/utils"
2
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils"
3
+ import type {
4
+ InterfaceType,
5
+ Symbol as TsSymbol,
6
+ Type,
7
+ TypeNode,
8
+ } from "typescript"
9
+
10
+ const createRule = ESLintUtils.RuleCreator(
11
+ (name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`,
12
+ )
13
+
14
+ const STATE_FUNCTIONS_WITH_CATCH = [
15
+ `atom`,
16
+ `atomFamily`,
17
+ `selector`,
18
+ `selectorFamily`,
19
+ ]
20
+ const STANDALONE_FUNCTIONS = [`atom`, `selector`]
21
+
22
+ export const exactCatchTypes: ESLintUtils.RuleModule<
23
+ `extraneousErrorTypes` | `invalidCatchProperty` | `missingCatchProperty`,
24
+ [],
25
+ unknown,
26
+ ESLintUtils.RuleListener
27
+ > = createRule({
28
+ name: `catch-constructor-type`,
29
+ meta: {
30
+ type: `problem`,
31
+ docs: {
32
+ description: `Ensures that when an error type (E) is provided to an atom, the 'catch' property is set and all constructors in it are assignable to E.`,
33
+ },
34
+ messages: {
35
+ missingCatchProperty:
36
+ `This {{functionName}} was provided the error type \`{{errorTypeName}}\` ` +
37
+ `but the required 'catch' property is missing from its options. ` +
38
+ `Either remove \`{{errorTypeName}}\`, or add \`catch: [{{errorTypeName}}]\` to the options object.`,
39
+ invalidCatchProperty:
40
+ `This {{functionName}} was provided a catch array containing the class \`{{constructorName}}\`. ` +
41
+ `However, that class is not represented in the {{functionName}}'s error type, \`{{errorTypeName}}\`. ` +
42
+ `As a result, it might catch errors that the {{functionName}} is not designed to handle. ` +
43
+ `Either include \`{{constructorName}}\` in the {{functionName}}'s error type, or remove it from the 'catch' array.`,
44
+ extraneousErrorTypes:
45
+ `This {{functionName}} was provided an error type including the class {{errorTypeName}}, ` +
46
+ `but its 'catch' property doesn't include a constructor for that class. ` +
47
+ `Either include a constructor for {{errorTypeName}} in the 'catch' array, or remove {{errorTypeName}} from the error type.`,
48
+ },
49
+ schema: [],
50
+ },
51
+ defaultOptions: [],
52
+ create(context) {
53
+ const parserServices = ESLintUtils.getParserServices(context)
54
+ const checker = parserServices.program.getTypeChecker()
55
+
56
+ return {
57
+ CallExpression(node) {
58
+ const {
59
+ callee,
60
+ typeArguments: directTypeArguments,
61
+ arguments: callArguments,
62
+ } = node
63
+
64
+ // Check if the function call is one of the targeted state functions
65
+ let functionName: string | null = null
66
+ if (callee.type === AST_NODE_TYPES.Identifier) {
67
+ if (STATE_FUNCTIONS_WITH_CATCH.includes(callee.name)) {
68
+ functionName = callee.name
69
+ }
70
+ } else if (callee.type === AST_NODE_TYPES.MemberExpression) {
71
+ if (
72
+ callee.property.type === AST_NODE_TYPES.Identifier &&
73
+ STATE_FUNCTIONS_WITH_CATCH.includes(callee.property.name)
74
+ ) {
75
+ functionName = callee.property.name
76
+ }
77
+ }
78
+
79
+ if (!functionName) return
80
+
81
+ // Where do the type arguments come from?
82
+ let typeArguments: TSESTree.TSTypeParameterInstantiation | undefined
83
+ if (directTypeArguments) {
84
+ typeArguments = directTypeArguments
85
+ } else {
86
+ const parent = node.parent
87
+ if (
88
+ parent?.type === AST_NODE_TYPES.VariableDeclarator &&
89
+ parent.init === node
90
+ ) {
91
+ // Check if the VariableDeclarator has an id with a TypeAnnotation
92
+ const declaratorId = parent.id
93
+ if (declaratorId.type === AST_NODE_TYPES.Identifier) {
94
+ // Check for 'const myAtom: AtomToken<string> = ...'
95
+ const typeAnnotation = declaratorId.typeAnnotation?.typeAnnotation
96
+ if (
97
+ typeAnnotation &&
98
+ `typeArguments` in typeAnnotation &&
99
+ typeAnnotation.typeArguments
100
+ ) {
101
+ typeArguments = typeAnnotation.typeArguments
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ const optionsObject = callArguments[0]
108
+
109
+ if (optionsObject?.type !== AST_NODE_TYPES.ObjectExpression) return
110
+
111
+ const isStandalone = STANDALONE_FUNCTIONS.includes(functionName)
112
+
113
+ const errorTypeNode = typeArguments
114
+ ? isStandalone
115
+ ? typeArguments.params[1]
116
+ : typeArguments.params[2]
117
+ : undefined
118
+
119
+ let catchProperty: TSESTree.Property | undefined
120
+ optionsObject.properties.forEach((property) => {
121
+ if (property.type === AST_NODE_TYPES.Property) {
122
+ if (
123
+ (property.key.type === AST_NODE_TYPES.Identifier &&
124
+ property.key.name === `catch`) ||
125
+ (property.key.type === AST_NODE_TYPES.Literal &&
126
+ property.key.value === `catch`)
127
+ ) {
128
+ catchProperty = property
129
+ }
130
+ }
131
+ })
132
+
133
+ if (!errorTypeNode) {
134
+ return
135
+ }
136
+
137
+ const typeNode = parserServices.esTreeNodeToTSNodeMap.get(
138
+ errorTypeNode,
139
+ ) as TypeNode
140
+ // Get the TypeScript Type object for E
141
+ const errorTypeTs = checker.getTypeFromTypeNode(typeNode)
142
+ const errorTypeName = checker.typeToString(errorTypeTs)
143
+
144
+ if (!catchProperty) {
145
+ context.report({
146
+ node: optionsObject,
147
+ messageId: `missingCatchProperty`,
148
+ data: { functionName, errorTypeName },
149
+ })
150
+ return
151
+ }
152
+
153
+ // --- New Validation: Check Constructor Types ---
154
+ const catchArray = catchProperty.value
155
+ if (catchArray.type !== AST_NODE_TYPES.ArrayExpression) {
156
+ // We only check array literals (e.g., [Ctor1, Ctor2])
157
+ return
158
+ }
159
+
160
+ // 3. Collect all acceptable nominal symbols from E
161
+ const acceptableErrorSymbols: TsSymbol[] = []
162
+
163
+ // Check if E is a Union Type
164
+ if (errorTypeTs.isUnion()) {
165
+ // Add the symbol of every member of the union (e.g., Symbol(SpecialError), Symbol(FancyError))
166
+ for (const memberType of errorTypeTs.types) {
167
+ const symbol = memberType.getSymbol()
168
+ if (symbol) {
169
+ acceptableErrorSymbols.push(symbol)
170
+ }
171
+ }
172
+ } else {
173
+ // E is a single type, add its symbol
174
+ const symbol = errorTypeTs.getSymbol()
175
+ if (symbol) {
176
+ acceptableErrorSymbols.push(symbol)
177
+ }
178
+ }
179
+
180
+ if (catchArray.elements.length === 0) {
181
+ context.report({
182
+ node: catchProperty,
183
+ messageId: `missingCatchProperty`,
184
+ data: { functionName, errorTypeName },
185
+ })
186
+ return
187
+ }
188
+ const errorSymbolsToRepresent = new Set(acceptableErrorSymbols)
189
+
190
+ // Iterate over each constructor reference in the 'catch' array
191
+ for (const element of catchArray.elements) {
192
+ if (!element || element.type !== AST_NODE_TYPES.Identifier) {
193
+ // Only check simple identifier references (e.g., [ClientError])
194
+ continue
195
+ }
196
+
197
+ // Get the type of the constructor identifier (e.g., the Type of 'Error')
198
+ const constructorTsNode =
199
+ parserServices.esTreeNodeToTSNodeMap.get(element)
200
+ const constructorType = checker.getTypeAtLocation(constructorTsNode)
201
+ const constructorName = element.name
202
+
203
+ // console.log(`constructorName`, constructorName)
204
+
205
+ // Extract the instance type from the constructor type.
206
+ // e.g., turn 'typeof ClientError' into 'ClientError'
207
+ let instanceType: Type | undefined
208
+ if (
209
+ (constructorType as InterfaceType).getConstructSignatures().length >
210
+ 0
211
+ ) {
212
+ // Get the return type of the constructor signature
213
+ const signature = (
214
+ constructorType as InterfaceType
215
+ ).getConstructSignatures()[0]
216
+ instanceType = signature.getReturnType()
217
+ }
218
+
219
+ // If we couldn't get the instance type, skip the check
220
+
221
+ const constructorInstanceSymbol = instanceType?.getSymbol()
222
+ if (!constructorInstanceSymbol) continue
223
+
224
+ // Check for symbol identity
225
+ if (acceptableErrorSymbols.includes(constructorInstanceSymbol)) {
226
+ errorSymbolsToRepresent.delete(constructorInstanceSymbol)
227
+ } else {
228
+ context.report({
229
+ node: element,
230
+ messageId: `invalidCatchProperty`,
231
+ data: {
232
+ functionName,
233
+ constructorName: constructorName,
234
+ errorTypeName: errorTypeName,
235
+ },
236
+ })
237
+ }
238
+ }
239
+
240
+ for (const errorSymbol of errorSymbolsToRepresent) {
241
+ context.report({
242
+ node: catchProperty,
243
+ messageId: `extraneousErrorTypes`,
244
+ data: {
245
+ errorTypeName: checker.symbolToString(errorSymbol),
246
+ functionName,
247
+ },
248
+ })
249
+ }
250
+ },
251
+ }
252
+ },
253
+ })
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable @typescript-eslint/switch-exhaustiveness-check */
2
- import type { TSESTree } from "@typescript-eslint/utils"
3
2
  import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils"
4
3
 
5
4
  const createRule = ESLintUtils.RuleCreator(
@@ -1 +1,2 @@
1
+ export * from "./exact-catch-types"
1
2
  export * from "./explicit-state-types"