From 279e10abe8537208abbe8c6463c4a76cf162420a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 8 Aug 2016 23:43:04 -0600 Subject: [PATCH] Rule to catch missing requires --- package.json | 1 + rules/index.js | 1 + rules/no-missing-requires.js | 90 ++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 rules/no-missing-requires.js diff --git a/package.json b/package.json index 292a67b02d..2e67cc7e97 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ ], "no-constant-condition": 0, "openlayers-internal/no-duplicate-requires": 2, + "openlayers-internal/no-missing-requires": 1, "openlayers-internal/no-unused-requires": 2, "openlayers-internal/requires-first": 2, "openlayers-internal/valid-requires": 2 diff --git a/rules/index.js b/rules/index.js index f625cac35b..fcd8ddf090 100644 --- a/rules/index.js +++ b/rules/index.js @@ -3,6 +3,7 @@ module.exports = { rules: { 'no-duplicate-requires': require('./no-duplicate-requires').rule, + 'no-missing-requires': require('./no-missing-requires').rule, 'no-unused-requires': require('./no-unused-requires').rule, 'requires-first': require('./requires-first').rule, 'valid-requires': require('./valid-requires').rule diff --git a/rules/no-missing-requires.js b/rules/no-missing-requires.js new file mode 100644 index 0000000000..e0ef07dda4 --- /dev/null +++ b/rules/no-missing-requires.js @@ -0,0 +1,90 @@ +'use strict'; + +const util = require('./util'); + +/** + * Unfortunately fragile RegExp to follow. Here is the logic: + * + * 1. check if a name looks like a const (ol.foo.BOO_HOO must have a "_") + * if so, require the "namespace" (ol.foo) + * 2. check if a name looks like a class (ol.foo.Bar or ol.foo.XYZ) + * if so, require the class (ol.foo.Bar) + * 3. otherwise, lop off the last part of a name and require the rest + * (e.g. ol.foo.bar would require ol.foo) + */ + +const CONST_RE = /^(ol(\.[a-z]\w*)*)\.[A-Z]+_([_A-Z])+$/; +const CLASS_RE = /^(ol(\.[a-z]\w*)*\.[A-Z]\w*)(\.\w+)*$/; + +exports.rule = { + meta: { + docs: { + description: 'ensure there are goog.require() calls for all used symbols' + }, + fixable: 'code' + }, + + create: function(context) { + const defined = {}; + + return { + + ExpressionStatement: function(statement) { + if (util.isRequireStatement(statement) || util.isProvideStatement(statement)) { + const expression = statement.expression; + const arg = expression.arguments[0]; + if (!arg || !arg.value) { + return; + } + defined[arg.value] = true; + } + }, + + MemberExpression: function(expression) { + const parent = expression.parent; + if (parent.type !== 'MemberExpression') { + const name = util.getName(expression); + if (name && name.startsWith('ol.')) { + // check if the name looks like a const + let match = name.match(CONST_RE); + if (match) { + if (!defined[match[1]]) { + context.report(expression, `Missing goog.require('${match[1]}')`); + } + return; + } + // check if the name looks like a class + match = name.match(CLASS_RE); + if (match) { + const className = match[1]; + const parts = className.split('.'); + const lastPart = parts[parts.length - 1]; + if (lastPart.toUpperCase() === lastPart) { + // unfortunately ambiguous: + // ol.has.WEBGL -> require('ol.has') + // ol.source.XYZ -> require('ol.source.XYZ') + const objectName = parts.slice(0, -1).join('.'); + if (!defined[className] && !defined[objectName]) { + context.report(expression, `Missing goog.require('${className}') or goog.require('${objectName}')`); + } + return; + } + if (!defined[className]) { + context.report(expression, `Missing goog.require('${className}')`); + } + return; + } + // otherwise, assume the object should be required + const parts = name.split('.'); + parts.pop(); + const objectName = parts.join('.'); + if (!defined[objectName]) { + context.report(expression, `Missing goog.require('${objectName}')`); + } + } + } + } + + }; + } +};