tslint-microsoft-contrib
Version:
TSLint Rules for Microsoft
324 lines • 13.2 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var ts = require("typescript");
var Lint = require("tslint");
var tsutils = require("tsutils");
var Utils_1 = require("./utils/Utils");
var TypeGuard_1 = require("./utils/TypeGuard");
var PROPS_REGEX = 'props-interface-regex';
var STATE_REGEX = 'state-interface-regex';
var FAILURE_UNUSED_PROP = 'Unused React property defined in interface: ';
var FAILURE_UNUSED_STATE = 'Unused React state defined in interface: ';
var Rule = (function (_super) {
__extends(Rule, _super);
function Rule() {
return _super !== null && _super.apply(this, arguments) || this;
}
Rule.prototype.apply = function (sourceFile) {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithFunction(sourceFile, walk, this.parseOptions(this.getOptions()));
}
return [];
};
Rule.prototype.parseOptions = function (options) {
var _this = this;
var parsed = {
propsInterfaceRegex: /^Props$/,
stateInterfaceRegex: /^State$/
};
options.ruleArguments.forEach(function (opt) {
if (TypeGuard_1.isObject(opt)) {
parsed.propsInterfaceRegex = _this.getOptionOrDefault(opt, PROPS_REGEX, parsed.propsInterfaceRegex);
parsed.stateInterfaceRegex = _this.getOptionOrDefault(opt, STATE_REGEX, parsed.stateInterfaceRegex);
}
});
return parsed;
};
Rule.prototype.getOptionOrDefault = function (option, key, defaultValue) {
try {
var value = option[key];
if (value !== undefined && typeof value === 'string') {
return new RegExp(value);
}
}
catch (e) {
console.error('Could not read ' + key + ' within react-unused-props-and-state-name configuration');
}
return defaultValue;
};
Rule.metadata = {
ruleName: 'react-unused-props-and-state',
type: 'maintainability',
description: 'Remove unneeded properties defined in React Props and State interfaces',
options: null,
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Low',
level: 'Opportunity for Excellence',
group: 'Correctness',
commonWeaknessEnumeration: '398'
};
return Rule;
}(Lint.Rules.AbstractRule));
exports.Rule = Rule;
function walk(ctx) {
var propNames = [];
var propNodes = {};
var stateNames = [];
var stateNodes = {};
var classDeclarations = [];
var arrowFunctions = [];
var functionComponents = [];
var propsAlias;
var stateAlias;
function getTypeElementData(node) {
var result = {};
node.members.forEach(function (typeElement) {
if (typeElement.name !== undefined) {
var text = typeElement.name.getText();
if (text !== undefined) {
result[text] = typeElement;
}
}
});
return result;
}
function getTypeLiteralData(node) {
var result = {};
node.members.forEach(function (typeElement) {
if (typeElement.name !== undefined) {
var text = typeElement.name.getText();
if (text !== undefined) {
result[text] = typeElement;
}
}
});
return result;
}
function getObjectBindingData(node) {
var result = {};
node.elements.forEach(function (element) {
if (element.name !== undefined) {
var text = element.name.getText();
if (text !== undefined) {
result[text] = element;
}
}
});
return result;
}
function isParentNodeSuperCall(node) {
if (node.parent !== undefined && node.parent.kind === ts.SyntaxKind.CallExpression) {
var call = node.parent;
return call.expression.getText() === 'super';
}
return false;
}
function inspectPropUsageInObjectBinding(name) {
var bindingElements = getObjectBindingData(name);
var foundPropNames = Object.keys(bindingElements);
for (var _i = 0, foundPropNames_1 = foundPropNames; _i < foundPropNames_1.length; _i++) {
var propName = foundPropNames_1[_i];
propNames = Utils_1.Utils.remove(propNames, propName);
}
}
function lookForReactSpecificArrowFunction(node) {
var nodeTypeText = node.typeName.getText();
var isReactFunctionComponentType = nodeTypeText === 'React.SFC' ||
nodeTypeText === 'SFC' ||
nodeTypeText === 'React.FC' ||
nodeTypeText === 'FC' ||
nodeTypeText === 'React.StatelessComponent' ||
nodeTypeText === 'StatelessComponent' ||
nodeTypeText === 'React.FunctionComponent' ||
nodeTypeText === 'FunctionComponent';
if (!isReactFunctionComponentType) {
return;
}
if (!node.typeArguments || node.typeArguments.length !== 1) {
return;
}
var typeArgument = node.typeArguments[0];
if (tsutils.isTypeLiteralNode(typeArgument)) {
propNodes = getTypeLiteralData(typeArgument);
propNames = Object.keys(propNodes);
}
else {
}
var arrowFunction = tsutils.getChildOfKind(node.parent, ts.SyntaxKind.ArrowFunction);
if (!arrowFunction || !tsutils.isArrowFunction(arrowFunction)) {
return;
}
lookForArrowFunction(arrowFunction);
}
function lookForArrowFunction(node) {
var parameters = node.parameters;
if (parameters.length !== 1) {
return;
}
var firstParameter = parameters[0];
var name = firstParameter.name, type = firstParameter.type;
if (type && tsutils.isTypeReferenceNode(type)) {
var typeName = type.typeName.getText();
if (!ctx.options.propsInterfaceRegex.test(typeName)) {
return;
}
}
if (tsutils.isIdentifier(name)) {
propsAlias = name.getText();
}
else if (tsutils.isObjectBindingPattern(name)) {
inspectPropUsageInObjectBinding(name);
}
arrowFunctions.push(node);
}
function lookForFunctionComponent(node) {
if (!node.body) {
return;
}
var parameters = node.parameters;
if (parameters.length !== 1) {
return;
}
var firstParameter = parameters[0];
var name = firstParameter.name, type = firstParameter.type;
if (type && tsutils.isTypeReferenceNode(type)) {
var typeName = type.typeName.getText();
if (!ctx.options.propsInterfaceRegex.test(typeName)) {
return;
}
}
if (tsutils.isIdentifier(name)) {
propsAlias = name.getText();
}
else if (tsutils.isObjectBindingPattern(name)) {
inspectPropUsageInObjectBinding(name);
}
functionComponents.push(node.body);
}
function cb(node) {
if (tsutils.isClassDeclaration(node)) {
classDeclarations.push(node);
return;
}
if (tsutils.isConstructorDeclaration(node)) {
if (node.parameters.length > 0) {
propsAlias = node.parameters[0].name.text;
}
ts.forEachChild(node, cb);
propsAlias = undefined;
return;
}
if (tsutils.isMethodDeclaration(node)) {
var methodName = node.name.text;
if (/componentWillReceiveProps|shouldComponentUpdate|componentWillUpdate|componentDidUpdate/.test(methodName) &&
node.parameters.length > 0) {
propsAlias = node.parameters[0].name.text;
}
if (/shouldComponentUpdate|componentWillUpdate|componentDidUpdate/.test(methodName) && node.parameters.length > 1) {
stateAlias = node.parameters[1].name.text;
}
ts.forEachChild(node, cb);
propsAlias = undefined;
stateAlias = undefined;
return;
}
if (tsutils.isInterfaceDeclaration(node)) {
if (ctx.options.propsInterfaceRegex.test(node.name.text)) {
propNodes = getTypeElementData(node);
propNames = Object.keys(propNodes);
}
if (ctx.options.stateInterfaceRegex.test(node.name.text)) {
stateNodes = getTypeElementData(node);
stateNames = Object.keys(stateNodes);
}
}
else if (tsutils.isPropertyAccessExpression(node)) {
var referencedPropertyName = node.getText();
if (/this\.props\..*/.test(referencedPropertyName)) {
propNames = Utils_1.Utils.remove(propNames, referencedPropertyName.substring(11));
}
else if (/this\.state\..*/.test(referencedPropertyName)) {
stateNames = Utils_1.Utils.remove(stateNames, referencedPropertyName.substring(11));
}
if (propsAlias !== undefined) {
if (new RegExp(propsAlias + '\\..*').test(referencedPropertyName)) {
propNames = Utils_1.Utils.remove(propNames, referencedPropertyName.substring(propsAlias.length + 1));
}
}
if (stateAlias !== undefined) {
if (new RegExp(stateAlias + '\\..*').test(referencedPropertyName)) {
stateNames = Utils_1.Utils.remove(stateNames, referencedPropertyName.substring(stateAlias.length + 1));
}
}
if (node.parent.kind !== ts.SyntaxKind.PropertyAccessExpression) {
if (referencedPropertyName === 'this.props') {
propNames = [];
}
else if (referencedPropertyName === 'this.state') {
stateNames = [];
}
}
}
else if (tsutils.isIdentifier(node)) {
if (propsAlias !== undefined) {
if (node.text === propsAlias &&
node.parent.kind !== ts.SyntaxKind.PropertyAccessExpression &&
node.parent.kind !== ts.SyntaxKind.Parameter &&
isParentNodeSuperCall(node) === false) {
propNames = [];
}
}
if (stateAlias !== undefined) {
if (node.text === stateAlias &&
node.parent.kind !== ts.SyntaxKind.PropertyAccessExpression &&
node.parent.kind !== ts.SyntaxKind.Parameter) {
stateNames = [];
}
}
}
else if (tsutils.isTypeReferenceNode(node)) {
lookForReactSpecificArrowFunction(node);
}
else if (tsutils.isArrowFunction(node)) {
lookForArrowFunction(node);
}
else if (tsutils.isFunctionDeclaration(node)) {
lookForFunctionComponent(node);
}
else if (tsutils.isFunctionExpression(node)) {
lookForFunctionComponent(node);
}
return ts.forEachChild(node, cb);
}
ts.forEachChild(ctx.sourceFile, cb);
if (propNames.length > 0 || stateNames.length > 0) {
classDeclarations.forEach(function (c) { return ts.forEachChild(c, cb); });
arrowFunctions.forEach(function (c) { return ts.forEachChild(c.body, cb); });
functionComponents.forEach(function (f) { return ts.forEachChild(f, cb); });
}
propNames.forEach(function (propName) {
var typeElement = propNodes[propName];
ctx.addFailureAt(typeElement.getStart(), typeElement.getWidth(), FAILURE_UNUSED_PROP + propName);
});
stateNames.forEach(function (stateName) {
var typeElement = stateNodes[stateName];
ctx.addFailureAt(typeElement.getStart(), typeElement.getWidth(), FAILURE_UNUSED_STATE + stateName);
});
}
//# sourceMappingURL=reactUnusedPropsAndStateRule.js.map