1 | "use strict";
|
2 | var __extends = (this && this.__extends) || (function () {
|
3 | var extendStatics = function (d, b) {
|
4 | extendStatics = Object.setPrototypeOf ||
|
5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
6 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
7 | return extendStatics(d, b);
|
8 | }
|
9 | return function (d, b) {
|
10 | extendStatics(d, b);
|
11 | function __() { this.constructor = d; }
|
12 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
13 | };
|
14 | })();
|
15 | Object.defineProperty(exports, "__esModule", { value: true });
|
16 | var ts = require("typescript");
|
17 | var Lint = require("tslint");
|
18 | var tsutils = require("tsutils");
|
19 | var Utils_1 = require("./utils/Utils");
|
20 | var TypeGuard_1 = require("./utils/TypeGuard");
|
21 | var PROPS_REGEX = 'props-interface-regex';
|
22 | var STATE_REGEX = 'state-interface-regex';
|
23 | var FAILURE_UNUSED_PROP = 'Unused React property defined in interface: ';
|
24 | var FAILURE_UNUSED_STATE = 'Unused React state defined in interface: ';
|
25 | var Rule = (function (_super) {
|
26 | __extends(Rule, _super);
|
27 | function Rule() {
|
28 | return _super !== null && _super.apply(this, arguments) || this;
|
29 | }
|
30 | Rule.prototype.apply = function (sourceFile) {
|
31 | if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
|
32 | return this.applyWithFunction(sourceFile, walk, this.parseOptions(this.getOptions()));
|
33 | }
|
34 | else {
|
35 | return [];
|
36 | }
|
37 | };
|
38 | Rule.prototype.parseOptions = function (options) {
|
39 | var _this = this;
|
40 | var parsed = {
|
41 | propsInterfaceRegex: /^Props$/,
|
42 | stateInterfaceRegex: /^State$/
|
43 | };
|
44 | options.ruleArguments.forEach(function (opt) {
|
45 | if (TypeGuard_1.isObject(opt)) {
|
46 | parsed.propsInterfaceRegex = _this.getOptionOrDefault(opt, PROPS_REGEX, parsed.propsInterfaceRegex);
|
47 | parsed.stateInterfaceRegex = _this.getOptionOrDefault(opt, STATE_REGEX, parsed.stateInterfaceRegex);
|
48 | }
|
49 | });
|
50 | return parsed;
|
51 | };
|
52 | Rule.prototype.getOptionOrDefault = function (option, key, defaultValue) {
|
53 | try {
|
54 | var value = option[key];
|
55 | if (value !== undefined && typeof value === 'string') {
|
56 | return new RegExp(value);
|
57 | }
|
58 | }
|
59 | catch (e) {
|
60 | console.error('Could not read ' + key + ' within react-unused-props-and-state-name configuration');
|
61 | }
|
62 | return defaultValue;
|
63 | };
|
64 | Rule.metadata = {
|
65 | ruleName: 'react-unused-props-and-state',
|
66 | type: 'maintainability',
|
67 | description: 'Remove unneeded properties defined in React Props and State interfaces',
|
68 | options: null,
|
69 | optionsDescription: '',
|
70 | typescriptOnly: true,
|
71 | issueClass: 'Non-SDL',
|
72 | issueType: 'Warning',
|
73 | severity: 'Low',
|
74 | level: 'Opportunity for Excellence',
|
75 | group: 'Correctness',
|
76 | commonWeaknessEnumeration: '398'
|
77 | };
|
78 | return Rule;
|
79 | }(Lint.Rules.AbstractRule));
|
80 | exports.Rule = Rule;
|
81 | function walk(ctx) {
|
82 | var propNames = [];
|
83 | var propNodes = {};
|
84 | var stateNames = [];
|
85 | var stateNodes = {};
|
86 | var classDeclarations = [];
|
87 | var arrowFunctions = [];
|
88 | var functionComponents = [];
|
89 | var propsAlias;
|
90 | var stateAlias;
|
91 | function getTypeElementData(node) {
|
92 | var result = {};
|
93 | node.members.forEach(function (typeElement) {
|
94 | if (typeElement.name !== undefined) {
|
95 | var text = typeElement.name.getText();
|
96 | if (text !== undefined) {
|
97 | result[text] = typeElement;
|
98 | }
|
99 | }
|
100 | });
|
101 | return result;
|
102 | }
|
103 | function getTypeLiteralData(node) {
|
104 | var result = {};
|
105 | node.members.forEach(function (typeElement) {
|
106 | if (typeElement.name !== undefined) {
|
107 | var text = typeElement.name.getText();
|
108 | if (text !== undefined) {
|
109 | result[text] = typeElement;
|
110 | }
|
111 | }
|
112 | });
|
113 | return result;
|
114 | }
|
115 | function getObjectBindingData(node) {
|
116 | var result = {};
|
117 | node.elements.forEach(function (element) {
|
118 | if (element.name !== undefined) {
|
119 | var text = element.name.getText();
|
120 | if (text !== undefined) {
|
121 | result[text] = element;
|
122 | }
|
123 | }
|
124 | });
|
125 | return result;
|
126 | }
|
127 | function isParentNodeSuperCall(node) {
|
128 | if (node.parent !== undefined && node.parent.kind === ts.SyntaxKind.CallExpression) {
|
129 | var call = node.parent;
|
130 | return call.expression.getText() === 'super';
|
131 | }
|
132 | return false;
|
133 | }
|
134 | function inspectPropUsageInObjectBinding(name) {
|
135 | var bindingElements = getObjectBindingData(name);
|
136 | var foundPropNames = Object.keys(bindingElements);
|
137 | for (var _i = 0, foundPropNames_1 = foundPropNames; _i < foundPropNames_1.length; _i++) {
|
138 | var propName = foundPropNames_1[_i];
|
139 | propNames = Utils_1.Utils.remove(propNames, propName);
|
140 | }
|
141 | }
|
142 | function lookForReactSpecificArrowFunction(node) {
|
143 | var nodeTypeText = node.typeName.getText();
|
144 | var isReactFunctionComponentType = nodeTypeText === 'React.SFC' ||
|
145 | nodeTypeText === 'SFC' ||
|
146 | nodeTypeText === 'React.FC' ||
|
147 | nodeTypeText === 'FC' ||
|
148 | nodeTypeText === 'React.StatelessComponent' ||
|
149 | nodeTypeText === 'StatelessComponent' ||
|
150 | nodeTypeText === 'React.FunctionComponent' ||
|
151 | nodeTypeText === 'FunctionComponent';
|
152 | if (!isReactFunctionComponentType) {
|
153 | return;
|
154 | }
|
155 | if (!node.typeArguments || node.typeArguments.length !== 1) {
|
156 | return;
|
157 | }
|
158 | var typeArgument = node.typeArguments[0];
|
159 | if (tsutils.isTypeLiteralNode(typeArgument)) {
|
160 | propNodes = getTypeLiteralData(typeArgument);
|
161 | propNames = Object.keys(propNodes);
|
162 | }
|
163 | else {
|
164 | }
|
165 | var arrowFunction = tsutils.getChildOfKind(node.parent, ts.SyntaxKind.ArrowFunction);
|
166 | if (!arrowFunction || !tsutils.isArrowFunction(arrowFunction)) {
|
167 | return;
|
168 | }
|
169 | lookForArrowFunction(arrowFunction);
|
170 | }
|
171 | function lookForArrowFunction(node) {
|
172 | var parameters = node.parameters;
|
173 | if (parameters.length !== 1) {
|
174 | return;
|
175 | }
|
176 | var firstParameter = parameters[0];
|
177 | var name = firstParameter.name, type = firstParameter.type;
|
178 | if (type && tsutils.isTypeReferenceNode(type)) {
|
179 | var typeName = type.typeName.getText();
|
180 | if (!ctx.options.propsInterfaceRegex.test(typeName)) {
|
181 | return;
|
182 | }
|
183 | }
|
184 | if (tsutils.isIdentifier(name)) {
|
185 | propsAlias = name.getText();
|
186 | }
|
187 | else if (tsutils.isObjectBindingPattern(name)) {
|
188 | inspectPropUsageInObjectBinding(name);
|
189 | }
|
190 | arrowFunctions.push(node);
|
191 | }
|
192 | function lookForFunctionComponent(node) {
|
193 | if (!node.body) {
|
194 | return;
|
195 | }
|
196 | var parameters = node.parameters;
|
197 | if (parameters.length !== 1) {
|
198 | return;
|
199 | }
|
200 | var firstParameter = parameters[0];
|
201 | var name = firstParameter.name, type = firstParameter.type;
|
202 | if (type && tsutils.isTypeReferenceNode(type)) {
|
203 | var typeName = type.typeName.getText();
|
204 | if (!ctx.options.propsInterfaceRegex.test(typeName)) {
|
205 | return;
|
206 | }
|
207 | }
|
208 | if (tsutils.isIdentifier(name)) {
|
209 | propsAlias = name.getText();
|
210 | }
|
211 | else if (tsutils.isObjectBindingPattern(name)) {
|
212 | inspectPropUsageInObjectBinding(name);
|
213 | }
|
214 | functionComponents.push(node.body);
|
215 | }
|
216 | function cb(node) {
|
217 | if (tsutils.isClassDeclaration(node)) {
|
218 | classDeclarations.push(node);
|
219 | return;
|
220 | }
|
221 | if (tsutils.isConstructorDeclaration(node)) {
|
222 | if (node.parameters.length > 0) {
|
223 | propsAlias = node.parameters[0].name.text;
|
224 | }
|
225 | ts.forEachChild(node, cb);
|
226 | propsAlias = undefined;
|
227 | return;
|
228 | }
|
229 | if (tsutils.isMethodDeclaration(node)) {
|
230 | var methodName = node.name.text;
|
231 | if (/componentWillReceiveProps|shouldComponentUpdate|componentWillUpdate|componentDidUpdate/.test(methodName) &&
|
232 | node.parameters.length > 0) {
|
233 | propsAlias = node.parameters[0].name.text;
|
234 | }
|
235 | if (/shouldComponentUpdate|componentWillUpdate|componentDidUpdate/.test(methodName) && node.parameters.length > 1) {
|
236 | stateAlias = node.parameters[1].name.text;
|
237 | }
|
238 | ts.forEachChild(node, cb);
|
239 | propsAlias = undefined;
|
240 | stateAlias = undefined;
|
241 | return;
|
242 | }
|
243 | if (tsutils.isInterfaceDeclaration(node)) {
|
244 | if (ctx.options.propsInterfaceRegex.test(node.name.text)) {
|
245 | propNodes = getTypeElementData(node);
|
246 | propNames = Object.keys(propNodes);
|
247 | }
|
248 | if (ctx.options.stateInterfaceRegex.test(node.name.text)) {
|
249 | stateNodes = getTypeElementData(node);
|
250 | stateNames = Object.keys(stateNodes);
|
251 | }
|
252 | }
|
253 | else if (tsutils.isPropertyAccessExpression(node)) {
|
254 | var referencedPropertyName = node.getText();
|
255 | if (/this\.props\..*/.test(referencedPropertyName)) {
|
256 | propNames = Utils_1.Utils.remove(propNames, referencedPropertyName.substring(11));
|
257 | }
|
258 | else if (/this\.state\..*/.test(referencedPropertyName)) {
|
259 | stateNames = Utils_1.Utils.remove(stateNames, referencedPropertyName.substring(11));
|
260 | }
|
261 | if (propsAlias !== undefined) {
|
262 | if (new RegExp(propsAlias + '\\..*').test(referencedPropertyName)) {
|
263 | propNames = Utils_1.Utils.remove(propNames, referencedPropertyName.substring(propsAlias.length + 1));
|
264 | }
|
265 | }
|
266 | if (stateAlias !== undefined) {
|
267 | if (new RegExp(stateAlias + '\\..*').test(referencedPropertyName)) {
|
268 | stateNames = Utils_1.Utils.remove(stateNames, referencedPropertyName.substring(stateAlias.length + 1));
|
269 | }
|
270 | }
|
271 | if (node.parent.kind !== ts.SyntaxKind.PropertyAccessExpression) {
|
272 | if (referencedPropertyName === 'this.props') {
|
273 | propNames = [];
|
274 | }
|
275 | else if (referencedPropertyName === 'this.state') {
|
276 | stateNames = [];
|
277 | }
|
278 | }
|
279 | }
|
280 | else if (tsutils.isIdentifier(node)) {
|
281 | if (propsAlias !== undefined) {
|
282 | if (node.text === propsAlias &&
|
283 | node.parent.kind !== ts.SyntaxKind.PropertyAccessExpression &&
|
284 | node.parent.kind !== ts.SyntaxKind.Parameter &&
|
285 | isParentNodeSuperCall(node) === false) {
|
286 | propNames = [];
|
287 | }
|
288 | }
|
289 | if (stateAlias !== undefined) {
|
290 | if (node.text === stateAlias &&
|
291 | node.parent.kind !== ts.SyntaxKind.PropertyAccessExpression &&
|
292 | node.parent.kind !== ts.SyntaxKind.Parameter) {
|
293 | stateNames = [];
|
294 | }
|
295 | }
|
296 | }
|
297 | else if (tsutils.isTypeReferenceNode(node)) {
|
298 | lookForReactSpecificArrowFunction(node);
|
299 | }
|
300 | else if (tsutils.isArrowFunction(node)) {
|
301 | lookForArrowFunction(node);
|
302 | }
|
303 | else if (tsutils.isFunctionDeclaration(node)) {
|
304 | lookForFunctionComponent(node);
|
305 | }
|
306 | else if (tsutils.isFunctionExpression(node)) {
|
307 | lookForFunctionComponent(node);
|
308 | }
|
309 | return ts.forEachChild(node, cb);
|
310 | }
|
311 | ts.forEachChild(ctx.sourceFile, cb);
|
312 | if (propNames.length > 0 || stateNames.length > 0) {
|
313 | classDeclarations.forEach(function (c) { return ts.forEachChild(c, cb); });
|
314 | arrowFunctions.forEach(function (c) { return ts.forEachChild(c.body, cb); });
|
315 | functionComponents.forEach(function (f) { return ts.forEachChild(f, cb); });
|
316 | }
|
317 | propNames.forEach(function (propName) {
|
318 | var typeElement = propNodes[propName];
|
319 | ctx.addFailureAt(typeElement.getStart(), typeElement.getWidth(), FAILURE_UNUSED_PROP + propName);
|
320 | });
|
321 | stateNames.forEach(function (stateName) {
|
322 | var typeElement = stateNodes[stateName];
|
323 | ctx.addFailureAt(typeElement.getStart(), typeElement.getWidth(), FAILURE_UNUSED_STATE + stateName);
|
324 | });
|
325 | }
|
326 |
|
\ | No newline at end of file |