diff --git a/.travis.yml b/.travis.yml index d248a521..8670e113 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js node_js: - - "6.0" - - "5.0" - - "4.2" + - 7 + - 6 + - 5 + - 4 after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" diff --git a/docs/timeout-service.md b/docs/timeout-service.md index 6182d605..2df3bae9 100644 --- a/docs/timeout-service.md +++ b/docs/timeout-service.md @@ -26,6 +26,11 @@ The following patterns are considered problems; window.setTimeout(function() { // ... }, 1000) // error: You should use the $timeout service instead of the default window.setTimeout method + + // invalid + $window.setTimeout(function() { + // ... + }, 1000) // error: You should use the $timeout service instead of the default window.setTimeout method The following patterns are **not** considered problems; diff --git a/examples/timeout-service.js b/examples/timeout-service.js index ae3b5fdc..2388cc26 100644 --- a/examples/timeout-service.js +++ b/examples/timeout-service.js @@ -15,3 +15,9 @@ window.setTimeout(function() { // ... }, 1000) + +// example - valid: false, errorMessage: "You should use the $timeout service instead of the default window.setTimeout method" +$window.setTimeout(function() { + // ... +}, 1000) + diff --git a/package.json b/package.json index 136f4aae..aa01c7fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-angular", - "version": "1.6.1", + "version": "1.6.2", "description": "ESLint rules for AngularJS projects", "main": "index.js", "scripts": { @@ -29,10 +29,10 @@ "gulp": "^3.9.1", "gulp-eslint": "^3.0.1", "gulp-istanbul": "^1.0.0", - "gulp-mocha": "^2.2.0", + "gulp-mocha": "^3.0.1", "istanbul": "^0.4.2", "lodash": "^4.13.1", - "mocha": "^2.4.5", + "mocha": "^3.2.0", "parse-comments": "^0.4.3", "shelljs": "^0.7.1", "shelljs-nodecli": "^0.1.1" diff --git a/rules/angularelement.js b/rules/angularelement.js index 350d7b23..4494add1 100644 --- a/rules/angularelement.js +++ b/rules/angularelement.js @@ -10,14 +10,17 @@ */ 'use strict'; -module.exports = function(context) { - return { - CallExpression: function(node) { - if (node.callee.name === '$' || node.callee.name === 'jQuery') { - context.report(node, 'You should use angular.element instead of the jQuery $ object', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { + CallExpression: function(node) { + if (node.callee.name === '$' || node.callee.name === 'jQuery') { + context.report(node, 'You should use angular.element instead of the jQuery $ object', {}); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/rules/component-limit.js b/rules/component-limit.js index a2f3ba0d..f53506c8 100644 --- a/rules/component-limit.js +++ b/rules/component-limit.js @@ -18,36 +18,39 @@ var angularRule = require('./utils/angular-rule'); -module.exports = angularRule(function(context) { - var limit = context.options[0] || 1; - var count = 0; - var msg = 'There may be at most {{limit}} AngularJS {{component}} per file, but found {{number}}'; +module.exports = { + meta: { + schema: [{ + type: 'integer' + }] + }, + create: angularRule(function(context) { + var limit = context.options[0] || 1; + var count = 0; + var msg = 'There may be at most {{limit}} AngularJS {{component}} per file, but found {{number}}'; - function checkLimit(callee) { - count++; - if (count > limit) { - context.report(callee, msg, { - limit: limit, - component: limit === 1 ? 'component' : 'components', - number: count - }); + function checkLimit(callee) { + count++; + if (count > limit) { + context.report(callee, msg, { + limit: limit, + component: limit === 1 ? 'component' : 'components', + number: count + }); + } } - } - return { - 'angular:animation': checkLimit, - 'angular:config': checkLimit, - 'angular:controller': checkLimit, - 'angular:directive': checkLimit, - 'angular:factory': checkLimit, - 'angular:filter': checkLimit, - 'angular:provider': checkLimit, - 'angular:run': checkLimit, - 'angular:service': checkLimit, - 'angular:component': checkLimit - }; -}); - -module.exports.schema = [{ - type: 'integer' -}]; + return { + 'angular:animation': checkLimit, + 'angular:config': checkLimit, + 'angular:controller': checkLimit, + 'angular:directive': checkLimit, + 'angular:factory': checkLimit, + 'angular:filter': checkLimit, + 'angular:provider': checkLimit, + 'angular:run': checkLimit, + 'angular:service': checkLimit, + 'angular:component': checkLimit + }; + }) +}; diff --git a/rules/component-name.js b/rules/component-name.js index 761d1048..b76556a6 100644 --- a/rules/component-name.js +++ b/rules/component-name.js @@ -13,44 +13,51 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - if (context.settings.angular === 2) { - return {}; - } +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }] + }, + create: function(context) { + if (context.settings.angular === 2) { + return {}; + } - return { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex - if (prefix === undefined) { - return; - } + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); + convertedPrefix = utils.convertPrefixToRegex(prefix); - if (utils.isAngularComponentDeclaration(node)) { - var name = node.arguments[0].value; + if (utils.isAngularComponentDeclaration(node)) { + var name = node.arguments[0].value; - if (name !== undefined && name.indexOf('ng') === 0) { - context.report(node, 'The {{component}} component should not start with "ng". This is reserved for AngularJS components', { - component: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{component}} component should be prefixed by {{prefix}}', { - component: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{component}} component should follow this pattern: {{prefix}}', { - component: name, - prefix: prefix.toString() + if (name !== undefined && name.indexOf('ng') === 0) { + context.report(node, 'The {{component}} component should not start with "ng". This is reserved for AngularJS components', { + component: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{component}} component should be prefixed by {{prefix}}', { + component: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{component}} component should follow this pattern: {{prefix}}', { + component: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/constant-name.js b/rules/constant-name.js index aa037f10..ca49a3ae 100644 --- a/rules/constant-name.js +++ b/rules/constant-name.js @@ -15,42 +15,51 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }, { + type: 'object' + }] + }, + create: function(context) { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex - var isConstant; + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex + var isConstant; - if (prefix === undefined) { - return; - } + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); - isConstant = utils.isAngularConstantDeclaration(node); - - if (isConstant) { - var name = node.arguments[0].value; - - if (name !== undefined && name.indexOf('$') === 0) { - context.report(node, 'The {{constant}} constant should not start with "$". This is reserved for AngularJS services', { - constant: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{constant}} constant should be prefixed by {{prefix}}', { - constant: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{constant}} constant should follow this pattern: {{prefix}}', { - constant: name, - prefix: prefix.toString() + convertedPrefix = utils.convertPrefixToRegex(prefix); + isConstant = utils.isAngularConstantDeclaration(node); + + if (isConstant) { + var name = node.arguments[0].value; + + if (name !== undefined && name.indexOf('$') === 0) { + context.report(node, 'The {{constant}} constant should not start with "$". This is reserved for AngularJS services', { + constant: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{constant}} constant should be prefixed by {{prefix}}', { + constant: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{constant}} constant should follow this pattern: {{prefix}}', { + constant: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/controller-as-route.js b/rules/controller-as-route.js index 220c1bc1..5ef902ec 100644 --- a/rules/controller-as-route.js +++ b/rules/controller-as-route.js @@ -12,76 +12,81 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { - CallExpression: function(node) { - var routeObject = null; - var stateObject = null; - var hasControllerAs = false; - var controllerProp = null; - var stateName = null; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { + CallExpression: function(node) { + var routeObject = null; + var stateObject = null; + var hasControllerAs = false; + var controllerProp = null; + var stateName = null; - if (utils.isRouteDefinition(node)) { - // second argument in $routeProvider.when('route', {...}) - routeObject = node.arguments[1]; + if (utils.isRouteDefinition(node)) { + // second argument in $routeProvider.when('route', {...}) + routeObject = node.arguments[1]; - if (routeObject.properties) { - routeObject.properties.forEach(function(prop) { - if (prop.key.name === 'controller') { - controllerProp = prop; + if (routeObject.properties) { + routeObject.properties.forEach(function(prop) { + if (prop.key.name === 'controller') { + controllerProp = prop; - if (new RegExp('\\sas\\s').test(prop.value.value)) { - hasControllerAs = true; + if (new RegExp('\\sas\\s').test(prop.value.value)) { + hasControllerAs = true; + } } - } - if (prop.key.name === 'controllerAs') { - if (hasControllerAs) { - context.report(node, 'The controllerAs syntax is defined twice for the route "{{route}}"', { - route: node.arguments[0].value - }); + if (prop.key.name === 'controllerAs') { + if (hasControllerAs) { + context.report(node, 'The controllerAs syntax is defined twice for the route "{{route}}"', { + route: node.arguments[0].value + }); + } + + hasControllerAs = true; } + }); - hasControllerAs = true; + // if it's a route without a controller, we shouldn't warn about controllerAs + if (controllerProp && !hasControllerAs) { + context.report(node, 'Route "{{route}}" should use controllerAs syntax', { + route: node.arguments[0].value + }); } - }); - - // if it's a route without a controller, we shouldn't warn about controllerAs - if (controllerProp && !hasControllerAs) { - context.report(node, 'Route "{{route}}" should use controllerAs syntax', { - route: node.arguments[0].value - }); } - } - } else if (utils.isUIRouterStateDefinition(node)) { - // state can be defined like .state({...}) or .state('name', {...}) - var isObjectState = node.arguments.length === 1; - stateObject = isObjectState ? node.arguments[0] : node.arguments[1]; + } else if (utils.isUIRouterStateDefinition(node)) { + // state can be defined like .state({...}) or .state('name', {...}) + var isObjectState = node.arguments.length === 1; + stateObject = isObjectState ? node.arguments[0] : node.arguments[1]; - if (stateObject && stateObject.properties) { - stateObject.properties.forEach(function(prop) { - if (prop.key.name === 'controller') { - controllerProp = prop; - } - if (prop.key.name === 'controllerAs') { - hasControllerAs = true; - } - // grab the name from the object for when they aren't using .state('name',...) - if (prop.key.name === 'name') { - stateName = prop.value.value; - } - }); + if (stateObject && stateObject.properties) { + stateObject.properties.forEach(function(prop) { + if (prop.key.name === 'controller') { + controllerProp = prop; + } + if (prop.key.name === 'controllerAs') { + hasControllerAs = true; + } + // grab the name from the object for when they aren't using .state('name',...) + if (prop.key.name === 'name') { + stateName = prop.value.value; + } + }); - if (!hasControllerAs && controllerProp) { - // if the controller is a string, controllerAs can be set like 'controller as vm' - if (controllerProp.value.type !== 'Literal' || controllerProp.value.value.indexOf(' as ') < 0) { - context.report(node, 'State "{{state}}" should use controllerAs syntax', { - state: isObjectState ? stateName : node.arguments[0].value - }); + if (!hasControllerAs && controllerProp) { + // if the controller is a string, controllerAs can be set like 'controller as vm' + if (controllerProp.value.type !== 'Literal' || controllerProp.value.value.indexOf(' as ') < 0) { + context.report(node, 'State "{{state}}" should use controllerAs syntax', { + state: isObjectState ? stateName : node.arguments[0].value + }); + } } } } } - } - }; + }; + } }; diff --git a/rules/controller-as-vm.js b/rules/controller-as-vm.js index faf9f2dd..c1ed9739 100644 --- a/rules/controller-as-vm.js +++ b/rules/controller-as-vm.js @@ -18,84 +18,87 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var badStatements = []; - var badCaptureStatements = []; - var controllerFunctions = []; +module.exports = { + meta: { + schema: [{ + type: 'string' + }, { + type: 'string' + }] + }, + create: function(context) { + var badStatements = []; + var badCaptureStatements = []; + var controllerFunctions = []; - var viewModelName = context.options[0] || 'vm'; - // If your Angular code is written so that controller functions are in - // separate files from your .controller() calls, you can specify a regex for your controller function names - var controllerNameMatcher = context.options[1]; - if (controllerNameMatcher && utils.isStringRegexp(controllerNameMatcher)) { - controllerNameMatcher = utils.convertStringToRegex(controllerNameMatcher); - } + var viewModelName = context.options[0] || 'vm'; + // If your Angular code is written so that controller functions are in + // separate files from your .controller() calls, you can specify a regex for your controller function names + var controllerNameMatcher = context.options[1]; + if (controllerNameMatcher && utils.isStringRegexp(controllerNameMatcher)) { + controllerNameMatcher = utils.convertStringToRegex(controllerNameMatcher); + } - // check node against known controller functions or pattern if specified - function isControllerFunction(node) { - return controllerFunctions.indexOf(node) >= 0 || - (controllerNameMatcher && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') && - node.id && controllerNameMatcher.test(node.id.name)); - } + // check node against known controller functions or pattern if specified + function isControllerFunction(node) { + return controllerFunctions.indexOf(node) >= 0 || + (controllerNameMatcher && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') && + node.id && controllerNameMatcher.test(node.id.name)); + } - // for each of the bad uses, find any parent nodes that are controller functions - function reportBadUses() { - if (controllerFunctions.length > 0 || controllerNameMatcher) { - badCaptureStatements.forEach(function(item) { - item.parents.filter(isControllerFunction).forEach(function() { - context.report(item.stmt, 'You should assign "this" to a consistent variable across your project: {{capture}}', - { - capture: viewModelName - } - ); + // for each of the bad uses, find any parent nodes that are controller functions + function reportBadUses() { + if (controllerFunctions.length > 0 || controllerNameMatcher) { + badCaptureStatements.forEach(function(item) { + item.parents.filter(isControllerFunction).forEach(function() { + context.report(item.stmt, 'You should assign "this" to a consistent variable across your project: {{capture}}', + { + capture: viewModelName + } + ); + }); }); - }); - badStatements.forEach(function(item) { - item.parents.filter(isControllerFunction).forEach(function() { - context.report(item.stmt, 'You should not use "this" directly. Instead, assign it to a variable called "{{capture}}"', - { - capture: viewModelName - } - ); + badStatements.forEach(function(item) { + item.parents.filter(isControllerFunction).forEach(function() { + context.report(item.stmt, 'You should not use "this" directly. Instead, assign it to a variable called "{{capture}}"', + { + capture: viewModelName + } + ); + }); }); - }); + } } - } - function isClassDeclaration(ancestors) { - return ancestors.findIndex(function(ancestor) { - return ancestor.type === 'ClassDeclaration'; - }) > -1; - } + function isClassDeclaration(ancestors) { + return ancestors.findIndex(function(ancestor) { + return ancestor.type === 'ClassDeclaration'; + }) > -1; + } - return { - // Looking for .controller() calls here and getting the associated controller function - 'CallExpression:exit': function(node) { - if (utils.isAngularControllerDeclaration(node)) { - controllerFunctions.push(utils.getControllerDefinition(context, node)); - } - }, - // statements are checked here for bad uses of $scope - ThisExpression: function(stmt) { - var parents = context.getAncestors(); - if (!isClassDeclaration(parents)) { - if (stmt.parent.type === 'VariableDeclarator') { - if (!stmt.parent.id || stmt.parent.id.name !== viewModelName) { - badCaptureStatements.push({parents: parents, stmt: stmt}); + return { + // Looking for .controller() calls here and getting the associated controller function + 'CallExpression:exit': function(node) { + if (utils.isAngularControllerDeclaration(node)) { + controllerFunctions.push(utils.getControllerDefinition(context, node)); + } + }, + // statements are checked here for bad uses of $scope + ThisExpression: function(stmt) { + var parents = context.getAncestors(); + if (!isClassDeclaration(parents)) { + if (stmt.parent.type === 'VariableDeclarator') { + if (!stmt.parent.id || stmt.parent.id.name !== viewModelName) { + badCaptureStatements.push({parents: parents, stmt: stmt}); + } + } else { + badStatements.push({parents: parents, stmt: stmt}); } - } else { - badStatements.push({parents: parents, stmt: stmt}); } + }, + 'Program:exit': function() { + reportBadUses(); } - }, - 'Program:exit': function() { - reportBadUses(); - } - }; + }; + } }; - -module.exports.schema = [{ - type: 'string' -}, { - type: 'string' -}]; diff --git a/rules/controller-as.js b/rules/controller-as.js index 6ac1abe2..819e4f32 100644 --- a/rules/controller-as.js +++ b/rules/controller-as.js @@ -14,64 +14,67 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var badStatements = []; - var controllerFunctions = []; +module.exports = { + meta: { + schema: [{ + type: ['object', 'string'] + }] + }, + create: function(context) { + var badStatements = []; + var controllerFunctions = []; - // If your Angular code is written so that controller functions are in - // separate files from your .controller() calls, you can specify a regex for your controller function names - var controllerNameMatcher = context.options[0]; - if (controllerNameMatcher && utils.isStringRegexp(controllerNameMatcher)) { - controllerNameMatcher = utils.convertStringToRegex(controllerNameMatcher); - } + // If your Angular code is written so that controller functions are in + // separate files from your .controller() calls, you can specify a regex for your controller function names + var controllerNameMatcher = context.options[0]; + if (controllerNameMatcher && utils.isStringRegexp(controllerNameMatcher)) { + controllerNameMatcher = utils.convertStringToRegex(controllerNameMatcher); + } - // check node against known controller functions or pattern if specified - function isControllerFunction(node) { - return controllerFunctions.indexOf(node) >= 0 || - (controllerNameMatcher && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') && - node.id && controllerNameMatcher.test(node.id.name)); - } + // check node against known controller functions or pattern if specified + function isControllerFunction(node) { + return controllerFunctions.indexOf(node) >= 0 || + (controllerNameMatcher && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') && + node.id && controllerNameMatcher.test(node.id.name)); + } - // for each of the bad uses, find any parent nodes that are controller functions - function reportBadUses() { - if (controllerFunctions.length > 0 || controllerNameMatcher) { - badStatements.forEach(function(item) { - item.parents.forEach(function(parent) { - if (isControllerFunction(parent)) { - context.report(item.stmt, 'You should not set properties on $scope in controllers. Use controllerAs syntax and add data to "this"'); - } + // for each of the bad uses, find any parent nodes that are controller functions + function reportBadUses() { + if (controllerFunctions.length > 0 || controllerNameMatcher) { + badStatements.forEach(function(item) { + item.parents.forEach(function(parent) { + if (isControllerFunction(parent)) { + context.report(item.stmt, 'You should not set properties on $scope in controllers. Use controllerAs syntax and add data to "this"'); + } + }); }); - }); + } } - } - return { - // Looking for .controller() calls here and getting the associated controller function - 'CallExpression:exit': function(node) { - if (utils.isAngularControllerDeclaration(node)) { - controllerFunctions.push(utils.getControllerDefinition(context, node)); - } - }, - // statements are checked here for bad uses of $scope - ExpressionStatement: function(stmt) { - if (stmt.expression.type === 'AssignmentExpression' && - stmt.expression.left.object && - stmt.expression.left.object.name === '$scope' && - utils.scopeProperties.indexOf(stmt.expression.left.property.name) < 0) { - badStatements.push({parents: context.getAncestors(), stmt: stmt}); - } else if (stmt.expression.type === 'CallExpression' && - stmt.expression.callee.object && - stmt.expression.callee.object.name === '$scope' && - utils.scopeProperties.indexOf(stmt.expression.callee.property.name) < 0) { - badStatements.push({parents: context.getAncestors(), stmt: stmt}); + return { + // Looking for .controller() calls here and getting the associated controller function + 'CallExpression:exit': function(node) { + if (utils.isAngularControllerDeclaration(node)) { + controllerFunctions.push(utils.getControllerDefinition(context, node)); + } + }, + // statements are checked here for bad uses of $scope + ExpressionStatement: function(stmt) { + if (stmt.expression.type === 'AssignmentExpression' && + stmt.expression.left.object && + stmt.expression.left.object.name === '$scope' && + utils.scopeProperties.indexOf(stmt.expression.left.property.name) < 0) { + badStatements.push({parents: context.getAncestors(), stmt: stmt}); + } else if (stmt.expression.type === 'CallExpression' && + stmt.expression.callee.object && + stmt.expression.callee.object.name === '$scope' && + utils.scopeProperties.indexOf(stmt.expression.callee.property.name) < 0) { + badStatements.push({parents: context.getAncestors(), stmt: stmt}); + } + }, + 'Program:exit': function() { + reportBadUses(); } - }, - 'Program:exit': function() { - reportBadUses(); - } - }; + }; + } }; - -module.exports.schema = [{ - type: ['object', 'string'] -}]; diff --git a/rules/controller-name.js b/rules/controller-name.js index afa485c2..ec715e8e 100644 --- a/rules/controller-name.js +++ b/rules/controller-name.js @@ -15,46 +15,49 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { - - CallExpression: function(node) { - var prefix = context.options[0] || '/^[A-Z][a-zA-Z0-9]*Controller$/'; - var convertedPrefix; // convert string from JSON .eslintrc to regex - - convertedPrefix = utils.convertPrefixToRegex(prefix); - - var callee = node.callee; - if (callee.type === 'MemberExpression' && callee.property.name === 'controller') { - /** - * Allow the usage of element.controller() and element.controller('directiveName') in unittests - */ - if (node.arguments.length < 2) { - return; - } - - var name = node.arguments[0].value; +module.exports = { + meta: { + schema: [ + { + type: 'string' + } + ] + }, + create: function(context) { + return { + + CallExpression: function(node) { + var prefix = context.options[0] || '/^[A-Z][a-zA-Z0-9]*Controller$/'; + var convertedPrefix; // convert string from JSON .eslintrc to regex + + convertedPrefix = utils.convertPrefixToRegex(prefix); + + var callee = node.callee; + if (callee.type === 'MemberExpression' && callee.property.name === 'controller') { + /** + * Allow the usage of element.controller() and element.controller('directiveName') in unittests + */ + if (node.arguments.length < 2) { + return; + } - if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{controller}} controller should be prefixed by {{prefix}}', { - controller: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{controller}} controller should follow this pattern: {{prefix}}', { - controller: name, - prefix: prefix.toString() - }); + var name = node.arguments[0].value; + + if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{controller}} controller should be prefixed by {{prefix}}', { + controller: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{controller}} controller should follow this pattern: {{prefix}}', { + controller: name, + prefix: prefix.toString() + }); + } } } } - } - }; -}; - -module.exports.schema = [ - { - type: 'string' + }; } -]; +}; diff --git a/rules/deferred.js b/rules/deferred.js index 7ca08964..2cf950e7 100644 --- a/rules/deferred.js +++ b/rules/deferred.js @@ -11,17 +11,20 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - MemberExpression: function(node) { - if (node.object.type === 'Identifier' && utils.isAngularServiceImport(node.object.name, '$q')) { - if (node.property.type === 'Identifier' && node.property.name === 'defer') { - context.report(node, 'You should not create a new promise with this syntax. Use the $q(function(resolve, reject) {}) syntax.', {}); + MemberExpression: function(node) { + if (node.object.type === 'Identifier' && utils.isAngularServiceImport(node.object.name, '$q')) { + if (node.property.type === 'Identifier' && node.property.name === 'defer') { + context.report(node, 'You should not create a new promise with this syntax. Use the $q(function(resolve, reject) {}) syntax.', {}); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/rules/definedundefined.js b/rules/definedundefined.js index 1edbede6..7b6f9705 100644 --- a/rules/definedundefined.js +++ b/rules/definedundefined.js @@ -13,44 +13,47 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function isCompareOperator(operator) { - return operator === '===' || operator === '!==' || operator === '==' || operator === '!='; - } - function reportError(node) { - context.report(node, 'You should not use directly the "undefined" keyword. Prefer ' + - 'angular.isUndefined or angular.isDefined', {}); - } - /** - * Rule that check if we use angular.is(Un)defined() instead of the undefined keyword - */ - return { - MemberExpression: function(node) { - if (node.object.name === 'angular' && +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function isCompareOperator(operator) { + return operator === '===' || operator === '!==' || operator === '==' || operator === '!='; + } + function reportError(node) { + context.report(node, 'You should not use directly the "undefined" keyword. Prefer ' + + 'angular.isUndefined or angular.isDefined', {}); + } + /** + * Rule that check if we use angular.is(Un)defined() instead of the undefined keyword + */ + return { + MemberExpression: function(node) { + if (node.object.name === 'angular' && node.parent !== undefined && node.parent.parent !== undefined && node.parent.parent.operator === '!') { - if (node.property.name === 'isDefined') { - context.report(node, 'Instead of !angular.isDefined, you can use the out-of-box angular.isUndefined method', {}); - } else if (node.property.name === 'isUndefined') { - context.report(node, 'Instead of !angular.isUndefined, you can use the out-of-box angular.isDefined method', {}); + if (node.property.name === 'isDefined') { + context.report(node, 'Instead of !angular.isDefined, you can use the out-of-box angular.isUndefined method', {}); + } else if (node.property.name === 'isUndefined') { + context.report(node, 'Instead of !angular.isUndefined, you can use the out-of-box angular.isDefined method', {}); + } } - } - }, - BinaryExpression: function(node) { - if (isCompareOperator(node.operator)) { - if (utils.isTypeOfStatement(node.left) && node.right.value === 'undefined') { - reportError(node); - } else if (utils.isTypeOfStatement(node.right) && node.left.value === 'undefined') { - reportError(node); - } else if (node.left.type === 'Identifier' && node.left.name === 'undefined') { - reportError(node); - } else if (node.right.type === 'Identifier' && node.right.name === 'undefined') { - reportError(node); + }, + BinaryExpression: function(node) { + if (isCompareOperator(node.operator)) { + if (utils.isTypeOfStatement(node.left) && node.right.value === 'undefined') { + reportError(node); + } else if (utils.isTypeOfStatement(node.right) && node.left.value === 'undefined') { + reportError(node); + } else if (node.left.type === 'Identifier' && node.left.name === 'undefined') { + reportError(node); + } else if (node.right.type === 'Identifier' && node.right.name === 'undefined') { + reportError(node); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/rules/di-order.js b/rules/di-order.js index 04a0849a..835f2e8d 100644 --- a/rules/di-order.js +++ b/rules/di-order.js @@ -14,43 +14,52 @@ var angularRule = require('./utils/angular-rule'); var caseSensitive = 'case_sensitive'; -module.exports = angularRule(function(context) { - var stripUnderscores = context.options[0] !== false; - var caseSensitiveOpt = (context.options[1] || caseSensitive) === caseSensitive; +module.exports = { + meta: { + schema: [{ + type: 'boolean' + }, { + type: 'string' + }] + }, + create: angularRule(function(context) { + var stripUnderscores = context.options[0] !== false; + var caseSensitiveOpt = (context.options[1] || caseSensitive) === caseSensitive; - function checkOrder(callee, fn) { - if (!fn || !fn.params) { - return; - } - var args = fn.params.map(function(arg) { - var formattedArg = arg.name; - if (stripUnderscores) { - formattedArg = formattedArg.replace(/^_(.+)_$/, '$1'); - } - return caseSensitiveOpt ? formattedArg : formattedArg.toLowerCase(); - }); - var sortedArgs = args.slice().sort(); - sortedArgs.some(function(value, index) { - if (args.indexOf(value) !== index) { - context.report(fn, 'Injected values should be sorted alphabetically'); - return true; + function checkOrder(callee, fn) { + if (!fn || !fn.params) { + return; } - }); - } - - return { - 'angular:animation': checkOrder, - 'angular:config': checkOrder, - 'angular:controller': checkOrder, - 'angular:directive': checkOrder, - 'angular:factory': checkOrder, - 'angular:filter': checkOrder, - 'angular:inject': checkOrder, - 'angular:run': checkOrder, - 'angular:service': checkOrder, - 'angular:provider': function(callee, providerFn, $get) { - checkOrder(null, providerFn); - checkOrder(null, $get); + var args = fn.params.map(function(arg) { + var formattedArg = arg.name; + if (stripUnderscores) { + formattedArg = formattedArg.replace(/^_(.+)_$/, '$1'); + } + return caseSensitiveOpt ? formattedArg : formattedArg.toLowerCase(); + }); + var sortedArgs = args.slice().sort(); + sortedArgs.some(function(value, index) { + if (args.indexOf(value) !== index) { + context.report(fn, 'Injected values should be sorted alphabetically'); + return true; + } + }); } - }; -}); + + return { + 'angular:animation': checkOrder, + 'angular:config': checkOrder, + 'angular:controller': checkOrder, + 'angular:directive': checkOrder, + 'angular:factory': checkOrder, + 'angular:filter': checkOrder, + 'angular:inject': checkOrder, + 'angular:run': checkOrder, + 'angular:service': checkOrder, + 'angular:provider': function(callee, providerFn, $get) { + checkOrder(null, providerFn); + checkOrder(null, $get); + } + }; + }) +}; diff --git a/rules/di-unused.js b/rules/di-unused.js index 325c4def..2403b596 100644 --- a/rules/di-unused.js +++ b/rules/di-unused.js @@ -12,62 +12,67 @@ var angularRule = require('./utils/angular-rule'); -module.exports = angularRule(function(context) { - // Keeps track of visited scopes in the collectAngularScopes function to prevent infinite recursion on circular references. - var visitedScopes = []; +module.exports = { + meta: { + schema: [] + }, + create: angularRule(function(context) { + // Keeps track of visited scopes in the collectAngularScopes function to prevent infinite recursion on circular references. + var visitedScopes = []; - // This collects the variable scopes for the injectible functions which have been collected. - function collectAngularScopes(scope) { - if (visitedScopes.indexOf(scope) === -1) { - visitedScopes.push(scope); - scope.childScopes.forEach(function(child) { - collectAngularScopes(child); - }); + // This collects the variable scopes for the injectible functions which have been collected. + function collectAngularScopes(scope) { + if (visitedScopes.indexOf(scope) === -1) { + visitedScopes.push(scope); + scope.childScopes.forEach(function(child) { + collectAngularScopes(child); + }); + } } - } - function reportUnusedVariables(callee, fn) { - if (!fn) { - return; - } - visitedScopes.some(function(scope) { - if (scope.block !== fn) { + function reportUnusedVariables(callee, fn) { + if (!fn) { return; } - scope.variables.forEach(function(variable) { - if (variable.name === 'arguments') { - return; - } - if (fn.params.indexOf(variable.identifiers[0]) === -1) { + visitedScopes.some(function(scope) { + if (scope.block !== fn) { return; } - if (variable.references.length === 0) { - context.report(fn, 'Unused injected value {{name}}', variable); - } + scope.variables.forEach(function(variable) { + if (variable.name === 'arguments') { + return; + } + if (fn.params.indexOf(variable.identifiers[0]) === -1) { + return; + } + if (variable.references.length === 0) { + context.report(fn, 'Unused injected value {{name}}', variable); + } + }); + return true; }); - return true; - }); - } + } - return { - 'angular:animation': reportUnusedVariables, - 'angular:config': reportUnusedVariables, - 'angular:controller': reportUnusedVariables, - 'angular:directive': reportUnusedVariables, - 'angular:factory': reportUnusedVariables, - 'angular:filter': reportUnusedVariables, - 'angular:inject': reportUnusedVariables, - 'angular:run': reportUnusedVariables, - 'angular:service': reportUnusedVariables, - 'angular:provider': function(callee, providerFn, $get) { - reportUnusedVariables(null, providerFn); - reportUnusedVariables(null, $get); - }, + return { + 'angular:animation': reportUnusedVariables, + 'angular:config': reportUnusedVariables, + 'angular:controller': reportUnusedVariables, + 'angular:directive': reportUnusedVariables, + 'angular:factory': reportUnusedVariables, + 'angular:filter': reportUnusedVariables, + 'angular:inject': reportUnusedVariables, + 'angular:run': reportUnusedVariables, + 'angular:service': reportUnusedVariables, + 'angular:provider': function(callee, providerFn, $get) { + reportUnusedVariables(null, providerFn); + reportUnusedVariables(null, $get); + }, - // Actually find and report unused injected variables. - 'Program:exit': function() { - var globalScope = context.getScope(); - collectAngularScopes(globalScope); - } - }; -}); + // Actually find and report unused injected variables. + 'Program:exit': function() { + var globalScope = context.getScope(); + collectAngularScopes(globalScope); + } + }; + }) +}; diff --git a/rules/di.js b/rules/di.js index 50690962..69485df8 100644 --- a/rules/di.js +++ b/rules/di.js @@ -14,122 +14,125 @@ var utils = require('./utils/utils'); var angularRule = require('./utils/angular-rule'); -module.exports = angularRule(function(context) { - var syntax = context.options[0] || 'function'; - - var extra = context.options[1] || {}; - var matchNames = extra.matchNames !== false; - - function report(node) { - context.report(node, 'You should use the {{syntax}} syntax for DI', { - syntax: syntax - }); - } - - var $injectProperties = {}; - - function maybeNoteInjection(node) { - if (syntax === '$inject' && node.left && node.left.property && - ((utils.isLiteralType(node.left.property) && node.left.property.value === '$inject') || - (utils.isIdentifierType(node.left.property) && node.left.property.name === '$inject'))) { - $injectProperties[node.left.object.name] = node.right; - } - } - - function checkDi(callee, fn) { - if (!fn) { - return; - } - - if (syntax === 'array') { - if (utils.isArrayType(fn.parent)) { - if (fn.parent.elements.length - 1 !== fn.params.length) { - context.report(fn, 'The signature of the method is incorrect', {}); - return; - } - - if (matchNames) { - var invalidArray = fn.params.filter(function(e, i) { - return e.name !== fn.parent.elements[i].value; - }); - if (invalidArray.length > 0) { - context.report(fn, 'You have an error in your DI configuration. Each items of the array should match exactly one function parameter', {}); - return; - } +module.exports = { + meta: { + schema: [{ + enum: [ + 'function', + 'array', + '$inject' + ] + }, { + type: 'object', + properties: { + matchNames: { + type: 'boolean' } - } else { - if (fn.params.length === 0) { - return; - } - report(fn); } + }] + }, + create: angularRule(function(context) { + var syntax = context.options[0] || 'function'; + + var extra = context.options[1] || {}; + var matchNames = extra.matchNames !== false; + + function report(node) { + context.report(node, 'You should use the {{syntax}} syntax for DI', { + syntax: syntax + }); } - if (syntax === 'function') { - if (utils.isArrayType(fn.parent)) { - report(fn); + var $injectProperties = {}; + + function maybeNoteInjection(node) { + if (syntax === '$inject' && node.left && node.left.property && + ((utils.isLiteralType(node.left.property) && node.left.property.value === '$inject') || + (utils.isIdentifierType(node.left.property) && node.left.property.name === '$inject'))) { + $injectProperties[node.left.object.name] = node.right; } } - if (syntax === '$inject') { - if (fn && fn.id && utils.isIdentifierType(fn.id)) { - var $injectArray = $injectProperties[fn.id.name]; + function checkDi(callee, fn) { + if (!fn) { + return; + } - if ($injectArray && utils.isArrayType($injectArray)) { - if ($injectArray.elements.length !== fn.params.length) { + if (syntax === 'array') { + if (utils.isArrayType(fn.parent)) { + if (fn.parent.elements.length - 1 !== fn.params.length) { context.report(fn, 'The signature of the method is incorrect', {}); return; } if (matchNames) { - var invalidInjectArray = fn.params.filter(function(e, i) { - return e.name !== $injectArray.elements[i].value; + var invalidArray = fn.params.filter(function(e, i) { + return e.name !== fn.parent.elements[i].value; }); - if (invalidInjectArray.length > 0) { + if (invalidArray.length > 0) { context.report(fn, 'You have an error in your DI configuration. Each items of the array should match exactly one function parameter', {}); return; } } } else { + if (fn.params.length === 0) { + return; + } + report(fn); + } + } + + if (syntax === 'function') { + if (utils.isArrayType(fn.parent)) { + report(fn); + } + } + + if (syntax === '$inject') { + if (fn && fn.id && utils.isIdentifierType(fn.id)) { + var $injectArray = $injectProperties[fn.id.name]; + + if ($injectArray && utils.isArrayType($injectArray)) { + if ($injectArray.elements.length !== fn.params.length) { + context.report(fn, 'The signature of the method is incorrect', {}); + return; + } + + if (matchNames) { + var invalidInjectArray = fn.params.filter(function(e, i) { + return e.name !== $injectArray.elements[i].value; + }); + if (invalidInjectArray.length > 0) { + context.report(fn, 'You have an error in your DI configuration. Each items of the array should match exactly one function parameter', {}); + return; + } + } + } else { + report(fn); + } + } else if (fn.params && fn.params.length !== 0) { report(fn); } - } else if (fn.params && fn.params.length !== 0) { - report(fn); } } - } - - return { - 'angular:animation': checkDi, - 'angular:config': checkDi, - 'angular:controller': checkDi, - 'angular:directive': checkDi, - 'angular:factory': checkDi, - 'angular:filter': checkDi, - 'angular:inject': checkDi, - 'angular:run': checkDi, - 'angular:service': checkDi, - 'angular:provider': function(callee, providerFn, $get) { - checkDi(null, providerFn); - checkDi(null, $get); - }, - AssignmentExpression: function(node) { - maybeNoteInjection(node); - } - }; -}); - -module.exports.schema = [{ - enum: [ - 'function', - 'array', - '$inject' - ] -}, { - type: 'object', - properties: { - matchNames: { - type: 'boolean' - } - } -}]; + + return { + 'angular:animation': checkDi, + 'angular:config': checkDi, + 'angular:controller': checkDi, + 'angular:directive': checkDi, + 'angular:factory': checkDi, + 'angular:filter': checkDi, + 'angular:inject': checkDi, + 'angular:run': checkDi, + 'angular:service': checkDi, + 'angular:provider': function(callee, providerFn, $get) { + checkDi(null, providerFn); + checkDi(null, $get); + }, + AssignmentExpression: function(node) { + maybeNoteInjection(node); + } + }; + }) +}; diff --git a/rules/directive-name.js b/rules/directive-name.js index 5b5ba933..52e6335e 100644 --- a/rules/directive-name.js +++ b/rules/directive-name.js @@ -15,44 +15,51 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - if (context.settings.angular === 2) { - return {}; - } +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }] + }, + create: function(context) { + if (context.settings.angular === 2) { + return {}; + } - return { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex - if (prefix === undefined) { - return; - } + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); + convertedPrefix = utils.convertPrefixToRegex(prefix); - if (utils.isAngularDirectiveDeclaration(node)) { - var name = node.arguments[0].value; + if (utils.isAngularDirectiveDeclaration(node)) { + var name = node.arguments[0].value; - if (name !== undefined && name.indexOf('ng') === 0) { - context.report(node, 'The {{directive}} directive should not start with "ng". This is reserved for AngularJS directives', { - directive: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{directive}} directive should be prefixed by {{prefix}}', { - directive: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{directive}} directive should follow this pattern: {{prefix}}', { - directive: name, - prefix: prefix.toString() + if (name !== undefined && name.indexOf('ng') === 0) { + context.report(node, 'The {{directive}} directive should not start with "ng". This is reserved for AngularJS directives', { + directive: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{directive}} directive should be prefixed by {{prefix}}', { + directive: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{directive}} directive should follow this pattern: {{prefix}}', { + directive: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/directive-restrict.js b/rules/directive-restrict.js index 502cc7ec..7311e6c6 100644 --- a/rules/directive-restrict.js +++ b/rules/directive-restrict.js @@ -15,92 +15,95 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var options = context.options[0] || {}; - var restrictOpt = options.restrict || 'AE'; - var explicitRestrict = options.explicit === 'always'; - var restrictChars = restrictOpt.split(''); - - // Example RegExp for AE: /^A?E?$/ - var restrictRegExp = new RegExp('^' + restrictChars.join('?') + '?$'); - var foundDirectives = []; - var checkedDirectives = []; - var defaultRestrictions = ['AE', 'EA']; - - function checkLiteralNode(node) { - if (node.type !== 'Literal') { - return; - } - var directiveNode; - context.getAncestors().some(function(ancestor) { - if (utils.isAngularDirectiveDeclaration(ancestor)) { - directiveNode = ancestor; - return true; +module.exports = { + meta: { + schema: [{ + type: 'object', + properties: { + restrict: { + type: 'string', + pattern: '^A|C|E|(AC)|(CA)|(AE)|(EA)|(EC)|(CE)|(AEC)|(ACE)|(EAC)|(CAE)|(ACE)|(AEC)|(CAE)|(ACE)|(AEC)$' + }, + explicit: { + enum: ['always', 'never'] + } } - }); - // The restrict property was not defined inside of a directive. - if (!directiveNode) { - return; - } - if (!explicitRestrict && defaultRestrictions.indexOf(node.value) !== -1) { - context.report(node, 'No need to explicitly specify a default directive restriction'); - return; - } + }] + }, + create: function(context) { + var options = context.options[0] || {}; + var restrictOpt = options.restrict || 'AE'; + var explicitRestrict = options.explicit === 'always'; + var restrictChars = restrictOpt.split(''); - if (!restrictRegExp.test(node.value)) { - context.report(directiveNode, 'Disallowed directive restriction. It must be one of {{allowed}} in that order', { - allowed: restrictOpt - }); - } - - checkedDirectives.push(directiveNode); - } + // Example RegExp for AE: /^A?E?$/ + var restrictRegExp = new RegExp('^' + restrictChars.join('?') + '?$'); + var foundDirectives = []; + var checkedDirectives = []; + var defaultRestrictions = ['AE', 'EA']; - return { - CallExpression: function(node) { - if (utils.isAngularDirectiveDeclaration(node)) { - foundDirectives.push(node); - } - }, - AssignmentExpression: function(node) { - // Only check for literal member property assignments. - if (node.left.type !== 'MemberExpression') { + function checkLiteralNode(node) { + if (node.type !== 'Literal') { return; } - // Only check setting properties named 'restrict'. - if (node.left.property.name !== 'restrict') { + var directiveNode; + context.getAncestors().some(function(ancestor) { + if (utils.isAngularDirectiveDeclaration(ancestor)) { + directiveNode = ancestor; + return true; + } + }); + // The restrict property was not defined inside of a directive. + if (!directiveNode) { return; } - checkLiteralNode(node.right); - }, - Property: function(node) { - // This only checks for objects which have defined a literal restrict property. - if (node.key.name !== 'restrict') { + if (!explicitRestrict && defaultRestrictions.indexOf(node.value) !== -1) { + context.report(node, 'No need to explicitly specify a default directive restriction'); return; } - checkLiteralNode(node.value); - }, - 'Program:exit': function() { - if (explicitRestrict) { - foundDirectives.filter(function(directive) { - return checkedDirectives.indexOf(directive) < 0; - }).forEach(function(directiveNode) { - context.report(directiveNode, 'Missing directive restriction'); + + if (!restrictRegExp.test(node.value)) { + context.report(directiveNode, 'Disallowed directive restriction. It must be one of {{allowed}} in that order', { + allowed: restrictOpt }); } - } - }; -}; -module.exports.schema = [{ - type: 'object', - properties: { - restrict: { - type: 'string', - pattern: '^A|C|E|(AC)|(CA)|(AE)|(EA)|(EC)|(CE)|(AEC)|(ACE)|(EAC)|(CAE)|(ACE)|(AEC)|(CAE)|(ACE)|(AEC)$' - }, - explicit: { - enum: ['always', 'never'] + checkedDirectives.push(directiveNode); } + + return { + CallExpression: function(node) { + if (utils.isAngularDirectiveDeclaration(node)) { + foundDirectives.push(node); + } + }, + AssignmentExpression: function(node) { + // Only check for literal member property assignments. + if (node.left.type !== 'MemberExpression') { + return; + } + // Only check setting properties named 'restrict'. + if (node.left.property.name !== 'restrict') { + return; + } + checkLiteralNode(node.right); + }, + Property: function(node) { + // This only checks for objects which have defined a literal restrict property. + if (node.key.name !== 'restrict') { + return; + } + checkLiteralNode(node.value); + }, + 'Program:exit': function() { + if (explicitRestrict) { + foundDirectives.filter(function(directive) { + return checkedDirectives.indexOf(directive) < 0; + }).forEach(function(directiveNode) { + context.report(directiveNode, 'Missing directive restriction'); + }); + } + } + }; } -}]; +}; diff --git a/rules/document-service.js b/rules/document-service.js index 26f9f742..d9fb1103 100644 --- a/rules/document-service.js +++ b/rules/document-service.js @@ -10,16 +10,17 @@ */ 'use strict'; -module.exports = function(context) { - return { - MemberExpression: function(node) { - if (node.object.name === 'document' || (node.object.name === 'window' && node.property.name === 'document')) { - context.report(node, 'You should use the $document service instead of the default document object', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { + MemberExpression: function(node) { + if (node.object.name === 'document' || (node.object.name === 'window' && node.property.name === 'document')) { + context.report(node, 'You should use the $document service instead of the default document object', {}); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/dumb-inject.js b/rules/dumb-inject.js index 4cd7e293..e2d310ce 100644 --- a/rules/dumb-inject.js +++ b/rules/dumb-inject.js @@ -13,60 +13,65 @@ var angularRule = require('./utils/angular-rule'); -module.exports = angularRule(function(context) { - function report(node, name) { - context.report(node, 'inject functions may only consist of assignments in the form {{name}} = _{{name}}_', { - name: name || 'myService' - }); - } - - return { - 'angular:inject': function(callExpression, fn) { - if (!fn) { - return; - } - var valid = []; - // Report bad statement types - fn.body.body.forEach(function(statement) { - if (statement.type !== 'ExpressionStatement') { - return report(statement); - } - if (statement.expression.type !== 'AssignmentExpression') { - return report(statement); - } - if (statement.expression.right.type !== 'Identifier') { - return report(statement); - } - // From this point there is more context on what to report. - var name = statement.expression.right.name.replace(/^_(.+)_$/, '$1'); - if (statement.expression.left.type !== 'Identifier') { - return report(statement, name); - } - if (statement.expression.right.name !== '_' + name + '_') { - return report(statement, name); - } - if (statement.expression.left.name !== name) { - return report(statement, name); - } - // Register valid statements for sort order validation - valid.push(statement); +module.exports = { + meta: { + schema: [] + }, + create: angularRule(function(context) { + function report(node, name) { + context.report(node, 'inject functions may only consist of assignments in the form {{name}} = _{{name}}_', { + name: name || 'myService' }); - // Validate the sorting order - var lastValid; - valid.forEach(function(statement) { - if (!lastValid) { - lastValid = statement.expression.left.name; - return; - } - if (statement.expression.left.name.localeCompare(lastValid) !== -1) { - lastValid = statement.expression.left.name; + } + + return { + 'angular:inject': function(callExpression, fn) { + if (!fn) { return; } - context.report(statement, "'{{current}}' must be sorted before '{{previous}}'", { - current: statement.expression.left.name, - previous: lastValid + var valid = []; + // Report bad statement types + fn.body.body.forEach(function(statement) { + if (statement.type !== 'ExpressionStatement') { + return report(statement); + } + if (statement.expression.type !== 'AssignmentExpression') { + return report(statement); + } + if (statement.expression.right.type !== 'Identifier') { + return report(statement); + } + // From this point there is more context on what to report. + var name = statement.expression.right.name.replace(/^_(.+)_$/, '$1'); + if (statement.expression.left.type !== 'Identifier') { + return report(statement, name); + } + if (statement.expression.right.name !== '_' + name + '_') { + return report(statement, name); + } + if (statement.expression.left.name !== name) { + return report(statement, name); + } + // Register valid statements for sort order validation + valid.push(statement); }); - }); - } - }; -}); + // Validate the sorting order + var lastValid; + valid.forEach(function(statement) { + if (!lastValid) { + lastValid = statement.expression.left.name; + return; + } + if (statement.expression.left.name.localeCompare(lastValid) !== -1) { + lastValid = statement.expression.left.name; + return; + } + context.report(statement, "'{{current}}' must be sorted before '{{previous}}'", { + current: statement.expression.left.name, + previous: lastValid + }); + }); + } + }; + }) +}; diff --git a/rules/empty-controller.js b/rules/empty-controller.js index 10cda2c1..4e555824 100644 --- a/rules/empty-controller.js +++ b/rules/empty-controller.js @@ -12,31 +12,32 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function report(node, name) { - context.report(node, 'The {{ctrl}} controller is useless because empty. You can remove it from your Router configuration or in one of your view', { - ctrl: name - }); - } +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function report(node, name) { + context.report(node, 'The {{ctrl}} controller is useless because empty. You can remove it from your Router configuration or in one of your view', { + ctrl: name + }); + } - return { + return { - CallExpression: function(node) { - if (utils.isAngularControllerDeclaration(node)) { - var name = node.arguments[0].value; + CallExpression: function(node) { + if (utils.isAngularControllerDeclaration(node)) { + var name = node.arguments[0].value; - var fn = node.arguments[1]; - if (utils.isArrayType(node.arguments[1])) { - fn = node.arguments[1].elements[node.arguments[1].elements.length - 1]; - } - if (utils.isFunctionType(fn) && utils.isEmptyFunction(fn)) { - report(node, name); + var fn = node.arguments[1]; + if (utils.isArrayType(node.arguments[1])) { + fn = node.arguments[1].elements[node.arguments[1].elements.length - 1]; + } + if (utils.isFunctionType(fn) && utils.isEmptyFunction(fn)) { + report(node, name); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/factory-name.js b/rules/factory-name.js index 823bc586..6f3391c3 100644 --- a/rules/factory-name.js +++ b/rules/factory-name.js @@ -15,42 +15,51 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }, { + type: 'object' + }] + }, + create: function(context) { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex - var isFactory; + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex + var isFactory; - if (prefix === undefined) { - return; - } + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); - isFactory = utils.isAngularFactoryDeclaration(node); - - if (isFactory) { - var name = node.arguments[0].value; - - if (name !== undefined && name.indexOf('$') === 0) { - context.report(node, 'The {{factory}} factory should not start with "$". This is reserved for AngularJS services', { - factory: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{factory}} factory should be prefixed by {{prefix}}', { - factory: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{factory}} factory should follow this pattern: {{prefix}}', { - factory: name, - prefix: prefix.toString() + convertedPrefix = utils.convertPrefixToRegex(prefix); + isFactory = utils.isAngularFactoryDeclaration(node); + + if (isFactory) { + var name = node.arguments[0].value; + + if (name !== undefined && name.indexOf('$') === 0) { + context.report(node, 'The {{factory}} factory should not start with "$". This is reserved for AngularJS services', { + factory: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{factory}} factory should be prefixed by {{prefix}}', { + factory: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{factory}} factory should follow this pattern: {{prefix}}', { + factory: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/file-name.js b/rules/file-name.js index a8f45e92..cceb6cc5 100644 --- a/rules/file-name.js +++ b/rules/file-name.js @@ -23,111 +23,118 @@ var path = require('path'); var utils = require('./utils/utils'); -module.exports = (function() { - var fileEnding = '.js'; +module.exports = { + meta: { + schema: [{ + type: ['object'] + }] + }, + create: (function() { + var fileEnding = '.js'; - var separators = { - dot: '.', - dash: '-', - underscore: '_' - }; + var separators = { + dot: '.', + dash: '-', + underscore: '_' + }; - function createComponentTypeMappings(options) { - var componentTypeMappingOptions = options.componentTypeMappings || {}; + function createComponentTypeMappings(options) { + var componentTypeMappingOptions = options.componentTypeMappings || {}; - return { - module: componentTypeMappingOptions.module || 'module', - controller: componentTypeMappingOptions.controller || 'controller', - directive: componentTypeMappingOptions.directive || 'directive', - filter: componentTypeMappingOptions.filter || 'filter', - service: componentTypeMappingOptions.service || 'service', - factory: componentTypeMappingOptions.factory || 'service', - provider: componentTypeMappingOptions.provider || 'service', - value: componentTypeMappingOptions.value || 'service', - constant: componentTypeMappingOptions.constant || 'constant', - component: componentTypeMappingOptions.component || 'component' - }; - } + return { + module: componentTypeMappingOptions.module || 'module', + controller: componentTypeMappingOptions.controller || 'controller', + directive: componentTypeMappingOptions.directive || 'directive', + filter: componentTypeMappingOptions.filter || 'filter', + service: componentTypeMappingOptions.service || 'service', + factory: componentTypeMappingOptions.factory || 'service', + provider: componentTypeMappingOptions.provider || 'service', + value: componentTypeMappingOptions.value || 'service', + constant: componentTypeMappingOptions.constant || 'constant', + component: componentTypeMappingOptions.component || 'component' + }; + } - var filenameUtil = { - firstToUpper: function(value) { - return value[0].toUpperCase() + value.slice(1); - }, - firstToLower: function(value) { - return value[0].toLowerCase() + value.slice(1); - }, - removeTypeSuffix: function(name, type) { - var nameTypeLengthDiff = name.length - type.length; - if (nameTypeLengthDiff <= 0) { + var filenameUtil = { + firstToUpper: function(value) { + return value[0].toUpperCase() + value.slice(1); + }, + firstToLower: function(value) { + return value[0].toLowerCase() + value.slice(1); + }, + removeTypeSuffix: function(name, type) { + var nameTypeLengthDiff = name.length - type.length; + if (nameTypeLengthDiff <= 0) { + return name; + } + var typeCamelCase = this.firstToUpper(type); + if (name.indexOf(typeCamelCase) === nameTypeLengthDiff) { + return name.slice(0, nameTypeLengthDiff); + } return name; - } - var typeCamelCase = this.firstToUpper(type); - if (name.indexOf(typeCamelCase) === nameTypeLengthDiff) { - return name.slice(0, nameTypeLengthDiff); - } - return name; - }, - removePrefix: function(name, options) { - var regName = '^' + options.ignorePrefix.replace(/[\.]/g, '\\$&'); - regName += options.ignorePrefix.indexOf('\.') === -1 ? '[A-Z]' : '[a-zA-z]'; - if (new RegExp(regName).test(name)) { - return this.firstToLower(name.slice(options.ignorePrefix.length)); - } - return name; - }, - transformComponentName: function(name, options) { - var nameStyle = options.nameStyle; - var nameSeparator = separators[nameStyle]; - if (nameSeparator) { - var replacement = '$1' + nameSeparator + '$2'; - name = name.replace(/([a-z0-9])([A-Z])/g, replacement).toLowerCase(); - } - return name; - }, - createExpectedName: function(name, type, options) { - var typeSeparator = separators[options.typeSeparator]; + }, + removePrefix: function(name, options) { + var regName = '^' + options.ignorePrefix.replace(/[\.]/g, '\\$&'); + regName += options.ignorePrefix.indexOf('\.') === -1 ? '[A-Z]' : '[a-zA-z]'; + if (new RegExp(regName).test(name)) { + return this.firstToLower(name.slice(options.ignorePrefix.length)); + } + return name; + }, + transformComponentName: function(name, options) { + var nameStyle = options.nameStyle; + var nameSeparator = separators[nameStyle]; + if (nameSeparator) { + var replacement = '$1' + nameSeparator + '$2'; + name = name.replace(/([a-z0-9])([A-Z])/g, replacement).toLowerCase(); + } + return name; + }, + createExpectedName: function(name, type, options) { + var typeSeparator = separators[options.typeSeparator]; - if (options.ignoreTypeSuffix) { - name = filenameUtil.removeTypeSuffix(name, type); - } - if (options.ignorePrefix && options.ignorePrefix.length > 0) { - name = filenameUtil.removePrefix(name, options); - } - if (options.nameStyle) { - name = filenameUtil.transformComponentName(name, options); - } - if (typeSeparator !== undefined) { - name = name + typeSeparator + type; + if (options.ignoreTypeSuffix) { + name = filenameUtil.removeTypeSuffix(name, type); + } + if (options.ignorePrefix && options.ignorePrefix.length > 0) { + name = filenameUtil.removePrefix(name, options); + } + if (options.nameStyle) { + name = filenameUtil.transformComponentName(name, options); + } + if (typeSeparator !== undefined) { + name = name + typeSeparator + type; + } + return name + fileEnding; } - return name + fileEnding; - } - }; + }; - return function(context) { - var options = context.options[0] || {}; - var filename = path.basename(context.getFilename()); - var componentTypeMappings = createComponentTypeMappings(options); + return function(context) { + var options = context.options[0] || {}; + var filename = path.basename(context.getFilename()); + var componentTypeMappings = createComponentTypeMappings(options); - return { - CallExpression: function(node) { - if (utils.isAngularComponent(node) && utils.isMemberExpression(node.callee)) { - var name = node.arguments[0].value; - var type = componentTypeMappings[node.callee.property.name]; - var expectedName; + return { + CallExpression: function(node) { + if (utils.isAngularComponent(node) && utils.isMemberExpression(node.callee)) { + var name = node.arguments[0].value; + var type = componentTypeMappings[node.callee.property.name]; + var expectedName; - if (type === undefined || (type === 'service' && node.callee.object.name === '$provide')) { - return; - } + if (type === undefined || (type === 'service' && node.callee.object.name === '$provide')) { + return; + } - expectedName = filenameUtil.createExpectedName(name, type, options); + expectedName = filenameUtil.createExpectedName(name, type, options); - if (expectedName !== filename) { - context.report(node, 'Filename must be "{{expectedName}}"', { - expectedName: expectedName - }); + if (expectedName !== filename) { + context.report(node, 'Filename must be "{{expectedName}}"', { + expectedName: expectedName + }); + } } } - } + }; }; - }; -}()); + }()) +}; diff --git a/rules/filter-name.js b/rules/filter-name.js index 11104cd5..0840362a 100644 --- a/rules/filter-name.js +++ b/rules/filter-name.js @@ -13,35 +13,43 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }] + }, + create: function(context) { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex; - if (prefix === undefined) { - return; - } + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex; + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); + convertedPrefix = utils.convertPrefixToRegex(prefix); - if (utils.isAngularFilterDeclaration(node)) { - var name = node.arguments[0].value; + if (utils.isAngularFilterDeclaration(node)) { + var name = node.arguments[0].value; - if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{filter}} filter should be prefixed by {{prefix}}', { - filter: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{filter}} filter should follow this pattern: {{prefix}}', { - filter: name, - prefix: prefix.toString() - }); + if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{filter}} filter should be prefixed by {{prefix}}', { + filter: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{filter}} filter should follow this pattern: {{prefix}}', { + filter: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; + diff --git a/rules/foreach.js b/rules/foreach.js index 07412a3a..9ba523fa 100644 --- a/rules/foreach.js +++ b/rules/foreach.js @@ -9,16 +9,17 @@ */ 'use strict'; -module.exports = function(context) { - return { - MemberExpression: function(node) { - if (node.object.type === 'Identifier' && node.object.name !== 'angular' && node.property.name === 'forEach') { - context.report(node, 'You should use the angular.forEach method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { + MemberExpression: function(node) { + if (node.object.type === 'Identifier' && node.object.name !== 'angular' && node.property.name === 'forEach') { + context.report(node, 'You should use the angular.forEach method', {}); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/function-type.js b/rules/function-type.js index 7917110f..2f82c3f8 100644 --- a/rules/function-type.js +++ b/rules/function-type.js @@ -15,58 +15,61 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var angularObjectList = ['animation', 'config', 'constant', 'controller', 'directive', 'factory', 'filter', 'provider', 'service', 'value', 'decorator']; - var configType = context.options[0] || 'anonymous'; - var messageByConfigType = { - anonymous: 'Use anonymous functions instead of named function', - named: 'Use named functions instead of anonymous function' - }; - var message = messageByConfigType[configType]; - - if (context.options[1]) { - angularObjectList = context.options[1]; - } +module.exports = { + meta: { + schema: [{ + enum: [ + 'named', + 'anonymous' + ] + }, { + type: 'array', + items: { + type: 'string' + } + }] + }, + create: function(context) { + var angularObjectList = ['animation', 'config', 'constant', 'controller', 'directive', 'factory', 'filter', 'provider', 'service', 'value', 'decorator']; + var configType = context.options[0] || 'anonymous'; + var messageByConfigType = { + anonymous: 'Use anonymous functions instead of named function', + named: 'Use named functions instead of anonymous function' + }; + var message = messageByConfigType[configType]; - function checkType(arg) { - return utils.isCallExpression(arg) || - (configType === 'named' && (utils.isIdentifierType(arg) || utils.isNamedInlineFunction(arg))) || - (configType === 'anonymous' && utils.isFunctionType(arg) && !utils.isNamedInlineFunction(arg)); - } + if (context.options[1]) { + angularObjectList = context.options[1]; + } - return { + function checkType(arg) { + return utils.isCallExpression(arg) || + (configType === 'named' && (utils.isIdentifierType(arg) || utils.isNamedInlineFunction(arg))) || + (configType === 'anonymous' && utils.isFunctionType(arg) && !utils.isNamedInlineFunction(arg)); + } - CallExpression: function(node) { - var callee = node.callee; - var angularObjectName = callee.property && callee.property.name; - var firstArgument = node.arguments[1]; + return { - if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(angularObjectName) >= 0) { - if (checkType(firstArgument)) { - return; - } + CallExpression: function(node) { + var callee = node.callee; + var angularObjectName = callee.property && callee.property.name; + var firstArgument = node.arguments[1]; - if (utils.isArrayType(firstArgument)) { - var last = firstArgument.elements[firstArgument.elements.length - 1]; - if (checkType(last) || (!utils.isFunctionType(last) && !utils.isIdentifierType(last))) { + if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(angularObjectName) >= 0) { + if (checkType(firstArgument)) { return; } - } - context.report(node, message, {}); - } - } - }; -}; + if (utils.isArrayType(firstArgument)) { + var last = firstArgument.elements[firstArgument.elements.length - 1]; + if (checkType(last) || (!utils.isFunctionType(last) && !utils.isIdentifierType(last))) { + return; + } + } -module.exports.schema = [{ - enum: [ - 'named', - 'anonymous' - ] -}, { - type: 'array', - items: { - type: 'string' + context.report(node, message, {}); + } + } + }; } -}]; +}; diff --git a/rules/interval-service.js b/rules/interval-service.js index ccd6a7c0..1288739e 100644 --- a/rules/interval-service.js +++ b/rules/interval-service.js @@ -10,25 +10,26 @@ */ 'use strict'; -module.exports = function(context) { - var message = 'You should use the $interval service instead of the default window.setInterval method'; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + var message = 'You should use the $interval service instead of the default window.setInterval method'; - return { + return { - MemberExpression: function(node) { - if (node.object.name === 'window' && node.property.name === 'setInterval') { - context.report(node, message, {}); - } - }, + MemberExpression: function(node) { + if (node.object.name === 'window' && node.property.name === 'setInterval') { + context.report(node, message, {}); + } + }, - CallExpression: function(node) { - if (node.callee.name === 'setInterval') { - context.report(node, message, {}); + CallExpression: function(node) { + if (node.callee.name === 'setInterval') { + context.report(node, message, {}); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/json-functions.js b/rules/json-functions.js index ad6932c1..c4823c63 100644 --- a/rules/json-functions.js +++ b/rules/json-functions.js @@ -10,21 +10,22 @@ */ 'use strict'; -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - MemberExpression: function(node) { - if (node.object.name === 'JSON') { - if (node.property.name === 'stringify') { - context.report(node, 'You should use the angular.toJson method instead of JSON.stringify', {}); - } else if (node.property.name === 'parse') { - context.report(node, 'You should use the angular.fromJson method instead of JSON.parse', {}); + MemberExpression: function(node) { + if (node.object.name === 'JSON') { + if (node.property.name === 'stringify') { + context.report(node, 'You should use the angular.toJson method instead of JSON.stringify', {}); + } else if (node.property.name === 'parse') { + context.report(node, 'You should use the angular.fromJson method instead of JSON.parse', {}); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/log.js b/rules/log.js index 3322bf39..1eca8c6d 100644 --- a/rules/log.js +++ b/rules/log.js @@ -8,19 +8,20 @@ */ 'use strict'; -module.exports = function(context) { - var method = ['log', 'debug', 'error', 'info', 'warn']; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + var method = ['log', 'debug', 'error', 'info', 'warn']; - return { + return { - MemberExpression: function(node) { - if (node.object.name === 'console' && method.indexOf(node.property.name) >= 0) { - context.report(node, 'You should use the "' + node.property.name + '" method of the AngularJS Service $log instead of the console object'); + MemberExpression: function(node) { + if (node.object.name === 'console' && method.indexOf(node.property.name) >= 0) { + context.report(node, 'You should use the "' + node.property.name + '" method of the AngularJS Service $log instead of the console object'); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/module-dependency-order.js b/rules/module-dependency-order.js index 037083a0..f5878bad 100644 --- a/rules/module-dependency-order.js +++ b/rules/module-dependency-order.js @@ -17,91 +17,131 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var options = context.options[0] || {}; - var groupedMode = options.grouped !== false; - var moduleRegex; - if (groupedMode) { - moduleRegex = utils.convertPrefixToRegex(options.prefix); - } +module.exports = { + meta: { + schema: [{ + type: 'object', + properties: { + grouped: { + type: 'boolean' + }, + prefix: { + type: ['string', 'null'] + } + } + }] + }, + create: function(context) { + var options = context.options[0] || {}; + var groupedMode = options.grouped !== false; + var moduleRegex; + if (groupedMode) { + moduleRegex = utils.convertPrefixToRegex(options.prefix); + } - var standard = [ - // Libraries in the angular.js repository - 'ng', - 'ngAnimate', - 'ngAria', - 'ngCookies', - 'ngLocale', - 'ngMessageFormat', - 'ngMessages', - 'ngMock', - 'ngResource', - 'ngRoute', - 'ngSanitize', - 'ngTouch', + var standard = [ + // Libraries in the angular.js repository + 'ng', + 'ngAnimate', + 'ngAria', + 'ngCookies', + 'ngLocale', + 'ngMessageFormat', + 'ngMessages', + 'ngMock', + 'ngResource', + 'ngRoute', + 'ngSanitize', + 'ngTouch', - // Libraries maintained by the angular team, but in another repository - 'ngMaterial', - 'ngNewRouter' - ]; + // Libraries maintained by the angular team, but in another repository + 'ngMaterial', + 'ngNewRouter' + ]; - function checkLiteral(node) { - if (node && node.type !== 'Literal') { - context.report(node, 'Unexpected non-literal value'); - return false; - } - if (!node) { - return false; - } - return true; - } - - function checkCombined(deps) { - var lastCorrect; - deps.elements.forEach(function(node) { - if (!checkLiteral(node)) { - return; + function checkLiteral(node) { + if (node && node.type !== 'Literal') { + context.report(node, 'Unexpected non-literal value'); + return false; } - var value = node.value; - if (lastCorrect === undefined || lastCorrect.localeCompare(value) < 0) { - lastCorrect = value; - } else { - context.report(node, '{{current}} should be sorted before {{last}}', { - current: value, - last: lastCorrect - }); + if (!node) { + return false; } - }); - } + return true; + } - function isStandardModule(value) { - return standard.indexOf(value) !== -1; - } + function checkCombined(deps) { + var lastCorrect; + deps.elements.forEach(function(node) { + if (!checkLiteral(node)) { + return; + } + var value = node.value; + if (lastCorrect === undefined || lastCorrect.localeCompare(value) < 0) { + lastCorrect = value; + } else { + context.report(node, '{{current}} should be sorted before {{last}}', { + current: value, + last: lastCorrect + }); + } + }); + } - function isCustomModule(value) { - return moduleRegex && moduleRegex.test(value); - } + function isStandardModule(value) { + return standard.indexOf(value) !== -1; + } - function checkGrouped(deps) { - var lastCorrect; - var group = 'standard'; - deps.elements.forEach(function loop(node) { - if (!checkLiteral(node)) { - return; - } - var value = node.value; - if (lastCorrect === undefined) { - lastCorrect = value; - if (isCustomModule(value)) { - group = 'custom'; - } else if (standard.indexOf(value) === -1) { - group = 'third party'; + function isCustomModule(value) { + return moduleRegex && moduleRegex.test(value); + } + + function checkGrouped(deps) { + var lastCorrect; + var group = 'standard'; + deps.elements.forEach(function loop(node) { + if (!checkLiteral(node)) { + return; } - return; - } - if (group === 'standard') { - if (isStandardModule(value)) { - if (lastCorrect.localeCompare(value) > 0) { + var value = node.value; + if (lastCorrect === undefined) { + lastCorrect = value; + if (isCustomModule(value)) { + group = 'custom'; + } else if (standard.indexOf(value) === -1) { + group = 'third party'; + } + return; + } + if (group === 'standard') { + if (isStandardModule(value)) { + if (lastCorrect.localeCompare(value) > 0) { + context.report(node, '{{current}} should be sorted before {{last}}', { + current: value, + last: lastCorrect + }); + } else { + lastCorrect = value; + } + } else { + if (isCustomModule(value)) { + group = 'custom'; + } else { + group = 'third party'; + } + lastCorrect = value; + } + } + if (group === 'third party') { + if (isStandardModule(value)) { + context.report(node, '{{current}} is a standard module and should be sorted before {{last}}', { + current: value, + last: lastCorrect + }); + } else if (isCustomModule(value)) { + group = 'custom'; + lastCorrect = value; + } else if (lastCorrect.localeCompare(value) > 0) { context.report(node, '{{current}} should be sorted before {{last}}', { current: value, last: lastCorrect @@ -109,76 +149,39 @@ module.exports = function(context) { } else { lastCorrect = value; } - } else { - if (isCustomModule(value)) { - group = 'custom'; - } else { - group = 'third party'; + } + if (group === 'custom') { + if (isStandardModule(value)) { + context.report(node, '{{current}} is a standard module and should be sorted before {{last}}', { + current: value, + last: lastCorrect + }); + } else if (!isCustomModule(value)) { + context.report(node, '{{current}} is a third party module and should be sorted before {{last}}', { + current: value, + last: lastCorrect + }); } - lastCorrect = value; } - } - if (group === 'third party') { - if (isStandardModule(value)) { - context.report(node, '{{current}} is a standard module and should be sorted before {{last}}', { - current: value, - last: lastCorrect - }); - } else if (isCustomModule(value)) { - group = 'custom'; - lastCorrect = value; - } else if (lastCorrect.localeCompare(value) > 0) { - context.report(node, '{{current}} should be sorted before {{last}}', { - current: value, - last: lastCorrect - }); - } else { - lastCorrect = value; + }); + } + + return { + CallExpression: function(node) { + if (!utils.isAngularModuleDeclaration(node)) { + return; } - } - if (group === 'custom') { - if (isStandardModule(value)) { - context.report(node, '{{current}} is a standard module and should be sorted before {{last}}', { - current: value, - last: lastCorrect - }); - } else if (!isCustomModule(value)) { - context.report(node, '{{current}} is a third party module and should be sorted before {{last}}', { - current: value, - last: lastCorrect - }); + var deps = node.arguments[1]; + if (deps.type !== 'ArrayExpression') { + context.report(deps, 'Dependencies should be a literal array'); + return; + } + if (groupedMode) { + checkGrouped(deps); + } else { + checkCombined(deps); } } - }); + }; } - - return { - CallExpression: function(node) { - if (!utils.isAngularModuleDeclaration(node)) { - return; - } - var deps = node.arguments[1]; - if (deps.type !== 'ArrayExpression') { - context.report(deps, 'Dependencies should be a literal array'); - return; - } - if (groupedMode) { - checkGrouped(deps); - } else { - checkCombined(deps); - } - } - }; }; - -module.exports.schema = [{ - type: 'object', - properties: { - grouped: { - type: 'boolean' - }, - prefix: { - type: ['string', 'null'] - } - } -}]; diff --git a/rules/module-getter.js b/rules/module-getter.js index f1c8b229..92771450 100644 --- a/rules/module-getter.js +++ b/rules/module-getter.js @@ -13,11 +13,15 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - ExpressionStatement: function(node) { - if ((utils.isAngularControllerDeclaration(node.expression) || + ExpressionStatement: function(node) { + if ((utils.isAngularControllerDeclaration(node.expression) || utils.isAngularFilterDeclaration(node.expression) || utils.isAngularServiceDeclaration(node.expression) || utils.isAngularFactoryDeclaration(node.expression) || @@ -28,19 +32,16 @@ module.exports = function(context) { utils.isAngularConfigSection(node.expression)) && !utils.isAngularModuleDeclaration(node.expression)) { - var calleeObject = node.expression.callee.object; - while (calleeObject !== undefined && calleeObject.type === 'CallExpression' && !utils.isAngularModuleGetter(calleeObject)) { - calleeObject = calleeObject.callee.object; - } + var calleeObject = node.expression.callee.object; - if (!(calleeObject !== undefined && calleeObject.type === 'CallExpression' && utils.isAngularModuleGetter(calleeObject))) { - context.report(node, 'Avoid using a variable and instead use chaining with the getter syntax.'); + while (calleeObject !== undefined && calleeObject.type === 'CallExpression' && !utils.isAngularModuleGetter(calleeObject)) { + calleeObject = calleeObject.callee.object; + } + if (!(calleeObject !== undefined && calleeObject.type === 'CallExpression' && utils.isAngularModuleGetter(calleeObject))) { + context.report(node, 'Avoid using a variable and instead use chaining with the getter syntax.'); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/module-name.js b/rules/module-name.js index f0667c0d..00a40726 100644 --- a/rules/module-name.js +++ b/rules/module-name.js @@ -14,40 +14,47 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }] + }, + create: function(context) { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex - if (prefix === undefined) { - return; - } + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); + convertedPrefix = utils.convertPrefixToRegex(prefix); - if (utils.isAngularModuleDeclaration(node)) { - var name = node.arguments[0].value; + if (utils.isAngularModuleDeclaration(node)) { + var name = node.arguments[0].value; - if (name !== undefined && name.indexOf('ng') === 0) { - context.report(node, 'The {{module}} module should not start with "ng". This is reserved for AngularJS modules', { - module: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{module}} module should be prefixed by {{prefix}}', { - module: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{module}} module should follow this pattern: {{prefix}}', { - module: name, - prefix: prefix.toString() + if (name !== undefined && name.indexOf('ng') === 0) { + context.report(node, 'The {{module}} module should not start with "ng". This is reserved for AngularJS modules', { + module: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{module}} module should be prefixed by {{prefix}}', { + module: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{module}} module should follow this pattern: {{prefix}}', { + module: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/module-setter.js b/rules/module-setter.js index a0208fda..51cb887e 100644 --- a/rules/module-setter.js +++ b/rules/module-setter.js @@ -13,29 +13,30 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - VariableDeclaration: function(node) { - var variableDeclarator = node.declarations[0]; - var rightExpression; + VariableDeclaration: function(node) { + var variableDeclarator = node.declarations[0]; + var rightExpression; - if (variableDeclarator.init) { - rightExpression = variableDeclarator.init; + if (variableDeclarator.init) { + rightExpression = variableDeclarator.init; - if (rightExpression.arguments && utils.isAngularModuleDeclaration(rightExpression)) { - context.report(rightExpression, 'Declare modules without a variable using the setter syntax.'); + if (rightExpression.arguments && utils.isAngularModuleDeclaration(rightExpression)) { + context.report(rightExpression, 'Declare modules without a variable using the setter syntax.'); + } + } + }, + AssignmentExpression: function(node) { + if (node.right.arguments && utils.isAngularModuleDeclaration(node.right)) { + context.report(node.right, 'Declare modules without a variable using the setter syntax.'); } } - }, - AssignmentExpression: function(node) { - if (node.right.arguments && utils.isAngularModuleDeclaration(node.right)) { - context.report(node.right, 'Declare modules without a variable using the setter syntax.'); - } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/no-angular-mock.js b/rules/no-angular-mock.js index d9c6ecbb..5f1cdbd3 100644 --- a/rules/no-angular-mock.js +++ b/rules/no-angular-mock.js @@ -30,22 +30,23 @@ */ 'use strict'; -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - MemberExpression: function(node) { - if (node.object.type === 'Identifier' && node.object.name === 'angular' && + MemberExpression: function(node) { + if (node.object.type === 'Identifier' && node.object.name === 'angular' && node.property.type === 'Identifier' && node.property.name === 'mock') { - if (node.parent.type === 'MemberExpression' && node.parent.property.type === 'Identifier') { - context.report(node, 'You should use the "{{method}}" method available in the window object.', { - method: node.parent.property.name - }); + if (node.parent.type === 'MemberExpression' && node.parent.property.type === 'Identifier') { + context.report(node, 'You should use the "{{method}}" method available in the window object.', { + method: node.parent.property.name + }); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/no-controller.js b/rules/no-controller.js index 2624a4dd..1ea3c63a 100644 --- a/rules/no-controller.js +++ b/rules/no-controller.js @@ -11,17 +11,18 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - CallExpression: function(node) { - if (utils.isAngularControllerDeclaration(node)) { - context.report(node, 'Based on the Component-First Pattern, you should avoid the use of controllers', {}); + CallExpression: function(node) { + if (utils.isAngularControllerDeclaration(node)) { + context.report(node, 'Based on the Component-First Pattern, you should avoid the use of controllers', {}); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/no-cookiestore.js b/rules/no-cookiestore.js index 22916d9e..1a5467eb 100644 --- a/rules/no-cookiestore.js +++ b/rules/no-cookiestore.js @@ -10,17 +10,18 @@ */ 'use strict'; -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - MemberExpression: function(node) { - if (node.object && node.object.name === '$cookieStore') { - context.report(node, 'Since Angular 1.4, the $cookieStore service is deprecated. Please use now the $cookies service.', {}); + MemberExpression: function(node) { + if (node.object && node.object.name === '$cookieStore') { + context.report(node, 'Since Angular 1.4, the $cookieStore service is deprecated. Please use now the $cookies service.', {}); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/no-directive-replace.js b/rules/no-directive-replace.js index d4cb442e..fbdcdb52 100644 --- a/rules/no-directive-replace.js +++ b/rules/no-directive-replace.js @@ -14,89 +14,92 @@ var angularRule = require('./utils/angular-rule'); -module.exports = angularRule(function(context) { - var options = context.options[0] || {}; - var ignoreReplaceFalse = !!options.ignoreReplaceFalse; +module.exports = { + meta: { + schema: [{ + type: 'object', + properties: { + ignoreReplaceFalse: { + type: 'boolean' + } + } + }] + }, + create: angularRule(function(context) { + var options = context.options[0] || {}; + var ignoreReplaceFalse = !!options.ignoreReplaceFalse; - var potentialReplaceNodes = {}; + var potentialReplaceNodes = {}; - function addPotentialReplaceNode(variableName, node) { - var nodeList = potentialReplaceNodes[variableName] || []; + function addPotentialReplaceNode(variableName, node) { + var nodeList = potentialReplaceNodes[variableName] || []; - nodeList.push({ - name: variableName, - node: node, - block: context.getScope().block.body - }); + nodeList.push({ + name: variableName, + node: node, + block: context.getScope().block.body + }); - potentialReplaceNodes[variableName] = nodeList; - } + potentialReplaceNodes[variableName] = nodeList; + } - return { - 'angular:directive': function(callExpressionNode, fnNode) { - if (!fnNode || !fnNode.body) { - return; - } - fnNode.body.body.forEach(function(statement) { - if (statement.type === 'ReturnStatement') { - // get potential replace node by argument name of empty string for object expressions - var potentialNodes = potentialReplaceNodes[statement.argument.name || '']; - if (!potentialNodes) { - return; - } - potentialNodes.forEach(function(report) { - // only reports nodes that belong to the same expression - if (report.block === statement.parent) { - context.report(report.node, 'Directive definition property replace is deprecated.'); + return { + 'angular:directive': function(callExpressionNode, fnNode) { + if (!fnNode || !fnNode.body) { + return; + } + fnNode.body.body.forEach(function(statement) { + if (statement.type === 'ReturnStatement') { + // get potential replace node by argument name of empty string for object expressions + var potentialNodes = potentialReplaceNodes[statement.argument.name || '']; + if (!potentialNodes) { + return; } - }); + potentialNodes.forEach(function(report) { + // only reports nodes that belong to the same expression + if (report.block === statement.parent) { + context.report(report.node, 'Directive definition property replace is deprecated.'); + } + }); + } + }); + }, + AssignmentExpression: function(node) { + // Only check for literal member property assignments. + if (node.left.type !== 'MemberExpression') { + return; + } + // Only check setting properties named 'replace'. + if (node.left.property.name !== 'replace') { + return; + } + if (ignoreReplaceFalse && node.right.value === false) { + return; + } + addPotentialReplaceNode(node.left.object.name, node); + }, + Property: function(node) { + // This only checks for objects which have defined a literal restrict property. + if (node.key.name !== 'replace') { + return; + } + if (ignoreReplaceFalse === true && node.value.value === false) { + return; } - }); - }, - AssignmentExpression: function(node) { - // Only check for literal member property assignments. - if (node.left.type !== 'MemberExpression') { - return; - } - // Only check setting properties named 'replace'. - if (node.left.property.name !== 'replace') { - return; - } - if (ignoreReplaceFalse && node.right.value === false) { - return; - } - addPotentialReplaceNode(node.left.object.name, node); - }, - Property: function(node) { - // This only checks for objects which have defined a literal restrict property. - if (node.key.name !== 'replace') { - return; - } - if (ignoreReplaceFalse === true && node.value.value === false) { - return; - } - // assumption: Property always belongs to a ObjectExpression - var objectExpressionParent = node.parent.parent; + // assumption: Property always belongs to a ObjectExpression + var objectExpressionParent = node.parent.parent; - // add to potential replace nodes if the object is defined in a variable - if (objectExpressionParent.type === 'VariableDeclarator') { - addPotentialReplaceNode(objectExpressionParent.id.name, node); - } + // add to potential replace nodes if the object is defined in a variable + if (objectExpressionParent.type === 'VariableDeclarator') { + addPotentialReplaceNode(objectExpressionParent.id.name, node); + } - // report directly if object is part of a return statement and inside a directive body - if (objectExpressionParent.type === 'ReturnStatement') { - addPotentialReplaceNode('', node); + // report directly if object is part of a return statement and inside a directive body + if (objectExpressionParent.type === 'ReturnStatement') { + addPotentialReplaceNode('', node); + } } - } - }; -}); - -module.exports.schema = [{ - type: 'object', - properties: { - ignoreReplaceFalse: { - type: 'boolean' - } - } -}]; + }; + }) +}; diff --git a/rules/no-http-callback.js b/rules/no-http-callback.js index 12cfcd3f..a153f169 100644 --- a/rules/no-http-callback.js +++ b/rules/no-http-callback.js @@ -10,38 +10,43 @@ */ 'use strict'; -module.exports = function(context) { - var httpMethods = [ - 'delete', - 'get', - 'head', - 'jsonp', - 'patch', - 'post', - 'put' - ]; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + var httpMethods = [ + 'delete', + 'get', + 'head', + 'jsonp', + 'patch', + 'post', + 'put' + ]; - function isHttpCall(node) { - if (node.callee.type === 'MemberExpression') { - return httpMethods.indexOf(node.callee.property.name) !== -1 || - (node.callee.object.type === 'CallExpression' && isHttpCall(node.callee.object)); - } - if (node.callee.type === 'Identifier') { - return node.callee.name === '$http'; - } - } - - return { - CallExpression: function(node) { - if (node.callee.type !== 'MemberExpression') { - return; + function isHttpCall(node) { + if (node.callee.type === 'MemberExpression') { + return httpMethods.indexOf(node.callee.property.name) !== -1 || + (node.callee.object.type === 'CallExpression' && isHttpCall(node.callee.object)); } - if (node.callee.property.name === 'success' && isHttpCall(node)) { - return context.report(node, '$http success is deprecated. Use then instead'); - } - if (node.callee.property.name === 'error' && isHttpCall(node)) { - context.report(node, '$http error is deprecated. Use then or catch instead'); + if (node.callee.type === 'Identifier') { + return node.callee.name === '$http'; } } - }; + + return { + CallExpression: function(node) { + if (node.callee.type !== 'MemberExpression') { + return; + } + if (node.callee.property.name === 'success' && isHttpCall(node)) { + return context.report(node, '$http success is deprecated. Use then instead'); + } + if (node.callee.property.name === 'error' && isHttpCall(node)) { + context.report(node, '$http error is deprecated. Use then or catch instead'); + } + } + }; + } }; diff --git a/rules/no-inline-template.js b/rules/no-inline-template.js index 9c88050b..c5237a02 100644 --- a/rules/no-inline-template.js +++ b/rules/no-inline-template.js @@ -11,41 +11,44 @@ */ 'use strict'; -module.exports = function(context) { - // Extracts any HTML tags. - var regularTagPattern = /<(.+?)>/g; - // Extracts self closing HTML tags. - var selfClosingTagPattern = /<(.+?)\/>/g; - - var allowSimple = (context.options[0] && context.options[0].allowSimple) !== false; +module.exports = { + meta: { + schema: [{ + allowSimple: { + type: 'boolean' + } + }] + }, + create: function(context) { + // Extracts any HTML tags. + var regularTagPattern = /<(.+?)>/g; + // Extracts self closing HTML tags. + var selfClosingTagPattern = /<(.+?)\/>/g; - function reportComplex(node) { - context.report(node, 'Inline template is too complex. Use an external template instead'); - } + var allowSimple = (context.options[0] && context.options[0].allowSimple) !== false; - return { - Property: function(node) { - if (node.key.name !== 'template' || node.value.type !== 'Literal') { - return; - } - if (!allowSimple) { - context.report(node, 'Inline templates are not allowed. Use an external template instead'); - } - if ((node.value.value && node.value.value.match(regularTagPattern) || []).length > 2) { - return reportComplex(node); - } - if ((node.value.value && node.value.value.match(selfClosingTagPattern) || []).length > 1) { - return reportComplex(node); - } - if (node.value && node.value.raw.indexOf('\\') !== -1) { - reportComplex(node); - } + function reportComplex(node) { + context.report(node, 'Inline template is too complex. Use an external template instead'); } - }; -}; -module.exports.schema = [{ - allowSimple: { - type: 'boolean' + return { + Property: function(node) { + if (node.key.name !== 'template' || node.value.type !== 'Literal') { + return; + } + if (!allowSimple) { + context.report(node, 'Inline templates are not allowed. Use an external template instead'); + } + if ((node.value.value && node.value.value.match(regularTagPattern) || []).length > 2) { + return reportComplex(node); + } + if ((node.value.value && node.value.value.match(selfClosingTagPattern) || []).length > 1) { + return reportComplex(node); + } + if (node.value && node.value.raw.indexOf('\\') !== -1) { + reportComplex(node); + } + } + }; } -}]; +}; diff --git a/rules/no-jquery-angularelement.js b/rules/no-jquery-angularelement.js index c03e381d..06ab2c6e 100644 --- a/rules/no-jquery-angularelement.js +++ b/rules/no-jquery-angularelement.js @@ -8,22 +8,23 @@ */ 'use strict'; -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - MemberExpression: function(node) { - if (node.object.name === 'angular' && node.property.name === 'element') { - if (node.parent !== undefined && node.parent.parent !== undefined && + MemberExpression: function(node) { + if (node.object.name === 'angular' && node.property.name === 'element') { + if (node.parent !== undefined && node.parent.parent !== undefined && node.parent.parent.type === 'CallExpression' && node.parent.parent.callee.type === 'Identifier' && (node.parent.parent.callee.name === 'jQuery' || node.parent.parent.callee.name === '$')) { - context.report(node, 'angular.element returns already a jQLite element. No need to wrap with the jQuery object', {}); + context.report(node, 'angular.element returns already a jQLite element. No need to wrap with the jQuery object', {}); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/no-private-call.js b/rules/no-private-call.js index cb1ed2a4..3b9c3015 100644 --- a/rules/no-private-call.js +++ b/rules/no-private-call.js @@ -11,34 +11,37 @@ */ 'use strict'; -module.exports = function(context) { - var options = context.options[0] || {}; - var allowed = options.allow || []; - - function check(node, name) { - if (name.slice(0, 2) === '$$' && allowed.indexOf(name) < 0) { - context.report(node, 'Using $$-prefixed Angular objects/methods are not recommended', {}); - } - } - return { +module.exports = { + meta: { + schema: [ + { + type: 'object', + properties: { + allow: { + type: 'array', + items: { + type: 'string' + } + } + }, + additionalProperties: false + } + ] + }, + create: function(context) { + var options = context.options[0] || {}; + var allowed = options.allow || []; - Identifier: function(node) { - check(node, node.name); + function check(node, name) { + if (name.slice(0, 2) === '$$' && allowed.indexOf(name) < 0) { + context.report(node, 'Using $$-prefixed Angular objects/methods are not recommended', {}); + } } - }; -}; + return { -module.exports.schema = [ - { - type: 'object', - properties: { - allow: { - type: 'array', - items: { - type: 'string' - } + Identifier: function(node) { + check(node, node.name); } - }, - additionalProperties: false + }; } -]; +}; diff --git a/rules/no-run-logic.js b/rules/no-run-logic.js index 26e4d9a7..7ad8ecb2 100644 --- a/rules/no-run-logic.js +++ b/rules/no-run-logic.js @@ -13,48 +13,51 @@ var angularRule = require('./utils/angular-rule'); -module.exports = angularRule(function(context) { - var options = context.options[0] || {}; - var allowParams = options.allowParams !== false; +module.exports = { + meta: { + schema: [{ + type: 'object', + properties: { + allowParams: { + type: 'boolean' + } + } + }] + }, + create: angularRule(function(context) { + var options = context.options[0] || {}; + var allowParams = options.allowParams !== false; - function report(node) { - context.report(node, 'The run function may only contain call expressions'); - } + function report(node) { + context.report(node, 'The run function may only contain call expressions'); + } - return { - 'angular:run': function(callExpression, fn) { - if (!fn) { - return; - } - fn.body.body.forEach(function(statement) { - if (statement.type !== 'ExpressionStatement') { - return report(statement); - } - var expression = statement.expression; - if (expression.type !== 'CallExpression') { - return report(statement); - } - if (expression.callee.type === 'MemberExpression' && expression.callee.object.type !== 'Identifier') { - return report(statement); - } - if (!allowParams && expression.arguments.length) { - return context.report(expression, 'Run function call expressions may not take any arguments'); + return { + 'angular:run': function(callExpression, fn) { + if (!fn) { + return; } - expression.arguments.forEach(function(argument) { - if (argument.type !== 'Literal' && argument.type !== 'Identifier') { - context.report(argument, 'Run function call expressions may only take simple arguments'); + fn.body.body.forEach(function(statement) { + if (statement.type !== 'ExpressionStatement') { + return report(statement); } + var expression = statement.expression; + if (expression.type !== 'CallExpression') { + return report(statement); + } + if (expression.callee.type === 'MemberExpression' && expression.callee.object.type !== 'Identifier') { + return report(statement); + } + if (!allowParams && expression.arguments.length) { + return context.report(expression, 'Run function call expressions may not take any arguments'); + } + expression.arguments.forEach(function(argument) { + if (argument.type !== 'Literal' && argument.type !== 'Identifier') { + context.report(argument, 'Run function call expressions may only take simple arguments'); + } + }); }); - }); - } - }; -}); - -module.exports.schema = [{ - type: 'object', - properties: { - allowParams: { - type: 'boolean' - } - } -}]; + } + }; + }) +}; diff --git a/rules/no-service-method.js b/rules/no-service-method.js index 96984015..5b1445d5 100644 --- a/rules/no-service-method.js +++ b/rules/no-service-method.js @@ -12,13 +12,18 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + return { - CallExpression: function(node) { - if (utils.isAngularComponent(node) && node.callee.property && node.callee.property.name === 'service') { - context.report(node, 'You should prefer the factory() method instead of service()', {}); + CallExpression: function(node) { + if (utils.isAngularComponent(node) && node.callee.property && node.callee.property.name === 'service') { + context.report(node, 'You should prefer the factory() method instead of service()', {}); + } } - } - }; + }; + } }; diff --git a/rules/no-services.js b/rules/no-services.js index 3c216499..ff0918eb 100644 --- a/rules/no-services.js +++ b/rules/no-services.js @@ -15,82 +15,84 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var angularObjectList = ['controller', 'filter', 'directive']; - var badServices; - var map; - var message = 'REST API calls should be implemented in a specific service'; - - function isArray(item) { - return Object.prototype.toString.call(item) === '[object Array]'; - } +module.exports = { + meta: { + schema: [{ + type: ['array', 'object'] + }, { + type: 'array' + }] + }, + create: function(context) { + var angularObjectList = ['controller', 'filter', 'directive']; + var badServices; + var map; + var message = 'REST API calls should be implemented in a specific service'; + + function isArray(item) { + return Object.prototype.toString.call(item) === '[object Array]'; + } - function isObject(item) { - return Object.prototype.toString.call(item) === '[object Object]'; - } + function isObject(item) { + return Object.prototype.toString.call(item) === '[object Object]'; + } - if (context.options[0] === undefined) { - badServices = ['$http', '$resource', 'Restangular', '$q', '$filter']; - } + if (context.options[0] === undefined) { + badServices = ['$http', '$resource', 'Restangular', '$q', '$filter']; + } - if (isArray(context.options[0])) { - badServices = context.options[0]; - } + if (isArray(context.options[0])) { + badServices = context.options[0]; + } - if (isArray(context.options[1])) { - angularObjectList = context.options[1]; - } + if (isArray(context.options[1])) { + angularObjectList = context.options[1]; + } - if (isObject(context.options[0])) { - map = context.options[0]; - var result = []; - var prop; + if (isObject(context.options[0])) { + map = context.options[0]; + var result = []; + var prop; - for (prop in map) { - if (map.hasOwnProperty(prop)) { - result.push(prop); + for (prop in map) { + if (map.hasOwnProperty(prop)) { + result.push(prop); + } } - } - angularObjectList = result; - } - - function isSetBedService(serviceName, angularObjectName) { - if (map) { - return map[angularObjectName].indexOf(serviceName) >= 0; + angularObjectList = result; } - return badServices.indexOf(serviceName) >= 0; - } - return { - - CallExpression: function(node) { - var callee = node.callee; - - if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(callee.property.name) >= 0) { - if (utils.isFunctionType(node.arguments[1])) { - node.arguments[1].params.forEach(function(service) { - if (service.type === 'Identifier' && isSetBedService(service.name, callee.property.name)) { - context.report(node, message + ' (' + service.name + ' in ' + callee.property.name + ')', {}); - } - }); - } + function isSetBedService(serviceName, angularObjectName) { + if (map) { + return map[angularObjectName].indexOf(serviceName) >= 0; + } + return badServices.indexOf(serviceName) >= 0; + } - if (utils.isArrayType(node.arguments[1])) { - node.arguments[1].elements.forEach(function(service) { - if (service.type === 'Literal' && isSetBedService(service.value, callee.property.name)) { - context.report(node, message + ' (' + service.value + ' in ' + callee.property.name + ')', {}); - } - }); + return { + + CallExpression: function(node) { + var callee = node.callee; + + if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(callee.property.name) >= 0) { + if (utils.isFunctionType(node.arguments[1])) { + node.arguments[1].params.forEach(function(service) { + if (service.type === 'Identifier' && isSetBedService(service.name, callee.property.name)) { + context.report(node, message + ' (' + service.name + ' in ' + callee.property.name + ')', {}); + } + }); + } + + if (utils.isArrayType(node.arguments[1])) { + node.arguments[1].elements.forEach(function(service) { + if (service.type === 'Literal' && isSetBedService(service.value, callee.property.name)) { + context.report(node, message + ' (' + service.value + ' in ' + callee.property.name + ')', {}); + } + }); + } } } - } - }; + }; + } }; - - -module.exports.schema = [{ - type: ['array', 'object'] -}, { - type: 'array' -}]; diff --git a/rules/on-destroy.js b/rules/on-destroy.js index fc8d9306..dc367ce5 100644 --- a/rules/on-destroy.js +++ b/rules/on-destroy.js @@ -8,54 +8,55 @@ */ 'use strict'; -module.exports = function(context) { - function report(node) { - context.report(node, 'You probably misspelled $on("$destroy").'); - } - - /** - * Return true if the given node is a call expression calling a function - * named '$on'. - */ - function isOn(node) { - var calledFunction = node.callee; - if (calledFunction.type !== 'MemberExpression') { - return false; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function report(node) { + context.report(node, 'You probably misspelled $on("$destroy").'); } - // can only easily tell what name was used if a simple - // identifiers were used to access it. - var accessedFunction = calledFunction.property; - if (accessedFunction.type !== 'Identifier') { - return false; - } + /** + * Return true if the given node is a call expression calling a function + * named '$on'. + */ + function isOn(node) { + var calledFunction = node.callee; + if (calledFunction.type !== 'MemberExpression') { + return false; + } - var functionName = accessedFunction.name; + // can only easily tell what name was used if a simple + // identifiers were used to access it. + var accessedFunction = calledFunction.property; + if (accessedFunction.type !== 'Identifier') { + return false; + } - return functionName === '$on'; - } + var functionName = accessedFunction.name; - /** - * Return true if the given node is a call expression that has a first - * argument of the string '$destroy'. - */ - function isFirstArgDestroy(node) { - var args = node.arguments; + return functionName === '$on'; + } - return (args.length >= 1 && + /** + * Return true if the given node is a call expression that has a first + * argument of the string '$destroy'. + */ + function isFirstArgDestroy(node) { + var args = node.arguments; + + return (args.length >= 1 && args[0].type === 'Literal' && args[0].value === 'destroy'); - } + } - return { - CallExpression: function(node) { - if (isOn(node) && isFirstArgDestroy(node)) { - report(node); + return { + CallExpression: function(node) { + if (isOn(node) && isFirstArgDestroy(node)) { + report(node); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/on-watch.js b/rules/on-watch.js index 75c4d839..2a34b7bc 100644 --- a/rules/on-watch.js +++ b/rules/on-watch.js @@ -8,74 +8,75 @@ */ 'use strict'; -module.exports = function(context) { - function report(node, method) { - context.report(node, 'The "{{method}}" call should be assigned to a variable, in order to be destroyed during the $destroy event', { - method: method - }); - } - - /** - * Return true if the given node is a call expression calling a function - * named '$on' or '$watch' on an object named '$scope', '$rootScope' or - * 'scope'. - */ - function isScopeOnOrWatch(node, scopes) { - if (node.type !== 'CallExpression') { - return false; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function report(node, method) { + context.report(node, 'The "{{method}}" call should be assigned to a variable, in order to be destroyed during the $destroy event', { + method: method + }); } - var calledFunction = node.callee; - if (calledFunction.type !== 'MemberExpression') { - return false; - } + /** + * Return true if the given node is a call expression calling a function + * named '$on' or '$watch' on an object named '$scope', '$rootScope' or + * 'scope'. + */ + function isScopeOnOrWatch(node, scopes) { + if (node.type !== 'CallExpression') { + return false; + } - // can only easily tell what name was used if a simple - // identifiers were used to access it. - var parentObject = calledFunction.object; - var accessedFunction = calledFunction.property; + var calledFunction = node.callee; + if (calledFunction.type !== 'MemberExpression') { + return false; + } - // cannot check name of the parent object if it is returned from a - // complex expression. - if (parentObject.type !== 'Identifier' || - accessedFunction.type !== 'Identifier') { - return false; - } + // can only easily tell what name was used if a simple + // identifiers were used to access it. + var parentObject = calledFunction.object; + var accessedFunction = calledFunction.property; - var objectName = parentObject.name; - var functionName = accessedFunction.name; + // cannot check name of the parent object if it is returned from a + // complex expression. + if (parentObject.type !== 'Identifier' || + accessedFunction.type !== 'Identifier') { + return false; + } - return scopes.indexOf(objectName) >= 0 && (functionName === '$on' || - functionName === '$watch'); - } + var objectName = parentObject.name; + var functionName = accessedFunction.name; - /** - * Return true if the given node is a call expression that has a first - * argument of the string '$destroy'. - */ - function isFirstArgDestroy(node) { - var args = node.arguments; + return scopes.indexOf(objectName) >= 0 && (functionName === '$on' || + functionName === '$watch'); + } + + /** + * Return true if the given node is a call expression that has a first + * argument of the string '$destroy'. + */ + function isFirstArgDestroy(node) { + var args = node.arguments; - return (args.length >= 1 && + return (args.length >= 1 && args[0].type === 'Literal' && args[0].value === '$destroy'); - } + } - return { + return { - CallExpression: function(node) { - if (isScopeOnOrWatch(node, ['$rootScope']) && !isFirstArgDestroy(node)) { - if (node.parent.type !== 'VariableDeclarator' && - node.parent.type !== 'AssignmentExpression' && - !(isScopeOnOrWatch(node.parent, ['$rootScope', '$scope', 'scope']) && - isFirstArgDestroy(node.parent))) { - report(node, node.callee.property.name); + CallExpression: function(node) { + if (isScopeOnOrWatch(node, ['$rootScope']) && !isFirstArgDestroy(node)) { + if (node.parent.type !== 'VariableDeclarator' && + node.parent.type !== 'AssignmentExpression' && + !(isScopeOnOrWatch(node.parent, ['$rootScope', '$scope', 'scope']) && + isFirstArgDestroy(node.parent))) { + report(node, node.callee.property.name); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/one-dependency-per-line.js b/rules/one-dependency-per-line.js index 41c7eb6f..65748fc7 100644 --- a/rules/one-dependency-per-line.js +++ b/rules/one-dependency-per-line.js @@ -11,120 +11,125 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var angularObjectList = ['animation', 'config', 'constant', 'controller', 'directive', 'factory', 'filter', 'provider', 'service', 'value', 'decorator']; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + var angularObjectList = ['animation', 'config', 'constant', 'controller', 'directive', 'factory', 'filter', 'provider', 'service', 'value', 'decorator']; - function checkArgumentPositionInFunction(node) { - if (!node.params || node.params.length < 2) { - return; - } - - var linesFound = []; - node.params.forEach(reportMultipleItemsInOneLine.bind(null, node, linesFound)); - } - - function reportMultipleItemsInOneLine(node, linesFound, item) { - var currentLine = item.loc.start.line; - if (linesFound.indexOf(currentLine) !== -1) { - context.report({ - node: node, - message: 'Do not use multiple dependencies in one line', - loc: item.loc.start - }); - } - linesFound.push(currentLine); - } - - function checkArgumentPositionArrayExpression(angularComponentNode, arrayNode) { - var linesFound = []; - - arrayNode.elements.forEach(function(element) { - if (element.type === 'Literal') { - reportMultipleItemsInOneLine(arrayNode, linesFound, element); - } - if (element.type === 'FunctionExpression') { - checkArgumentPositionInFunction(element); - } - if (element.type === 'Identifier') { - var fn = getFunctionDeclaration(angularComponentNode, element.name); - checkArgumentPositionInFunction(fn); + function checkArgumentPositionInFunction(node) { + if (!node.params || node.params.length < 2) { + return; } - }); - } - function findFunctionDeclarationByDeclaration(body, fName) { - return body.find(function(item) { - return item.type === 'FunctionDeclaration' && item.id.name === fName; - }); - } + var linesFound = []; + node.params.forEach(reportMultipleItemsInOneLine.bind(null, node, linesFound)); + } - function findFunctionDeclarationByVariableDeclaration(body, fName) { - var fn; - body.forEach(function(item) { - if (fn) { - return; - } - if (item.type === 'VariableDeclaration') { - item.declarations.forEach(function(declaration) { - if (declaration.type === 'VariableDeclarator' && - declaration.id && - declaration.id.name === fName && - declaration.init && - declaration.init.type === 'FunctionExpression' - ) { - fn = declaration.init; - } + function reportMultipleItemsInOneLine(node, linesFound, item) { + var currentLine = item.loc.start.line; + if (linesFound.indexOf(currentLine) !== -1) { + context.report({ + node: node, + message: 'Do not use multiple dependencies in one line', + loc: item.loc.start }); } - }); - return fn; - } + linesFound.push(currentLine); + } - function getFunctionDeclaration(node, fName) { - if (node.type === 'BlockStatement' || node.type === 'Program') { - if (node.body) { - var fn = findFunctionDeclarationByDeclaration(node.body, fName); - if (fn) { - return fn; + function checkArgumentPositionArrayExpression(angularComponentNode, arrayNode) { + var linesFound = []; + + arrayNode.elements.forEach(function(element) { + if (element.type === 'Literal') { + reportMultipleItemsInOneLine(arrayNode, linesFound, element); } - fn = findFunctionDeclarationByVariableDeclaration(node.body, fName); - if (fn) { - return fn; + if (element.type === 'FunctionExpression') { + checkArgumentPositionInFunction(element); } - } - } - if (node.parent) { - return getFunctionDeclaration(node.parent, fName); + if (element.type === 'Identifier') { + var fn = getFunctionDeclaration(angularComponentNode, element.name); + checkArgumentPositionInFunction(fn); + } + }); } - } - return { + function findFunctionDeclarationByDeclaration(body, fName) { + return body.find(function(item) { + return item.type === 'FunctionDeclaration' && item.id.name === fName; + }); + } - CallExpression: function(node) { + function findFunctionDeclarationByVariableDeclaration(body, fName) { var fn; - if (utils.isAngularComponent(node) && - node.callee.type === 'MemberExpression' && - node.arguments[1].type === 'FunctionExpression' && - angularObjectList.indexOf(node.callee.property.name) >= 0) { - fn = node.arguments[1]; - return checkArgumentPositionInFunction(fn); - } - if (utils.isAngularComponent(node) && - node.callee.type === 'MemberExpression' && - node.arguments[1].type === 'Identifier' && - angularObjectList.indexOf(node.callee.property.name) >= 0) { - var fName = node.arguments[1].name; - fn = getFunctionDeclaration(node, fName); + body.forEach(function(item) { if (fn) { - return checkArgumentPositionInFunction(fn); + return; + } + if (item.type === 'VariableDeclaration') { + item.declarations.forEach(function(declaration) { + if (declaration.type === 'VariableDeclarator' && + declaration.id && + declaration.id.name === fName && + declaration.init && + declaration.init.type === 'FunctionExpression' + ) { + fn = declaration.init; + } + }); + } + }); + return fn; + } + + function getFunctionDeclaration(node, fName) { + if (node.type === 'BlockStatement' || node.type === 'Program') { + if (node.body) { + var fn = findFunctionDeclarationByDeclaration(node.body, fName); + if (fn) { + return fn; + } + fn = findFunctionDeclarationByVariableDeclaration(node.body, fName); + if (fn) { + return fn; + } } } - if (utils.isAngularComponent(node) && - node.callee.type === 'MemberExpression' && - node.arguments[1].type === 'ArrayExpression' && - angularObjectList.indexOf(node.callee.property.name) >= 0) { - return checkArgumentPositionArrayExpression(node, node.arguments[1]); + if (node.parent) { + return getFunctionDeclaration(node.parent, fName); } } - }; + + return { + + CallExpression: function(node) { + var fn; + if (utils.isAngularComponent(node) && + node.callee.type === 'MemberExpression' && + node.arguments[1].type === 'FunctionExpression' && + angularObjectList.indexOf(node.callee.property.name) >= 0) { + fn = node.arguments[1]; + return checkArgumentPositionInFunction(fn); + } + if (utils.isAngularComponent(node) && + node.callee.type === 'MemberExpression' && + node.arguments[1].type === 'Identifier' && + angularObjectList.indexOf(node.callee.property.name) >= 0) { + var fName = node.arguments[1].name; + fn = getFunctionDeclaration(node, fName); + if (fn) { + return checkArgumentPositionInFunction(fn); + } + } + if (utils.isAngularComponent(node) && + node.callee.type === 'MemberExpression' && + node.arguments[1].type === 'ArrayExpression' && + angularObjectList.indexOf(node.callee.property.name) >= 0) { + return checkArgumentPositionArrayExpression(node, node.arguments[1]); + } + } + }; + } }; diff --git a/rules/prefer-component.js b/rules/prefer-component.js index d2c73d71..2822365f 100644 --- a/rules/prefer-component.js +++ b/rules/prefer-component.js @@ -11,67 +11,70 @@ var angularRule = require('./utils/angular-rule'); var allowedProperties = ['compile', 'link', 'multiElement', 'priority', 'templateNamespace', 'terminal']; -module.exports = angularRule(function(context) { - var potentialReplaceNodes = {}; +module.exports = { + meta: { + schema: [] + }, + create: angularRule(function(context) { + var potentialReplaceNodes = {}; - function addPotentialLinkNode(variableName, node) { - var nodeList = potentialReplaceNodes[variableName] || []; + function addPotentialLinkNode(variableName, node) { + var nodeList = potentialReplaceNodes[variableName] || []; - nodeList.push({ - name: variableName, - node: node, - block: context.getScope().block.body - }); + nodeList.push({ + name: variableName, + node: node, + block: context.getScope().block.body + }); - potentialReplaceNodes[variableName] = nodeList; - } + potentialReplaceNodes[variableName] = nodeList; + } - return { - 'angular:directive': function(callExpressionNode, fnNode) { - if (!fnNode || !fnNode.body) { - return; - } - fnNode.body.body.forEach(function(statement) { - if (statement.type === 'ReturnStatement' && !potentialReplaceNodes[statement.argument.name || '']) { - context.report(statement, 'Directive should be implemented with the component method.'); + return { + 'angular:directive': function(callExpressionNode, fnNode) { + if (!fnNode || !fnNode.body) { + return; + } + fnNode.body.body.forEach(function(statement) { + if (statement.type === 'ReturnStatement' && !potentialReplaceNodes[statement.argument.name || '']) { + context.report(statement, 'Directive should be implemented with the component method.'); + } + }); + }, + AssignmentExpression: function(node) { + // Only check for literal member property assignments. + if (node.left.type !== 'MemberExpression') { + return; } - }); - }, - AssignmentExpression: function(node) { - // Only check for literal member property assignments. - if (node.left.type !== 'MemberExpression') { - return; - } - if (allowedProperties.indexOf(node.left.property.name) < 0) { - return; - } + if (allowedProperties.indexOf(node.left.property.name) < 0) { + return; + } - addPotentialLinkNode(node.left.object.name, node); - }, - Property: function(node) { - if (node.key.name === 'restrict') { - if (node.value.raw && node.value.raw.indexOf('C') < 0 && node.value.raw.indexOf('A') < 0) { + addPotentialLinkNode(node.left.object.name, node); + }, + Property: function(node) { + if (node.key.name === 'restrict') { + if (node.value.raw && node.value.raw.indexOf('C') < 0 && node.value.raw.indexOf('A') < 0) { + return; + } + } else if (allowedProperties.indexOf(node.key.name) < 0) { return; } - } else if (allowedProperties.indexOf(node.key.name) < 0) { - return; - } - // assumption: Property always belongs to a ObjectExpression - var objectExpressionParent = node.parent.parent; + // assumption: Property always belongs to a ObjectExpression + var objectExpressionParent = node.parent.parent; - // add to potential link nodes if the object is defined in a variable - if (objectExpressionParent.type === 'VariableDeclarator') { - addPotentialLinkNode(objectExpressionParent.id.name, node); - } + // add to potential link nodes if the object is defined in a variable + if (objectExpressionParent.type === 'VariableDeclarator') { + addPotentialLinkNode(objectExpressionParent.id.name, node); + } - // report directly if object is part of a return statement and inside a directive body - if (objectExpressionParent.type === 'ReturnStatement') { - addPotentialLinkNode('', node); + // report directly if object is part of a return statement and inside a directive body + if (objectExpressionParent.type === 'ReturnStatement') { + addPotentialLinkNode('', node); + } } - } - }; -}); - -module.exports.schema = []; + }; + }) +}; diff --git a/rules/provider-name.js b/rules/provider-name.js index 83fdb818..b873582c 100644 --- a/rules/provider-name.js +++ b/rules/provider-name.js @@ -15,42 +15,51 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }, { + type: 'object' + }] + }, + create: function(context) { + return { - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex - var isProvider; + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex + var isProvider; - if (prefix === undefined) { - return; - } + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); - isProvider = utils.isAngularProviderDeclaration(node); - - if (isProvider) { - var name = node.arguments[0].value; - - if (name !== undefined && name.indexOf('$') === 0) { - context.report(node, 'The {{provider}} provider should not start with "$". This is reserved for AngularJS services', { - provider: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{provider}} provider should be prefixed by {{prefix}}', { - provider: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{provider}} provider should follow this pattern: {{prefix}}', { - provider: name, - prefix: prefix.toString() + convertedPrefix = utils.convertPrefixToRegex(prefix); + isProvider = utils.isAngularProviderDeclaration(node); + + if (isProvider) { + var name = node.arguments[0].value; + + if (name !== undefined && name.indexOf('$') === 0) { + context.report(node, 'The {{provider}} provider should not start with "$". This is reserved for AngularJS services', { + provider: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{provider}} provider should be prefixed by {{prefix}}', { + provider: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{provider}} provider should follow this pattern: {{prefix}}', { + provider: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/rest-service.js b/rules/rest-service.js index 1f2e6143..01792437 100644 --- a/rules/rest-service.js +++ b/rules/rest-service.js @@ -12,46 +12,49 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - var angularObjectList = ['controller', 'filter', 'directive', 'service', 'factory', 'provider']; - var services = ['$http', '$resource', 'Restangular']; - var message = 'You should use the same service ({{method}}) for REST API calls'; - - - return { - - CallExpression: function(node) { - function checkElement(element) { - if (element.type === 'Identifier' && services.indexOf(element.name) >= 0 && context.options[0] !== element.name) { - context.report(node, message, { - method: context.options[0] - }); - } else if (element.type === 'Literal' && services.indexOf(element.value) >= 0 && context.options[0] !== element.value) { - context.report(node, message, { - method: context.options[0] - }); +module.exports = { + meta: { + schema: [{ + type: 'string' + }] + }, + create: function(context) { + var angularObjectList = ['controller', 'filter', 'directive', 'service', 'factory', 'provider']; + var services = ['$http', '$resource', 'Restangular']; + var message = 'You should use the same service ({{method}}) for REST API calls'; + + + return { + + CallExpression: function(node) { + function checkElement(element) { + if (element.type === 'Identifier' && services.indexOf(element.name) >= 0 && context.options[0] !== element.name) { + context.report(node, message, { + method: context.options[0] + }); + } else if (element.type === 'Literal' && services.indexOf(element.value) >= 0 && context.options[0] !== element.value) { + context.report(node, message, { + method: context.options[0] + }); + } } - } - function checkAllElements(elements) { - elements.forEach(checkElement); - } + function checkAllElements(elements) { + elements.forEach(checkElement); + } - var callee = node.callee; + var callee = node.callee; - if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(callee.property.name) >= 0) { - if (utils.isFunctionType(node.arguments[1])) { - checkAllElements(node.arguments[1].params); - } + if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(callee.property.name) >= 0) { + if (utils.isFunctionType(node.arguments[1])) { + checkAllElements(node.arguments[1].params); + } - if (utils.isArrayType(node.arguments[1])) { - checkAllElements(node.arguments[1].elements); + if (utils.isArrayType(node.arguments[1])) { + checkAllElements(node.arguments[1].elements); + } } } - } - }; + }; + } }; - -module.exports.schema = [{ - type: 'string' -}]; diff --git a/rules/service-name.js b/rules/service-name.js index c177a17c..9924c0b7 100644 --- a/rules/service-name.js +++ b/rules/service-name.js @@ -44,76 +44,60 @@ function getConfig(options) { return config; } -/** - * Used only by `ForDeprecatedBehavior()` for making sure it was run only one time - * @type {boolean} - */ -var didWarnForDeprecatedBehavior = false; - -/** - * Warn if API is deprecated - * @param {Array.<*>} options - */ -function warnForDeprecatedBehavior(options) { - if (didWarnForDeprecatedBehavior) { - return; - } - didWarnForDeprecatedBehavior = true; - - var config = getConfig(options); - - /* istanbul ignore if */ - if (config.oldBehavior) { - // eslint-disable-next-line - console.warn('The rule `angular/service-name` will be split up to different rules in the next version. Please read the docs for more information'); - } -} - -module.exports = function(context) { - // Warn if needed for breaking changes in API in new versions - warnForDeprecatedBehavior(context.options); - - return { - - CallExpression: function(node) { - var config = getConfig(context.options); - var prefix = getPrefixFromOptions(context.options); - var convertedPrefix; // convert string from JSON .eslintrc to regex - var isService; - - if (prefix === undefined) { - return; - } +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }, { + type: 'object' + }] + }, + create: function(context) { + return { + + CallExpression: function(node) { + var config = getConfig(context.options); + var prefix = getPrefixFromOptions(context.options); + var convertedPrefix; // convert string from JSON .eslintrc to regex + var isService; + + if (prefix === undefined) { + return; + } - convertedPrefix = utils.convertPrefixToRegex(prefix); + convertedPrefix = utils.convertPrefixToRegex(prefix); - if (config.oldBehavior) { - isService = utils.isAngularServiceDeclarationDeprecated(node); - } else { - isService = utils.isAngularServiceDeclaration(node); - } + if (config.oldBehavior) { + isService = utils.isAngularServiceDeclarationDeprecated(node); + // Warning that the API is deprecated + // eslint-disable-next-line + console.warn('The rule `angular/service-name` will be split up to different rules in the next version. Please read the docs for more information'); + } else { + isService = utils.isAngularServiceDeclaration(node); + } - if (isService) { - var name = node.arguments[0].value; + if (isService) { + var name = node.arguments[0].value; - if (name !== undefined && name.indexOf('$') === 0) { - context.report(node, 'The {{service}} service should not start with "$". This is reserved for AngularJS services', { - service: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{service}} service should be prefixed by {{prefix}}', { - service: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{service}} service should follow this pattern: {{prefix}}', { - service: name, - prefix: prefix.toString() + if (name !== undefined && name.indexOf('$') === 0) { + context.report(node, 'The {{service}} service should not start with "$". This is reserved for AngularJS services', { + service: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{service}} service should be prefixed by {{prefix}}', { + service: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{service}} service should follow this pattern: {{prefix}}', { + service: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/timeout-service.js b/rules/timeout-service.js index 822b3fcf..c02272d0 100644 --- a/rules/timeout-service.js +++ b/rules/timeout-service.js @@ -10,25 +10,41 @@ */ 'use strict'; -module.exports = function(context) { - var message = 'You should use the $timeout service instead of the default window.setTimeout method'; +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + var message = 'You should use the $timeout service instead of the default window.setTimeout method'; - return { + return { - MemberExpression: function(node) { - if (node.object.name === 'window' && node.property.name === 'setTimeout') { - context.report(node, message, {}); - } - }, + MemberExpression: function(node) { + if (node.property.name !== 'setTimeout') { + return; + } + + if (node.object.type === 'Identifier') { + if ((node.object.name === 'window' || node.object.name === '$window')) { + context.report(node, message, {}); + } + + return; + } - CallExpression: function(node) { - if (node.callee.name === 'setTimeout') { - context.report(node, message, {}); + // Detect expression this.$window.setTimeout which is what we would see in ES6 code when using classes + var parentNode = node.object; + + if (parentNode.object.type === 'ThisExpression' && parentNode.property.name === '$window') { + context.report(node, message, {}); + } + }, + + CallExpression: function(node) { + if (node.callee.name === 'setTimeout') { + context.report(node, message, {}); + } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/typecheck-array.js b/rules/typecheck-array.js index 64f4ac84..f949336b 100644 --- a/rules/typecheck-array.js +++ b/rules/typecheck-array.js @@ -11,31 +11,32 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function recordError(node, origin) { - if (node.type === 'Literal' && node.value === '[object Array]') { - context.report(origin, 'You should use the angular.isArray method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function recordError(node, origin) { + if (node.type === 'Literal' && node.value === '[object Array]') { + context.report(origin, 'You should use the angular.isArray method', {}); + } } - } - return { - MemberExpression: function(node) { - if (node.object.name === 'Array' && node.property.name === 'isArray') { - context.report(node, 'You should use the angular.isArray method', {}); - } - }, - BinaryExpression: function(node) { - if (node.operator === '===' || node.operator === '!==') { - if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { - recordError(node.right, node); - } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { - recordError(node.left, node); + return { + MemberExpression: function(node) { + if (node.object.name === 'Array' && node.property.name === 'isArray') { + context.report(node, 'You should use the angular.isArray method', {}); + } + }, + BinaryExpression: function(node) { + if (node.operator === '===' || node.operator === '!==') { + if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { + recordError(node.right, node); + } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { + recordError(node.left, node); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/typecheck-date.js b/rules/typecheck-date.js index 25e6ae66..83d95119 100644 --- a/rules/typecheck-date.js +++ b/rules/typecheck-date.js @@ -11,27 +11,28 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function recordError(node, origin) { - if (node.type === 'Literal' && node.value === '[object Date]') { - context.report(origin, 'You should use the angular.isDate method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function recordError(node, origin) { + if (node.type === 'Literal' && node.value === '[object Date]') { + context.report(origin, 'You should use the angular.isDate method', {}); + } } - } - return { + return { - BinaryExpression: function(node) { - if (node.operator === '===' || node.operator === '!==') { - if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { - recordError(node.right, node); - } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { - recordError(node.left, node); + BinaryExpression: function(node) { + if (node.operator === '===' || node.operator === '!==') { + if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { + recordError(node.right, node); + } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { + recordError(node.left, node); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/typecheck-function.js b/rules/typecheck-function.js index 36f4ac6a..73ccee00 100644 --- a/rules/typecheck-function.js +++ b/rules/typecheck-function.js @@ -11,27 +11,28 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function recordError(node, origin) { - if (node.type === 'Literal' && (node.value === 'function' || node.value === '[object Function]')) { - context.report(origin, 'You should use the angular.isFunction method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function recordError(node, origin) { + if (node.type === 'Literal' && (node.value === 'function' || node.value === '[object Function]')) { + context.report(origin, 'You should use the angular.isFunction method', {}); + } } - } - return { + return { - BinaryExpression: function(node) { - if (node.operator === '===' || node.operator === '!==') { - if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { - recordError(node.right, node); - } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { - recordError(node.left, node); + BinaryExpression: function(node) { + if (node.operator === '===' || node.operator === '!==') { + if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { + recordError(node.right, node); + } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { + recordError(node.left, node); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/typecheck-number.js b/rules/typecheck-number.js index 706418a0..956dc9cc 100644 --- a/rules/typecheck-number.js +++ b/rules/typecheck-number.js @@ -11,28 +11,29 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function recordError(node, origin) { - if (node.type === 'Literal' && (node.value === 'number' || node.value === '[object Number]')) { - context.report(origin, 'You should use the angular.isNumber method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function recordError(node, origin) { + if (node.type === 'Literal' && (node.value === 'number' || node.value === '[object Number]')) { + context.report(origin, 'You should use the angular.isNumber method', {}); + } } - } - return { + return { - BinaryExpression: function(node) { - if (node.operator === '===' || node.operator === '!==') { - if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { - recordError(node.right, node); - } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { - recordError(node.left, node); + BinaryExpression: function(node) { + if (node.operator === '===' || node.operator === '!==') { + if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { + recordError(node.right, node); + } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { + recordError(node.left, node); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/typecheck-object.js b/rules/typecheck-object.js index 16ae0ba2..67169e94 100644 --- a/rules/typecheck-object.js +++ b/rules/typecheck-object.js @@ -11,26 +11,27 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function recordError(node, origin) { - if (node.type === 'Literal' && (node.value === 'object' || node.value === '[object Object]')) { - context.report(origin, 'You should use the angular.isObject method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function recordError(node, origin) { + if (node.type === 'Literal' && (node.value === 'object' || node.value === '[object Object]')) { + context.report(origin, 'You should use the angular.isObject method', {}); + } } - } - return { - BinaryExpression: function(node) { - if (node.operator === '===' || node.operator === '!==') { - if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { - recordError(node.right, node); - } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { - recordError(node.left, node); + return { + BinaryExpression: function(node) { + if (node.operator === '===' || node.operator === '!==') { + if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { + recordError(node.right, node); + } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { + recordError(node.left, node); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/typecheck-string.js b/rules/typecheck-string.js index a4759d1b..7a7e69ab 100644 --- a/rules/typecheck-string.js +++ b/rules/typecheck-string.js @@ -11,27 +11,28 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - function recordError(node, origin) { - if (node.type === 'Literal' && (node.value === 'string' || node.value === '[object String]')) { - context.report(origin, 'You should use the angular.isString method', {}); +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + function recordError(node, origin) { + if (node.type === 'Literal' && (node.value === 'string' || node.value === '[object String]')) { + context.report(origin, 'You should use the angular.isString method', {}); + } } - } - return { + return { - BinaryExpression: function(node) { - if (node.operator === '===' || node.operator === '!==') { - if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { - recordError(node.right, node); - } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { - recordError(node.left, node); + BinaryExpression: function(node) { + if (node.operator === '===' || node.operator === '!==') { + if (utils.isTypeOfStatement(node.left) || utils.isToStringStatement(node.left)) { + recordError(node.right, node); + } else if (utils.isTypeOfStatement(node.right) || utils.isToStringStatement(node.right)) { + recordError(node.left, node); + } } } - } - }; + }; + } }; - -module.exports.schema = [ - // JSON Schema for rule options goes here -]; diff --git a/rules/utils/angular-rule.js b/rules/utils/angular-rule.js index 287c064f..90254b90 100644 --- a/rules/utils/angular-rule.js +++ b/rules/utils/angular-rule.js @@ -197,7 +197,7 @@ function angularRule(ruleDefinition) { return; } if (node.type === 'ArrayExpression') { - node = node.elements[node.elements.length - 1]; + node = node.elements[node.elements.length - 1] || {}; } if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') { return node; @@ -205,6 +205,7 @@ function angularRule(ruleDefinition) { if (node.type !== 'Identifier') { return; } + var func; scope.variables.some(function(variable) { if (variable.name === node.name) { diff --git a/rules/utils/false-values.js b/rules/utils/false-values.js new file mode 100644 index 00000000..fcb55de0 --- /dev/null +++ b/rules/utils/false-values.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + config: ['jwtOptionsProvider', 'ngTableHelperProvider', '$ocLazyLoadProvider', '$route', '$localForageProvider'] +}; diff --git a/rules/utils/utils.js b/rules/utils/utils.js index a34335a1..9bfbc694 100644 --- a/rules/utils/utils.js +++ b/rules/utils/utils.js @@ -1,5 +1,5 @@ 'use strict'; - +var falseConfigValues = require('./false-values').config; var scopeProperties = [ '$id', @@ -255,7 +255,7 @@ function isStringRegexp(string) { function isAngularComponent(node) { return node.arguments !== undefined && node.arguments.length === 2 && - isLiteralType(node.arguments[0]) && + (isLiteralType(node.arguments[0]) || isIdentifierType(node.arguments[0])) && (isIdentifierType(node.arguments[1]) || isFunctionType(node.arguments[1]) || isArrayType(node.arguments[1]) || @@ -487,7 +487,8 @@ function isAngularRunSection(node) { function isAngularConfigSection(node) { return isMemberExpression(node.callee) && node.callee.property.type === 'Identifier' && - node.callee.property.name === 'config'; + node.callee.property.name === 'config' && + falseConfigValues.indexOf(node.callee.object.name) < 0; } /** diff --git a/rules/value-name.js b/rules/value-name.js index 0d0f039c..49f1904c 100644 --- a/rules/value-name.js +++ b/rules/value-name.js @@ -15,42 +15,49 @@ var utils = require('./utils/utils'); -module.exports = function(context) { - return { +module.exports = { + meta: { + schema: [{ + type: ['string', 'object'] + }] + }, + create: function(context) { + return { + + CallExpression: function(node) { + var prefix = context.options[0]; + var convertedPrefix; // convert string from JSON .eslintrc to regex + var isValue; + + if (prefix === undefined) { + return; + } - CallExpression: function(node) { - var prefix = context.options[0]; - var convertedPrefix; // convert string from JSON .eslintrc to regex - var isValue; + convertedPrefix = utils.convertPrefixToRegex(prefix); + isValue = utils.isAngularValueDeclaration(node); - if (prefix === undefined) { - return; - } + if (isValue) { + var name = node.arguments[0].value; - convertedPrefix = utils.convertPrefixToRegex(prefix); - isValue = utils.isAngularValueDeclaration(node); - - if (isValue) { - var name = node.arguments[0].value; - - if (name !== undefined && name.indexOf('$') === 0) { - context.report(node, 'The {{value}} value should not start with "$". This is reserved for AngularJS services', { - value: name - }); - } else if (name !== undefined && !convertedPrefix.test(name)) { - if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { - context.report(node, 'The {{value}} value should be prefixed by {{prefix}}', { - value: name, - prefix: prefix - }); - } else { - context.report(node, 'The {{value}} value should follow this pattern: {{prefix}}', { - value: name, - prefix: prefix.toString() + if (name !== undefined && name.indexOf('$') === 0) { + context.report(node, 'The {{value}} value should not start with "$". This is reserved for AngularJS services', { + value: name }); + } else if (name !== undefined && !convertedPrefix.test(name)) { + if (typeof prefix === 'string' && !utils.isStringRegexp(prefix)) { + context.report(node, 'The {{value}} value should be prefixed by {{prefix}}', { + value: name, + prefix: prefix + }); + } else { + context.report(node, 'The {{value}} value should follow this pattern: {{prefix}}', { + value: name, + prefix: prefix.toString() + }); + } } } } - } - }; + }; + } }; diff --git a/rules/watchers-execution.js b/rules/watchers-execution.js index 8ccd08b6..f0920fce 100644 --- a/rules/watchers-execution.js +++ b/rules/watchers-execution.js @@ -10,25 +10,28 @@ */ 'use strict'; -module.exports = function(context) { - var method = context.options[0] || '$digest'; - var methods = ['$apply', '$digest']; - return { +module.exports = { + meta: { + schema: [{ + enum: ['$apply', '$digest'] + }] + }, + create: function(context) { + var method = context.options[0] || '$digest'; + var methods = ['$apply', '$digest']; + return { - MemberExpression: function(node) { - var forbiddenMethod = methods.filter(function(m) { - return m !== method; - }); - if (forbiddenMethod.length > 0 && node.property.type === 'Identifier' && forbiddenMethod.indexOf(node.property.name) >= 0) { - context.report(node, 'Instead of using the {{forbidden}}() method, you should prefer {{method}}()', { - forbidden: node.property.name, - method: method + MemberExpression: function(node) { + var forbiddenMethod = methods.filter(function(m) { + return m !== method; }); + if (forbiddenMethod.length > 0 && node.property.type === 'Identifier' && forbiddenMethod.indexOf(node.property.name) >= 0) { + context.report(node, 'Instead of using the {{forbidden}}() method, you should prefer {{method}}()', { + forbidden: node.property.name, + method: method + }); + } } - } - }; + }; + } }; - -module.exports.schema = [{ - enum: ['$apply', '$digest'] -}]; diff --git a/rules/window-service.js b/rules/window-service.js index 56939f47..cb918808 100644 --- a/rules/window-service.js +++ b/rules/window-service.js @@ -10,16 +10,19 @@ */ 'use strict'; -module.exports = function(context) { - var restrict = ['document', 'setInterval', 'setTimeout']; - return { +module.exports = { + meta: { + schema: [] + }, + create: function(context) { + var restrict = ['document', 'setInterval', 'setTimeout']; + return { - MemberExpression: function(node) { - if (node.object.name === 'window' && restrict.indexOf(node.property.name) < 0) { - context.report(node, 'You should use the $window service instead of the default window object', {}); + MemberExpression: function(node) { + if (node.object.name === 'window' && restrict.indexOf(node.property.name) < 0) { + context.report(node, 'You should use the $window service instead of the default window object', {}); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/test/controller-as.js b/test/controller-as.js index be982cc3..fc9bb6b1 100644 --- a/test/controller-as.js +++ b/test/controller-as.js @@ -19,6 +19,16 @@ eslintTester.run('controller-as', rule, { 'angular.module("test").controller("Test", function() {doSomething($scope)} )' ].concat(commonFalsePositives), invalid: [ + {code: '' + + 'var controllerId = \'UserController\';' + + 'angular.module(\'inspinia\')' + + '.controller(controllerId, UserController);' + + + 'function UserController(UserService, $compile, $scope, $q, CommonEnum, CommonService, $uibModal, TenantService) {' + + '$scope.myform = {};' + + '}', + errors: [{message: 'You should not set properties on $scope in controllers. Use controllerAs syntax and add data to "this"'}] + }, {code: 'angular.module("test").controller("Test", function() {$scope.name = "test"} );', errors: [{message: 'You should not set properties on $scope in controllers. Use controllerAs syntax and add data to "this"'}]}, {code: 'angular.module("test").controller("Test", function() {var test = function() {$scope.thing = "none"};} );', diff --git a/test/di.js b/test/di.js index 60b3a403..6bd77cd4 100644 --- a/test/di.js +++ b/test/di.js @@ -199,6 +199,12 @@ angularNamedObjectList.forEach(function(object) { valid.push({ + code: 'vngTableHelperProvider.config({count: 10}, {});', + options: ['function'] +}, { + code: 'angular.module("MyModule").value("emptyArray", []);', + options: ['function'] +}, { code: 'vm.navRoutes = states.filter(x).sort(y);', options: ['function'] }, { diff --git a/test/module-getter.js b/test/module-getter.js index 9b564688..6d03200c 100644 --- a/test/module-getter.js +++ b/test/module-getter.js @@ -15,6 +15,14 @@ var commonFalsePositives = require('./utils/commonFalsePositives'); var eslintTester = new RuleTester(); eslintTester.run('module-getter', rule, { valid: [ + 'angular.module("module").config(function(jwtOptionsProvider){' + + 'jwtOptionsProvider.config({' + + 'whiteListedDomains: []' + + '});' + + '})', + 'angular.module("module").config(function(ngTableHelperProvider){' + + 'ngTableHelperProvider.config({count: 10}, {});' + + '})', 'angular.module("module").controller("TestCtrl", function() {});', 'angular.module("module").factory("TestService", function() {});', 'angular.module("module").service("TestService", function() {});', diff --git a/test/timeout-service.js b/test/timeout-service.js index eaa23b39..1b1f53f1 100644 --- a/test/timeout-service.js +++ b/test/timeout-service.js @@ -14,16 +14,21 @@ var commonFalsePositives = require('./utils/commonFalsePositives'); var eslintTester = new RuleTester(); +var message = 'You should use the $timeout service instead of the default window.setTimeout method'; + eslintTester.run('timeout-service', rule, { valid: [ '$timeout(function() {})', '$timeout(function() {}, 1000)', - '$timeout(function() {}, 1000, true)' + '$timeout(function() {}, 1000, true)', + 'nonWindowObject.setTimeout(function() {})' ].concat(commonFalsePositives), invalid: [ - {code: 'window.setTimeout(function() {}, 1000)', errors: [{message: 'You should use the $timeout service instead of the default window.setTimeout method'}]}, - {code: 'window.setTimeout(function() {}, 1000, param1)', errors: [{message: 'You should use the $timeout service instead of the default window.setTimeout method'}]}, - {code: 'setTimeout(function() {}, 1000)', errors: [{message: 'You should use the $timeout service instead of the default window.setTimeout method'}]}, - {code: 'setTimeout(function() {}, 1000, param1)', errors: [{message: 'You should use the $timeout service instead of the default window.setTimeout method'}]} + {code: 'window.setTimeout(function() {}, 1000)', errors: [{message: message}]}, + {code: 'window.setTimeout(function() {}, 1000, param1)', errors: [{message: message}]}, + {code: '$window.setTimeout(function() {}, 1000)', errors: [{message: message}]}, + {code: 'this.$window.setTimeout(function() {}, 1000)', errors: [{message: message}]}, + {code: 'setTimeout(function() {}, 1000)', errors: [{message: message}]}, + {code: 'setTimeout(function() {}, 1000, param1)', errors: [{message: message}]} ] });