UNPKG

12.6 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || (function () {
3 var extendStatics = Object.setPrototypeOf ||
4 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
6 return function (d, b) {
7 extendStatics(d, b);
8 function __() { this.constructor = d; }
9 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
10 };
11})();
12Object.defineProperty(exports, "__esModule", { value: true });
13var ts = require("typescript");
14var Lint = require("tslint");
15var AstUtils_1 = require("./utils/AstUtils");
16var ErrorTolerantWalker_1 = require("./utils/ErrorTolerantWalker");
17var Scope_1 = require("./utils/Scope");
18var Utils_1 = require("./utils/Utils");
19var FAILURE_ANONYMOUS_LISTENER = 'A new instance of an anonymous method is passed as a JSX attribute: ';
20var FAILURE_DOUBLE_BIND = 'A function is having its \'this\' reference bound twice in the constructor: ';
21var FAILURE_UNBOUND_LISTENER = 'A class method is passed as a JSX attribute without having the \'this\' ' +
22 'reference bound in the constructor: ';
23var Rule = (function (_super) {
24 __extends(Rule, _super);
25 function Rule() {
26 return _super !== null && _super.apply(this, arguments) || this;
27 }
28 Rule.prototype.apply = function (sourceFile) {
29 if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
30 return this.applyWithWalker(new ReactThisBindingIssueRuleWalker(sourceFile, this.getOptions()));
31 }
32 else {
33 return [];
34 }
35 };
36 Rule.metadata = {
37 ruleName: 'react-this-binding-issue',
38 type: 'maintainability',
39 description: 'When using React components you must be careful to correctly bind the `this` reference ' +
40 'on any methods that you pass off to child components as callbacks.',
41 options: null,
42 optionsDescription: '',
43 typescriptOnly: true,
44 issueClass: 'Non-SDL',
45 issueType: 'Error',
46 severity: 'Critical',
47 level: 'Opportunity for Excellence',
48 group: 'Correctness'
49 };
50 return Rule;
51}(Lint.Rules.AbstractRule));
52exports.Rule = Rule;
53var ReactThisBindingIssueRuleWalker = (function (_super) {
54 __extends(ReactThisBindingIssueRuleWalker, _super);
55 function ReactThisBindingIssueRuleWalker(sourceFile, options) {
56 var _this = _super.call(this, sourceFile, options) || this;
57 _this.allowAnonymousListeners = false;
58 _this.boundListeners = [];
59 _this.declaredMethods = [];
60 _this.getOptions().forEach(function (opt) {
61 if (typeof (opt) === 'object') {
62 _this.allowAnonymousListeners = opt['allow-anonymous-listeners'] === true;
63 }
64 });
65 return _this;
66 }
67 ReactThisBindingIssueRuleWalker.prototype.visitClassDeclaration = function (node) {
68 var _this = this;
69 this.boundListeners = [];
70 this.declaredMethods = [];
71 AstUtils_1.AstUtils.getDeclaredMethodNames(node).forEach(function (methodName) {
72 _this.declaredMethods.push('this.' + methodName);
73 });
74 _super.prototype.visitClassDeclaration.call(this, node);
75 };
76 ReactThisBindingIssueRuleWalker.prototype.visitConstructorDeclaration = function (node) {
77 this.boundListeners = this.getSelfBoundListeners(node);
78 _super.prototype.visitConstructorDeclaration.call(this, node);
79 };
80 ReactThisBindingIssueRuleWalker.prototype.visitJsxElement = function (node) {
81 this.visitJsxOpeningElement(node.openingElement);
82 _super.prototype.visitJsxElement.call(this, node);
83 };
84 ReactThisBindingIssueRuleWalker.prototype.visitJsxSelfClosingElement = function (node) {
85 this.visitJsxOpeningElement(node);
86 _super.prototype.visitJsxSelfClosingElement.call(this, node);
87 };
88 ReactThisBindingIssueRuleWalker.prototype.visitMethodDeclaration = function (node) {
89 this.scope = new Scope_1.Scope(null);
90 _super.prototype.visitMethodDeclaration.call(this, node);
91 this.scope = null;
92 };
93 ReactThisBindingIssueRuleWalker.prototype.visitArrowFunction = function (node) {
94 if (this.scope != null) {
95 this.scope = new Scope_1.Scope(this.scope);
96 }
97 _super.prototype.visitArrowFunction.call(this, node);
98 if (this.scope != null) {
99 this.scope = this.scope.parent;
100 }
101 };
102 ReactThisBindingIssueRuleWalker.prototype.visitFunctionExpression = function (node) {
103 if (this.scope != null) {
104 this.scope = new Scope_1.Scope(this.scope);
105 }
106 _super.prototype.visitFunctionExpression.call(this, node);
107 if (this.scope != null) {
108 this.scope = this.scope.parent;
109 }
110 };
111 ReactThisBindingIssueRuleWalker.prototype.visitVariableDeclaration = function (node) {
112 if (this.scope != null) {
113 if (node.name.kind === ts.SyntaxKind.Identifier) {
114 var variableName = node.name.text;
115 if (this.isExpressionAnonymousFunction(node.initializer)) {
116 this.scope.addFunctionSymbol(variableName);
117 }
118 }
119 }
120 _super.prototype.visitVariableDeclaration.call(this, node);
121 };
122 ReactThisBindingIssueRuleWalker.prototype.visitJsxOpeningElement = function (node) {
123 var _this = this;
124 node.attributes.properties.forEach(function (attributeLikeElement) {
125 if (_this.isUnboundListener(attributeLikeElement)) {
126 var attribute = attributeLikeElement;
127 if (attribute.initializer.kind === ts.SyntaxKind.StringLiteral) {
128 return;
129 }
130 var jsxExpression = attribute.initializer;
131 var propAccess = jsxExpression.expression;
132 var listenerText = propAccess.getText();
133 if (_this.declaredMethods.indexOf(listenerText) > -1 && _this.boundListeners.indexOf(listenerText) === -1) {
134 var start = propAccess.getStart();
135 var widget = propAccess.getWidth();
136 var message = FAILURE_UNBOUND_LISTENER + listenerText;
137 _this.addFailureAt(start, widget, message);
138 }
139 }
140 else if (_this.isAttributeAnonymousFunction(attributeLikeElement)) {
141 var attribute = attributeLikeElement;
142 if (attribute.initializer.kind === ts.SyntaxKind.StringLiteral) {
143 return;
144 }
145 var jsxExpression = attribute.initializer;
146 var expression = jsxExpression.expression;
147 var start = expression.getStart();
148 var widget = expression.getWidth();
149 var message = FAILURE_ANONYMOUS_LISTENER + Utils_1.Utils.trimTo(expression.getText(), 30);
150 _this.addFailureAt(start, widget, message);
151 }
152 });
153 };
154 ReactThisBindingIssueRuleWalker.prototype.isAttributeAnonymousFunction = function (attributeLikeElement) {
155 if (this.allowAnonymousListeners) {
156 return false;
157 }
158 if (attributeLikeElement.kind === ts.SyntaxKind.JsxAttribute) {
159 var attribute = attributeLikeElement;
160 if (attribute.initializer != null && attribute.initializer.kind === ts.SyntaxKind.JsxExpression) {
161 var jsxExpression = attribute.initializer;
162 var expression = jsxExpression.expression;
163 return this.isExpressionAnonymousFunction(expression);
164 }
165 }
166 return false;
167 };
168 ReactThisBindingIssueRuleWalker.prototype.isExpressionAnonymousFunction = function (expression) {
169 if (expression == null) {
170 return false;
171 }
172 if (expression.kind === ts.SyntaxKind.ArrowFunction
173 || expression.kind === ts.SyntaxKind.FunctionExpression) {
174 return true;
175 }
176 if (expression.kind === ts.SyntaxKind.CallExpression) {
177 var callExpression = expression;
178 var functionName = AstUtils_1.AstUtils.getFunctionName(callExpression);
179 if (functionName === 'bind') {
180 return true;
181 }
182 }
183 if (expression.kind === ts.SyntaxKind.Identifier) {
184 var symbolText = expression.getText();
185 return this.scope.isFunctionSymbol(symbolText);
186 }
187 return false;
188 };
189 ReactThisBindingIssueRuleWalker.prototype.isUnboundListener = function (attributeLikeElement) {
190 if (attributeLikeElement.kind === ts.SyntaxKind.JsxAttribute) {
191 var attribute = attributeLikeElement;
192 if (attribute.initializer != null && attribute.initializer.kind === ts.SyntaxKind.JsxExpression) {
193 var jsxExpression = attribute.initializer;
194 if (jsxExpression.expression != null && jsxExpression.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
195 var propAccess = jsxExpression.expression;
196 if (propAccess.expression.getText() === 'this') {
197 var listenerText = propAccess.getText();
198 if (this.declaredMethods.indexOf(listenerText) > -1 && this.boundListeners.indexOf(listenerText) === -1) {
199 return true;
200 }
201 }
202 }
203 }
204 }
205 return false;
206 };
207 ReactThisBindingIssueRuleWalker.prototype.getSelfBoundListeners = function (node) {
208 var _this = this;
209 var result = [];
210 if (node.body != null && node.body.statements != null) {
211 node.body.statements.forEach(function (statement) {
212 if (statement.kind === ts.SyntaxKind.ExpressionStatement) {
213 var expressionStatement = statement;
214 var expression = expressionStatement.expression;
215 if (expression.kind === ts.SyntaxKind.BinaryExpression) {
216 var binaryExpression = expression;
217 var operator = binaryExpression.operatorToken;
218 if (operator.kind === ts.SyntaxKind.EqualsToken) {
219 if (binaryExpression.left.kind === ts.SyntaxKind.PropertyAccessExpression) {
220 var leftPropText = binaryExpression.left.getText();
221 if (binaryExpression.right.kind === ts.SyntaxKind.CallExpression) {
222 var callExpression = binaryExpression.right;
223 if (AstUtils_1.AstUtils.getFunctionName(callExpression) === 'bind'
224 && callExpression.arguments != null
225 && callExpression.arguments.length === 1
226 && callExpression.arguments[0].getText() === 'this') {
227 var rightPropText = AstUtils_1.AstUtils.getFunctionTarget(callExpression);
228 if (leftPropText === rightPropText) {
229 if (result.indexOf(rightPropText) === -1) {
230 result.push(rightPropText);
231 }
232 else {
233 var start = binaryExpression.getStart();
234 var width = binaryExpression.getWidth();
235 var msg = FAILURE_DOUBLE_BIND + binaryExpression.getText();
236 _this.addFailureAt(start, width, msg);
237 }
238 }
239 }
240 }
241 }
242 }
243 }
244 }
245 });
246 }
247 return result;
248 };
249 return ReactThisBindingIssueRuleWalker;
250}(ErrorTolerantWalker_1.ErrorTolerantWalker));
251//# sourceMappingURL=reactThisBindingIssueRule.js.map
\No newline at end of file