Skip to content

Commit

Permalink
Add support for goog.module and add flexibility
Browse files Browse the repository at this point in the history
- add configuration to support more than just "ol";
- no-missing-require is less picky;
- both provide and module are handled
  • Loading branch information
gberaudo committed Jan 31, 2017
1 parent 7240fa8 commit a976881
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 59 deletions.
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ module.exports = {
'no-duplicate-requires': require('./no-duplicate-requires').rule,
'no-missing-requires': require('./no-missing-requires').rule,
'no-unused-requires': require('./no-unused-requires').rule,
'one-provide': require('./one-provide').rule,
'one-provide-or-module': require('./one-provide-or-module').rule,
'requires-first': require('./requires-first').rule,
'valid-provide': require('./valid-provide').rule,
'valid-provide-and-module': require('./valid-provide-and-module').rule,
'valid-requires': require('./valid-requires').rule
}
};
30 changes: 21 additions & 9 deletions no-missing-requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const util = require('./util');
* but ol.ext.foo would require ol.ext.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: {
Expand All @@ -27,11 +25,16 @@ exports.rule = {

create: function(context) {
const defined = {};
const prefixes = context.options[0].prefixes || ['ol'];
const joined = prefixes.join('|');
const CONST_RE = new RegExp(`^((?:${joined})(\\.[a-z]\\w*)*)\\.[A-Z]+_([_A-Z])+$`);
const CLASS_RE = new RegExp(`^((?:${joined})(\\.[a-z]\\w*)*\\.[A-Z]\\w*)(\\.\\w+)*$`);
const STARTS_WITH = new RegExp(`^(?:${joined})\\..*`);

return {

ExpressionStatement: function(statement) {
if (util.isRequireStatement(statement) || util.isProvideStatement(statement)) {
if (util.isRequireStatement(statement) || util.isRequireVariableDeclaration(statement) || util.isProvideStatement(statement)) {
const expression = statement.expression;
const arg = expression.arguments[0];
if (!arg || !arg.value) {
Expand All @@ -43,9 +46,17 @@ exports.rule = {

MemberExpression: function(expression) {
const parent = expression.parent;
if (parent.type === 'AssignmentExpression') {
const name = util.getName(expression);
if (name && STARTS_WITH.test(name)) {
defined[name] = true;
return;
}
}

if (parent.type !== 'MemberExpression') {
const name = util.getName(expression);
if (name && name.startsWith('ol.')) {
if (name && STARTS_WITH.test(name) && !defined[name]) {
// check if the name looks like a const
let match = name.match(CONST_RE);
if (match) {
Expand All @@ -70,8 +81,9 @@ exports.rule = {
}
return;
}
if (!defined[className]) {
context.report(expression, `Missing goog.require('${className}')`);
const parentObjectName = className.split('.').slice(0, -1).join('.'); // app.constants.RouteType enum in constants namespace
if (!defined[className] && !defined[parentObjectName]) {
context.report(expression, `Missing goog.require('${className}') or goog.require('${parentObjectName}')`);
}
return;
}
Expand All @@ -82,13 +94,13 @@ exports.rule = {
parts.pop();
}
const objectName = parts.join('.');
if (!defined[objectName]) {
context.report(expression, `Missing goog.require('${objectName}')`);
const parentObjectName = parts.slice(0, -1).join('.'); // app.widgets.sortable.dragOptionsDirective.module
if (!defined[objectName] && !defined[parentObjectName]) {
context.report(expression, `Missing goog.require('${objectName}') or goog.require('${parentObjectName}')`);
}
}
}
}

};
}
};
43 changes: 43 additions & 0 deletions one-provide-or-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

const util = require('./util');

exports.rule = {
meta: {
docs: {
description: 'disallow multiple goog.provide() or goog.module() calls'
}
},

create: function(context) {
let hasProvide = false;
let hasModule = false;

return {
ExpressionStatement: function(statement) {
if (util.isModuleStatement(statement)) {
if (hasModule) {
const name = statement.expression.arguments[0].value;
context.report(statement, `Extra goog.module('${name}')`);
} else if (hasProvide) {
const name = statement.expression.arguments[0].value;
context.report(statement, `Forbidden goog.module('${name}'), a goog.provide() is already present`);
} else {
hasModule = true;
}
}
if (util.isProvideStatement(statement)) {
if (hasModule) {
const name = statement.expression.arguments[0].value;
context.report(statement, `Forbidden goog.provide('${name}'), a goog.module() is already present`);
} else if (hasProvide) {
const name = statement.expression.arguments[0].value;
context.report(statement, `Extra goog.provide('${name}')`);
} else {
hasProvide = true;
}
}
}
};
}
};
28 changes: 0 additions & 28 deletions one-provide.js

This file was deleted.

8 changes: 3 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
{
"name": "eslint-plugin-openlayers-internal",
"name": "eslint-plugin-googshift",
"version": "3.1.0",
"description": "Custom ESLint rules for the OpenLayers project",
"main": "index.js",
"repository": {
"type": "git",
"url": "git://github.com/openlayers/eslint-plugin-openlayers-internal.git"
"url": "git://github.com/gberaudo/eslint-plugin-googshift.git"
},
"license": "BSD-2-Clause",
"scripts": {
"test": "eslint ."
},
"eslintConfig": {
"extends": "openlayers",
"env": {
"es6": true
}
},
"devDependencies": {
"eslint": "^3.12.2",
"eslint-config-openlayers": "^6.0.0"
"eslint": "^3.12.2"
}
}
6 changes: 3 additions & 3 deletions requires-first.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const util = require('./util');
exports.rule = {
meta: {
docs: {
description: 'require that all goog.require() precede other statements (except goog.provide())'
description: 'require that all goog.require() precede other statements (except goog.provide(), goog.module() and goog.module.declareLegacyNamespace())'
}
},

Expand All @@ -15,11 +15,11 @@ exports.rule = {
let otherSeen = false;

program.body.forEach(statement => {
if (util.isRequireStatement(statement)) {
if (util.isRequireStatement(statement) || util.isRequireVariableDeclaration(statement)) {
if (otherSeen) {
return context.report(statement, 'Expected goog.require() to precede other statements');
}
} else if (!util.isProvideStatement(statement)) {
} else if (!util.isProvideStatement(statement) && !util.isModuleStatement(statement) && !util.isModuleLegacyStatement(statement)) {
otherSeen = true;
}
});
Expand Down
29 changes: 29 additions & 0 deletions util.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ function isGoogStatement(node, name) {
isGoogCallExpression(node.expression, name);
}

function isGoogVariableDeclaration(node, name) {
return node.type === 'VariableDeclaration' && node.declarations &&
node.declarations.length > 0 && isGoogCallExpression(node.declarations[0].init, name);
}

exports.isModuleExpression = function(node) {
return isGoogCallExpression(node, 'module');
};

exports.isModuleStatement = function(node) {
return isGoogStatement(node, 'module');
};

exports.isModuleLegacyStatement = function(node) {
if (!node.expression || !node.expression.callee || !node.expression.callee.object) {
return false;
}
const callee = node.expression.callee;
return node.expression.type === 'CallExpression' &&
callee.object && callee.object.object &&
callee.object.object && callee.object.object.name === 'goog' &&
callee.object.property && callee.object.property.name === 'module' &&
callee.property && callee.property.name === 'declareLegacyNamespace';
}

exports.isProvideExpression = function(node) {
return isGoogCallExpression(node, 'provide');
};
Expand All @@ -29,6 +54,10 @@ exports.isRequireStatement = function(node) {
return isGoogStatement(node, 'require');
};

exports.isRequireVariableDeclaration = function(node) {
return isGoogVariableDeclaration(node, 'require');
};

var getName = exports.getName = function(node) {
if (node.type !== 'MemberExpression') {
return;
Expand Down
24 changes: 15 additions & 9 deletions valid-provide.js → valid-provide-and-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,55 @@ const util = require('./util');
exports.rule = {
meta: {
docs: {
description: 'require the first goog.provide() has an arg named like the file path'
description: 'require the first goog.provide() or goog.module() has an arg named like the file path'
}
},

create: function(context) {
const entryPoints = context.options[0].entryPoints || ['ol'];
const relativeSourceRoot = context.options[0].root || 'src';
let gotFirst = false;
return {
CallExpression: function(expression) {
if (gotFirst) {
return;
}
if (util.isProvideExpression(expression)) {
const isProvide = util.isProvideExpression(expression);
const isModule = util.isModuleExpression(expression);

if (isProvide || isModule) {
const type = isProvide ? 'provide' : 'module';
gotFirst = true;
const parent = expression.parent;
if (parent.type !== 'ExpressionStatement') {
return context.report(expression, 'Expected goog.provide() to be in an expression statement');
return context.report(expression, `Expected goog.${type}() to be in an expression statement`);
}

if (parent.parent.type !== 'Program') {
return context.report(expression, 'Expected goog.provide() to be at the top level');
return context.report(expression, `Expected goog.${type}() to be at the top level`);
}

if (expression.arguments.length !== 1) {
return context.report(expression, 'Expected one argument for goog.provide()');
return context.report(expression, `Expected one argument for goog.${type}()`);
}

const arg = expression.arguments[0];
if (arg.type !== 'Literal' || !arg.value || typeof arg.value !== 'string') {
return context.report(expression, 'Expected goog.provide() to be called with a string');
return context.report(expression, `Expected goog.${type}() to be called with a string`);
}

const filePath = context.getFilename();
const sourceRoot = path.join(process.cwd(), 'src');
const sourceRoot = path.join(process.cwd(), relativeSourceRoot);
const requirePath = path.relative(sourceRoot, filePath);
const ext = '.js';
let name = arg.value;
// special case for main entry point
if (name === 'ol') {
if (entryPoints.indexOf(name) !== -1) {
name += '.index';
}
const expectedPath = name.split('.').join(path.sep) + ext;
if (expectedPath.toLowerCase() !== requirePath.toLowerCase()) {
return context.report(expression, `Expected goog.provide('${name}') to be like ${requirePath}`);
return context.report(expression, `Expected goog.${type}('${name}') to be like ${requirePath}`);
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions valid-requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ exports.rule = {
CallExpression: function(expression) {
if (util.isRequireExpression(expression)) {
const parent = expression.parent;
if (parent.type !== 'ExpressionStatement') {
return context.report(expression, 'Expected goog.require() to be in an expression statement');
const parentIsExpression = parent.type === 'ExpressionStatement';
const parentIsVariableDeclarator = parent.type === 'VariableDeclarator';
if (!parentIsExpression && !parentIsVariableDeclarator) {
return context.report(expression, 'Expected goog.require() to be in an expression or variable declarator statement');
}

if (parent.parent.type !== 'Program') {
const expectedProgram = parentIsExpression ? parent.parent : parent.parent.parent;
if (expectedProgram.type !== 'Program') {
return context.report(expression, 'Expected goog.require() to be at the top level');
}

Expand Down

0 comments on commit a976881

Please sign in to comment.