diff --git a/package.json b/package.json index 2392356c89..9ffebad198 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "browserify": "13.1.0", "closure-util": "1.15.0", "derequire": "2.0.3", + "eslint-plugin-openlayers-internal": "file:test/rules", "fs-extra": "0.30.0", "glob": "7.0.5", "handlebars": "4.0.5", @@ -82,6 +83,9 @@ "goog": false, "proj4": false }, + "plugins": [ + "openlayers-internal" + ], "rules": { "no-console": [ 2, @@ -91,7 +95,8 @@ ] } ], - "no-constant-condition": 0 + "no-constant-condition": 0, + "openlayers-internal/no-unused-requires": 2 } }, "ext": [ diff --git a/test/rules/.eslintrc b/test/rules/.eslintrc new file mode 100644 index 0000000000..22abc7fb2b --- /dev/null +++ b/test/rules/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "es6": true + } +} diff --git a/test/rules/index.js b/test/rules/index.js new file mode 100644 index 0000000000..857d652fda --- /dev/null +++ b/test/rules/index.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + 'no-unused-requires': require('./no-unused-requires').rule + } +}; diff --git a/test/rules/no-unused-requires.js b/test/rules/no-unused-requires.js new file mode 100644 index 0000000000..663d1d63d2 --- /dev/null +++ b/test/rules/no-unused-requires.js @@ -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)}`); + } + } + } + + }; + } +}; diff --git a/test/rules/package.json b/test/rules/package.json new file mode 100644 index 0000000000..5ff7b3b4db --- /dev/null +++ b/test/rules/package.json @@ -0,0 +1,6 @@ +{ + "name": "eslint-plugin-openlayers-internal", + "version": "1.0.0", + "description": "Custom ESLint rules for the OpenLayers project", + "main": "index.js" +}