control-flow.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.callExpressionAffectsControlFlow = exports.SignatureEffect = exports.getControlFlowEnd = exports.endsControlFlow = void 0;
  4. const ts = require("typescript");
  5. const node_1 = require("../typeguard/node");
  6. const util_1 = require("./util");
  7. function endsControlFlow(statement, checker) {
  8. return getControlFlowEnd(statement, checker).end;
  9. }
  10. exports.endsControlFlow = endsControlFlow;
  11. const defaultControlFlowEnd = { statements: [], end: false };
  12. function getControlFlowEnd(statement, checker) {
  13. return node_1.isBlockLike(statement) ? handleBlock(statement, checker) : getControlFlowEndWorker(statement, checker);
  14. }
  15. exports.getControlFlowEnd = getControlFlowEnd;
  16. function getControlFlowEndWorker(statement, checker) {
  17. switch (statement.kind) {
  18. case ts.SyntaxKind.ReturnStatement:
  19. case ts.SyntaxKind.ThrowStatement:
  20. case ts.SyntaxKind.ContinueStatement:
  21. case ts.SyntaxKind.BreakStatement:
  22. return { statements: [statement], end: true };
  23. case ts.SyntaxKind.Block:
  24. return handleBlock(statement, checker);
  25. case ts.SyntaxKind.ForStatement:
  26. case ts.SyntaxKind.WhileStatement:
  27. return handleForAndWhileStatement(statement, checker);
  28. case ts.SyntaxKind.ForOfStatement:
  29. case ts.SyntaxKind.ForInStatement:
  30. return handleForInOrOfStatement(statement, checker);
  31. case ts.SyntaxKind.DoStatement:
  32. return matchBreakOrContinue(getControlFlowEndWorker(statement.statement, checker), node_1.isBreakOrContinueStatement);
  33. case ts.SyntaxKind.IfStatement:
  34. return handleIfStatement(statement, checker);
  35. case ts.SyntaxKind.SwitchStatement:
  36. return matchBreakOrContinue(handleSwitchStatement(statement, checker), node_1.isBreakStatement);
  37. case ts.SyntaxKind.TryStatement:
  38. return handleTryStatement(statement, checker);
  39. case ts.SyntaxKind.LabeledStatement:
  40. return matchLabel(getControlFlowEndWorker(statement.statement, checker), statement.label);
  41. case ts.SyntaxKind.WithStatement:
  42. return getControlFlowEndWorker(statement.statement, checker);
  43. case ts.SyntaxKind.ExpressionStatement:
  44. if (checker === undefined)
  45. return defaultControlFlowEnd;
  46. return handleExpressionStatement(statement, checker);
  47. default:
  48. return defaultControlFlowEnd;
  49. }
  50. }
  51. function handleBlock(statement, checker) {
  52. const result = { statements: [], end: false };
  53. for (const s of statement.statements) {
  54. const current = getControlFlowEndWorker(s, checker);
  55. result.statements.push(...current.statements);
  56. if (current.end) {
  57. result.end = true;
  58. break;
  59. }
  60. }
  61. return result;
  62. }
  63. function handleForInOrOfStatement(statement, checker) {
  64. const end = matchBreakOrContinue(getControlFlowEndWorker(statement.statement, checker), node_1.isBreakOrContinueStatement);
  65. end.end = false; // loop body is guaranteed to be executed
  66. return end;
  67. }
  68. function handleForAndWhileStatement(statement, checker) {
  69. const constantCondition = statement.kind === ts.SyntaxKind.WhileStatement
  70. ? getConstantCondition(statement.expression)
  71. : statement.condition === undefined || getConstantCondition(statement.condition);
  72. if (constantCondition === false)
  73. return defaultControlFlowEnd; // loop body is never executed
  74. const end = matchBreakOrContinue(getControlFlowEndWorker(statement.statement, checker), node_1.isBreakOrContinueStatement);
  75. if (constantCondition === undefined)
  76. end.end = false; // can't be sure that loop body is executed at all
  77. return end;
  78. }
  79. /** Simply detects `true` and `false` in conditions. That matches TypeScript's behavior. */
  80. function getConstantCondition(node) {
  81. switch (node.kind) {
  82. case ts.SyntaxKind.TrueKeyword:
  83. return true;
  84. case ts.SyntaxKind.FalseKeyword:
  85. return false;
  86. default:
  87. return;
  88. }
  89. }
  90. function handleIfStatement(node, checker) {
  91. switch (getConstantCondition(node.expression)) {
  92. case true:
  93. // else branch is never executed
  94. return getControlFlowEndWorker(node.thenStatement, checker);
  95. case false:
  96. // then branch is never executed
  97. return node.elseStatement === undefined
  98. ? defaultControlFlowEnd
  99. : getControlFlowEndWorker(node.elseStatement, checker);
  100. }
  101. const then = getControlFlowEndWorker(node.thenStatement, checker);
  102. if (node.elseStatement === undefined)
  103. return {
  104. statements: then.statements,
  105. end: false,
  106. };
  107. const elze = getControlFlowEndWorker(node.elseStatement, checker);
  108. return {
  109. statements: [...then.statements, ...elze.statements],
  110. end: then.end && elze.end,
  111. };
  112. }
  113. function handleSwitchStatement(node, checker) {
  114. let hasDefault = false;
  115. const result = {
  116. statements: [],
  117. end: false,
  118. };
  119. for (const clause of node.caseBlock.clauses) {
  120. if (clause.kind === ts.SyntaxKind.DefaultClause)
  121. hasDefault = true;
  122. const current = handleBlock(clause, checker);
  123. result.end = current.end;
  124. result.statements.push(...current.statements);
  125. }
  126. result.end && (result.end = hasDefault || checker !== undefined && util_1.hasExhaustiveCaseClauses(node, checker));
  127. return result;
  128. }
  129. function handleTryStatement(node, checker) {
  130. let finallyResult;
  131. if (node.finallyBlock !== undefined) {
  132. finallyResult = handleBlock(node.finallyBlock, checker);
  133. // if 'finally' always ends control flow, we are not interested in any jump statements from 'try' or 'catch'
  134. if (finallyResult.end)
  135. return finallyResult;
  136. }
  137. const tryResult = handleBlock(node.tryBlock, checker);
  138. if (node.catchClause === undefined)
  139. return { statements: finallyResult.statements.concat(tryResult.statements), end: tryResult.end };
  140. const catchResult = handleBlock(node.catchClause.block, checker);
  141. return {
  142. statements: tryResult.statements
  143. // remove all throw statements and throwing function calls from the list of control flow statements inside tryBlock
  144. .filter((s) => s.kind !== ts.SyntaxKind.ThrowStatement && s.kind !== ts.SyntaxKind.ExpressionStatement)
  145. .concat(catchResult.statements, finallyResult === undefined ? [] : finallyResult.statements),
  146. end: tryResult.end && catchResult.end, // only ends control flow if try AND catch definitely end control flow
  147. };
  148. }
  149. /** Dotted name as TypeScript requires it for assertion signatures to affect control flow. */
  150. function isDottedNameWithExplicitTypeAnnotation(node, checker) {
  151. while (true) {
  152. switch (node.kind) {
  153. case ts.SyntaxKind.Identifier: {
  154. const symbol = checker.getExportSymbolOfSymbol(checker.getSymbolAtLocation(node));
  155. return isExplicitlyTypedSymbol(util_1.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) ? checker.getAliasedSymbol(symbol) : symbol, checker);
  156. }
  157. case ts.SyntaxKind.ThisKeyword:
  158. return isExplicitlyTypedThis(node);
  159. case ts.SyntaxKind.SuperKeyword:
  160. return true;
  161. case ts.SyntaxKind.PropertyAccessExpression:
  162. if (!isExplicitlyTypedSymbol(checker.getSymbolAtLocation(node), checker))
  163. return false;
  164. // falls through
  165. case ts.SyntaxKind.ParenthesizedExpression:
  166. node = node.expression;
  167. continue;
  168. default:
  169. return false;
  170. }
  171. }
  172. }
  173. function isExplicitlyTypedSymbol(symbol, checker) {
  174. if (symbol === undefined)
  175. return false;
  176. if (util_1.isSymbolFlagSet(symbol, ts.SymbolFlags.Function | ts.SymbolFlags.Method | ts.SymbolFlags.Class | ts.SymbolFlags.ValueModule))
  177. return true;
  178. if (!util_1.isSymbolFlagSet(symbol, ts.SymbolFlags.Variable | ts.SymbolFlags.Property))
  179. return false;
  180. if (symbol.valueDeclaration === undefined)
  181. return false;
  182. if (declarationHasExplicitTypeAnnotation(symbol.valueDeclaration))
  183. return true;
  184. return node_1.isVariableDeclaration(symbol.valueDeclaration) &&
  185. symbol.valueDeclaration.parent.parent.kind === ts.SyntaxKind.ForOfStatement &&
  186. isDottedNameWithExplicitTypeAnnotation(symbol.valueDeclaration.parent.parent.expression, checker);
  187. }
  188. function declarationHasExplicitTypeAnnotation(node) {
  189. if (ts.isJSDocPropertyLikeTag(node))
  190. return node.typeExpression !== undefined;
  191. return (node_1.isVariableDeclaration(node) ||
  192. node_1.isParameterDeclaration(node) ||
  193. node_1.isPropertyDeclaration(node) ||
  194. node_1.isPropertySignature(node)) && (util_1.isNodeFlagSet(node, ts.NodeFlags.JavaScriptFile)
  195. ? ts.getJSDocType(node)
  196. : node.type) !== undefined;
  197. }
  198. function isExplicitlyTypedThis(node) {
  199. var _a;
  200. do {
  201. node = node.parent;
  202. if (node_1.isDecorator(node)) {
  203. // `this` in decorators always resolves outside of the containing class
  204. if (node.parent.kind === ts.SyntaxKind.Parameter && node_1.isClassLikeDeclaration(node.parent.parent.parent)) {
  205. node = node.parent.parent.parent.parent;
  206. }
  207. else if (node_1.isClassLikeDeclaration(node.parent.parent)) {
  208. node = node.parent.parent.parent;
  209. }
  210. else if (node_1.isClassLikeDeclaration(node.parent)) {
  211. node = node.parent.parent;
  212. }
  213. }
  214. } while (util_1.isFunctionScopeBoundary(node) !== 1 /* Function */ || node.kind === ts.SyntaxKind.ArrowFunction);
  215. return util_1.isFunctionWithBody(node) &&
  216. (util_1.isNodeFlagSet(node, ts.NodeFlags.JavaScriptFile)
  217. ? ((_a = ts.getJSDocThisTag(node)) === null || _a === void 0 ? void 0 : _a.typeExpression) !== undefined
  218. : node.parameters.length !== 0 && util_1.isThisParameter(node.parameters[0]) && node.parameters[0].type !== undefined) ||
  219. node_1.isClassLikeDeclaration(node.parent);
  220. }
  221. var SignatureEffect;
  222. (function (SignatureEffect) {
  223. SignatureEffect[SignatureEffect["Never"] = 1] = "Never";
  224. SignatureEffect[SignatureEffect["Asserts"] = 2] = "Asserts";
  225. })(SignatureEffect = exports.SignatureEffect || (exports.SignatureEffect = {}));
  226. /**
  227. * Dermines whether a top level CallExpression has a control flow effect according to TypeScript's rules.
  228. * This handles functions returning `never` and `asserts`.
  229. */
  230. function callExpressionAffectsControlFlow(node, checker) {
  231. var _a, _b, _c;
  232. if (!node_1.isExpressionStatement(node.parent) ||
  233. ts.isOptionalChain(node) ||
  234. !isDottedNameWithExplicitTypeAnnotation(node.expression, checker))
  235. return;
  236. const signature = checker.getResolvedSignature(node);
  237. if ((signature === null || signature === void 0 ? void 0 : signature.declaration) === undefined)
  238. return;
  239. const typeNode = ts.isJSDocSignature(signature.declaration)
  240. ? (_b = (_a = signature.declaration.type) === null || _a === void 0 ? void 0 : _a.typeExpression) === null || _b === void 0 ? void 0 : _b.type
  241. : (_c = signature.declaration.type) !== null && _c !== void 0 ? _c : (util_1.isNodeFlagSet(signature.declaration, ts.NodeFlags.JavaScriptFile)
  242. ? ts.getJSDocReturnType(signature.declaration)
  243. : undefined);
  244. if (typeNode === undefined)
  245. return;
  246. if (node_1.isTypePredicateNode(typeNode) && typeNode.assertsModifier !== undefined)
  247. return 2 /* Asserts */;
  248. return util_1.isTypeFlagSet(checker.getTypeFromTypeNode(typeNode), ts.TypeFlags.Never) ? 1 /* Never */ : undefined;
  249. }
  250. exports.callExpressionAffectsControlFlow = callExpressionAffectsControlFlow;
  251. function handleExpressionStatement(node, checker) {
  252. if (!node_1.isCallExpression(node.expression))
  253. return defaultControlFlowEnd;
  254. switch (callExpressionAffectsControlFlow(node.expression, checker)) {
  255. case 2 /* Asserts */:
  256. return { statements: [node], end: false };
  257. case 1 /* Never */:
  258. return { statements: [node], end: true };
  259. case undefined:
  260. return defaultControlFlowEnd;
  261. }
  262. }
  263. function matchBreakOrContinue(current, pred) {
  264. const result = {
  265. statements: [],
  266. end: current.end,
  267. };
  268. for (const statement of current.statements) {
  269. if (pred(statement) && statement.label === undefined) {
  270. result.end = false;
  271. continue;
  272. }
  273. result.statements.push(statement);
  274. }
  275. return result;
  276. }
  277. function matchLabel(current, label) {
  278. const result = {
  279. statements: [],
  280. end: current.end,
  281. };
  282. const labelText = label.text;
  283. for (const statement of current.statements) {
  284. switch (statement.kind) {
  285. case ts.SyntaxKind.BreakStatement:
  286. case ts.SyntaxKind.ContinueStatement:
  287. if (statement.label !== undefined && statement.label.text === labelText) {
  288. result.end = false;
  289. continue;
  290. }
  291. }
  292. result.statements.push(statement);
  293. }
  294. return result;
  295. }
  296. //# sourceMappingURL=control-flow.js.map