UNPKG

6.92 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const ts = __importStar(require("typescript"));
23const util_1 = require("../util");
24const tsutils_1 = require("tsutils");
25exports.default = (0, util_1.createRule)({
26 name: 'switch-exhaustiveness-check',
27 meta: {
28 type: 'suggestion',
29 docs: {
30 description: 'Exhaustiveness checking in switch with union type',
31 recommended: false,
32 suggestion: true,
33 requiresTypeChecking: true,
34 },
35 hasSuggestions: true,
36 schema: [],
37 messages: {
38 switchIsNotExhaustive: 'Switch is not exhaustive. Cases not matched: {{missingBranches}}',
39 addMissingCases: 'Add branches for missing cases.',
40 },
41 },
42 defaultOptions: [],
43 create(context) {
44 const sourceCode = context.getSourceCode();
45 const service = (0, util_1.getParserServices)(context);
46 const checker = service.program.getTypeChecker();
47 const compilerOptions = service.program.getCompilerOptions();
48 function getNodeType(node) {
49 const tsNode = service.esTreeNodeToTSNodeMap.get(node);
50 return (0, util_1.getConstrainedTypeAtLocation)(checker, tsNode);
51 }
52 function fixSwitch(fixer, node, missingBranchTypes, symbolName) {
53 var _a;
54 const lastCase = node.cases.length > 0 ? node.cases[node.cases.length - 1] : null;
55 const caseIndent = lastCase
56 ? ' '.repeat(lastCase.loc.start.column)
57 : // if there are no cases, use indentation of the switch statement
58 // and leave it to user to format it correctly
59 ' '.repeat(node.loc.start.column);
60 const missingCases = [];
61 for (const missingBranchType of missingBranchTypes) {
62 // While running this rule on checker.ts of TypeScript project
63 // the fix introduced a compiler error due to:
64 //
65 // type __String = (string & {
66 // __escapedIdentifier: void;
67 // }) | (void & {
68 // __escapedIdentifier: void;
69 // }) | InternalSymbolName;
70 //
71 // The following check fixes it.
72 if (missingBranchType.isIntersection()) {
73 continue;
74 }
75 const missingBranchName = (_a = missingBranchType.getSymbol()) === null || _a === void 0 ? void 0 : _a.escapedName;
76 let caseTest = checker.typeToString(missingBranchType);
77 if (symbolName &&
78 (missingBranchName || missingBranchName === '') &&
79 (0, util_1.requiresQuoting)(missingBranchName.toString(), compilerOptions.target)) {
80 caseTest = `${symbolName}['${missingBranchName}']`;
81 }
82 const errorMessage = `Not implemented yet: ${caseTest} case`;
83 missingCases.push(`case ${caseTest}: { throw new Error('${errorMessage}') }`);
84 }
85 const fixString = missingCases
86 .map(code => `${caseIndent}${code}`)
87 .join('\n');
88 if (lastCase) {
89 return fixer.insertTextAfter(lastCase, `\n${fixString}`);
90 }
91 // there were no existing cases
92 const openingBrace = sourceCode.getTokenAfter(node.discriminant, util_1.isOpeningBraceToken);
93 const closingBrace = sourceCode.getTokenAfter(node.discriminant, util_1.isClosingBraceToken);
94 return fixer.replaceTextRange([openingBrace.range[0], closingBrace.range[1]], ['{', fixString, `${caseIndent}}`].join('\n'));
95 }
96 function checkSwitchExhaustive(node) {
97 var _a;
98 const discriminantType = getNodeType(node.discriminant);
99 const symbolName = (_a = discriminantType.getSymbol()) === null || _a === void 0 ? void 0 : _a.escapedName;
100 if (discriminantType.isUnion()) {
101 const unionTypes = (0, tsutils_1.unionTypeParts)(discriminantType);
102 const caseTypes = new Set();
103 for (const switchCase of node.cases) {
104 if (switchCase.test === null) {
105 // Switch has 'default' branch - do nothing.
106 return;
107 }
108 caseTypes.add(getNodeType(switchCase.test));
109 }
110 const missingBranchTypes = unionTypes.filter(unionType => !caseTypes.has(unionType));
111 if (missingBranchTypes.length === 0) {
112 // All cases matched - do nothing.
113 return;
114 }
115 context.report({
116 node: node.discriminant,
117 messageId: 'switchIsNotExhaustive',
118 data: {
119 missingBranches: missingBranchTypes
120 .map(missingType => {
121 var _a;
122 return (0, tsutils_1.isTypeFlagSet)(missingType, ts.TypeFlags.ESSymbolLike)
123 ? `typeof ${(_a = missingType.getSymbol()) === null || _a === void 0 ? void 0 : _a.escapedName}`
124 : checker.typeToString(missingType);
125 })
126 .join(' | '),
127 },
128 suggest: [
129 {
130 messageId: 'addMissingCases',
131 fix(fixer) {
132 return fixSwitch(fixer, node, missingBranchTypes, symbolName === null || symbolName === void 0 ? void 0 : symbolName.toString());
133 },
134 },
135 ],
136 });
137 }
138 }
139 return {
140 SwitchStatement: checkSwitchExhaustive,
141 };
142 },
143});
144//# sourceMappingURL=switch-exhaustiveness-check.js.map
\No newline at end of file