noUnsafeFinallyRule.js
6.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Lint = require("../lint");
var ts = require("typescript");
var Rule = (function (_super) {
__extends(Rule, _super);
function Rule() {
_super.apply(this, arguments);
}
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new NoReturnInFinallyScopeAwareWalker(sourceFile, this.getOptions()));
};
Rule.metadata = {
ruleName: "no-unsafe-finally",
description: (_a = ["\n Disallows control flow statements, such as `return`, `continue`,\n `break` and `throws` in finally blocks."], _a.raw = ["\n Disallows control flow statements, such as \\`return\\`, \\`continue\\`,\n \\`break\\` and \\`throws\\` in finally blocks."], Lint.Utils.dedent(_a)),
descriptionDetails: "",
rationale: (_b = ["\n When used inside `finally` blocks, control flow statements,\n such as `return`, `continue`, `break` and `throws`\n override any other control flow statements in the same try/catch scope.\n This is confusing and unexpected behavior."], _b.raw = ["\n When used inside \\`finally\\` blocks, control flow statements,\n such as \\`return\\`, \\`continue\\`, \\`break\\` and \\`throws\\`\n override any other control flow statements in the same try/catch scope.\n This is confusing and unexpected behavior."], Lint.Utils.dedent(_b)),
optionsDescription: "Not configurable.",
options: null,
optionExamples: ["true"],
type: "functionality",
};
Rule.FAILURE_TYPE_BREAK = "break";
Rule.FAILURE_TYPE_CONTINUE = "continue";
Rule.FAILURE_TYPE_RETURN = "return";
Rule.FAILURE_TYPE_THROW = "throw";
Rule.FAILURE_STRING_FACTORY = function (name) { return (name + " statements in finally blocks are forbidden."); };
return Rule;
var _a, _b;
}(Lint.Rules.AbstractRule));
exports.Rule = Rule;
var NoReturnInFinallyScopeAwareWalker = (function (_super) {
__extends(NoReturnInFinallyScopeAwareWalker, _super);
function NoReturnInFinallyScopeAwareWalker() {
_super.apply(this, arguments);
}
NoReturnInFinallyScopeAwareWalker.prototype.visitBreakStatement = function (node) {
if (!this.isControlFlowWithinFinallyBlock(isBreakBoundary, node)) {
_super.prototype.visitBreakStatement.call(this, node);
return;
}
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_FACTORY(Rule.FAILURE_TYPE_BREAK)));
};
NoReturnInFinallyScopeAwareWalker.prototype.visitContinueStatement = function (node) {
if (!this.isControlFlowWithinFinallyBlock(isContinueBoundary, node)) {
_super.prototype.visitContinueStatement.call(this, node);
return;
}
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_FACTORY(Rule.FAILURE_TYPE_CONTINUE)));
};
NoReturnInFinallyScopeAwareWalker.prototype.visitLabeledStatement = function (node) {
this.getCurrentScope().labels.push(node.label.text);
_super.prototype.visitLabeledStatement.call(this, node);
};
NoReturnInFinallyScopeAwareWalker.prototype.visitReturnStatement = function (node) {
if (!this.isControlFlowWithinFinallyBlock(isReturnsOrThrowsBoundary)) {
_super.prototype.visitReturnStatement.call(this, node);
return;
}
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_FACTORY(Rule.FAILURE_TYPE_RETURN)));
};
NoReturnInFinallyScopeAwareWalker.prototype.visitThrowStatement = function (node) {
if (!this.isControlFlowWithinFinallyBlock(isReturnsOrThrowsBoundary)) {
_super.prototype.visitThrowStatement.call(this, node);
return;
}
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_FACTORY(Rule.FAILURE_TYPE_THROW)));
};
NoReturnInFinallyScopeAwareWalker.prototype.createScope = function (node) {
var isScopeBoundary = _super.prototype.isScopeBoundary.call(this, node);
return {
isBreakBoundary: isScopeBoundary || isLoopBlock(node) || isCaseBlock(node),
isContinueBoundary: isScopeBoundary || isLoopBlock(node),
isFinallyBlock: isFinallyBlock(node),
isReturnsThrowsBoundary: isScopeBoundary,
labels: [],
};
};
NoReturnInFinallyScopeAwareWalker.prototype.isScopeBoundary = function (node) {
return _super.prototype.isScopeBoundary.call(this, node) ||
isFinallyBlock(node) ||
isLoopBlock(node) ||
isCaseBlock(node);
};
NoReturnInFinallyScopeAwareWalker.prototype.isControlFlowWithinFinallyBlock = function (isControlFlowBoundary, node) {
var scopes = this.getAllScopes();
var currentScope = this.getCurrentScope();
var depth = this.getCurrentDepth();
while (currentScope) {
if (isControlFlowBoundary(currentScope, node)) {
return false;
}
if (currentScope.isFinallyBlock) {
return true;
}
currentScope = scopes[--depth];
}
};
return NoReturnInFinallyScopeAwareWalker;
}(Lint.ScopeAwareRuleWalker));
function isLoopBlock(node) {
var parent = node.parent;
return parent &&
node.kind === ts.SyntaxKind.Block &&
(parent.kind === ts.SyntaxKind.ForInStatement ||
parent.kind === ts.SyntaxKind.ForOfStatement ||
parent.kind === ts.SyntaxKind.ForStatement ||
parent.kind === ts.SyntaxKind.WhileStatement ||
parent.kind === ts.SyntaxKind.DoStatement);
}
function isCaseBlock(node) {
return node.kind === ts.SyntaxKind.CaseBlock;
}
function isFinallyBlock(node) {
var parent = node.parent;
return parent &&
node.kind === ts.SyntaxKind.Block &&
isTryStatement(parent) &&
parent.finallyBlock === node;
}
function isTryStatement(node) {
return node.kind === ts.SyntaxKind.TryStatement;
}
function isReturnsOrThrowsBoundary(scope) {
return scope.isReturnsThrowsBoundary;
}
function isContinueBoundary(scope, node) {
return node.label ? scope.labels.indexOf(node.label.text) >= 0 : scope.isContinueBoundary;
}
function isBreakBoundary(scope, node) {
return node.label ? scope.labels.indexOf(node.label.text) >= 0 : scope.isBreakBoundary;
}