Custom rule to identify unused goog.require() calls

This commit is contained in:
Tim Schaub
2016-08-07 16:14:40 -06:00
parent fe35c05860
commit 3c7e9f6870
5 changed files with 122 additions and 1 deletions

5
test/rules/.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"env": {
"es6": true
}
}

5
test/rules/index.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
rules: {
'no-unused-requires': require('./no-unused-requires').rule
}
};

View File

@@ -0,0 +1,100 @@
function isGoogRequire(node) {
const callee = node.callee;
return callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' && callee.object.name === 'goog' &&
callee.property.type === 'Identifier' && !callee.property.computed && callee.property.name === 'require';
}
function getName(node) {
if (node.type !== 'MemberExpression') {
return;
}
if (node.property.type !== 'Identifier' || node.property.computed) {
return;
}
let objectName;
if (node.object.type === 'Identifier' && !node.object.computed) {
objectName = node.object.name;
} else if (node.object.type === 'MemberExpression' && !node.object.computed) {
objectName = getName(node.object);
}
if (!objectName) {
return;
}
return `${objectName}.${node.property.name}`;
}
exports.rule = {
meta: {
docs: {
description: 'disallow unused goog.require() calls'
},
schema: []
},
create: function(context) {
// a lookup of goog.require() nodes by argument
const requireNodes = {};
// used names from member expressions that match the goog.require() arg
const usedNames = {};
return {
CallExpression: function(node) {
if (isGoogRequire(node)) {
if (node.arguments.length !== 1) {
return context.report(node, 'Expected one argument for goog.require()');
}
const arg = node.arguments[0];
if (arg.type !== 'Literal' || !arg.value || typeof arg.value !== 'string') {
return context.report(node, 'Expected goog.require() to be called with a string');
}
const name = arg.value;
if (name in requireNodes) {
return context.report(node, `Duplicate goog.require('${name}')`);
}
requireNodes[name] = node;
}
},
MemberExpression: function(node) {
const name = getName(node);
if (name in requireNodes) {
const requiredAncestor = context.getAncestors().some(ancestorNode => !!requireNodes[getName(ancestorNode)]);
if (!requiredAncestor) {
usedNames[name] = true;
}
}
},
Identifier: function(node) {
const name = node.name;
if (name in requireNodes) {
const ancestors = context.getAncestors();
if (ancestors.length && ancestors[0].type === 'MemberExpression') {
const requiredAncestor = context.getAncestors().some(ancestorNode => !!requireNodes[getName(ancestorNode)]);
if (!requiredAncestor) {
usedNames[name] = true;
}
} else {
usedNames[name] = true;
}
}
},
'Program:exit': function(node) {
const source = context.getSourceCode();
for (let name in requireNodes) {
if (!usedNames[name]) {
const unusedRequire = requireNodes[name];
context.report(unusedRequire, `Unused ${source.getText(unusedRequire)}`);
}
}
}
};
}
};

6
test/rules/package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "eslint-plugin-openlayers-internal",
"version": "1.0.0",
"description": "Custom ESLint rules for the OpenLayers project",
"main": "index.js"
}