eslint-plugin-angular
Advanced tools
Comparing version 1.6.1 to 1.6.2
{ | ||
"name": "eslint-plugin-angular", | ||
"version": "1.6.1", | ||
"version": "1.6.2", | ||
"description": "ESLint rules for AngularJS projects", | ||
@@ -32,6 +32,6 @@ "main": "index.js", | ||
"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", | ||
@@ -38,0 +38,0 @@ "shelljs": "^0.7.1", |
@@ -13,12 +13,15 @@ /** | ||
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 = []; |
@@ -21,34 +21,37 @@ /** | ||
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 | ||
}; | ||
}) | ||
}; |
@@ -16,42 +16,49 @@ /** | ||
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 | ||
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 { | ||
context.report(node, 'The {{component}} component should follow this pattern: {{prefix}}', { | ||
component: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -18,40 +18,49 @@ /** | ||
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); | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
isConstant = utils.isAngularConstantDeclaration(node); | ||
if (isConstant) { | ||
var name = node.arguments[0].value; | ||
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 | ||
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 { | ||
context.report(node, 'The {{constant}} constant should follow this pattern: {{prefix}}', { | ||
constant: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -15,74 +15,79 @@ /** | ||
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 | ||
}); | ||
} | ||
}); | ||
} | ||
} 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 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 (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; | ||
} | ||
}); | ||
} | ||
} | ||
} 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 (!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 (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 | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -21,82 +21,85 @@ /** | ||
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' | ||
}]; |
@@ -17,62 +17,65 @@ /** | ||
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)); | ||
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(); | ||
} | ||
}, | ||
// 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(); | ||
} | ||
}; | ||
}; | ||
} | ||
}; | ||
module.exports.schema = [{ | ||
type: ['object', 'string'] | ||
}]; |
@@ -18,44 +18,47 @@ /** | ||
module.exports = function(context) { | ||
return { | ||
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 | ||
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); | ||
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 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; | ||
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() | ||
}); | ||
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' | ||
} | ||
]; |
@@ -14,15 +14,18 @@ /** | ||
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 = []; |
@@ -16,42 +16,45 @@ /** | ||
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 = []; |
@@ -17,41 +17,50 @@ /** | ||
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; | ||
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; | ||
} | ||
}); | ||
} | ||
var args = fn.params.map(function(arg) { | ||
var formattedArg = arg.name; | ||
if (stripUnderscores) { | ||
formattedArg = formattedArg.replace(/^_(.+)_$/, '$1'); | ||
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); | ||
} | ||
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); | ||
} | ||
}; | ||
}); | ||
}; | ||
}) | ||
}; |
@@ -15,60 +15,65 @@ /** | ||
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') { | ||
visitedScopes.some(function(scope) { | ||
if (scope.block !== fn) { | ||
return; | ||
} | ||
if (fn.params.indexOf(variable.identifiers[0]) === -1) { | ||
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); | ||
} | ||
}; | ||
}) | ||
}; |
187
rules/di.js
@@ -17,65 +17,49 @@ /** | ||
module.exports = angularRule(function(context) { | ||
var syntax = context.options[0] || 'function'; | ||
module.exports = { | ||
meta: { | ||
schema: [{ | ||
enum: [ | ||
'function', | ||
'array', | ||
'$inject' | ||
] | ||
}, { | ||
type: 'object', | ||
properties: { | ||
matchNames: { | ||
type: 'boolean' | ||
} | ||
} | ||
}] | ||
}, | ||
create: angularRule(function(context) { | ||
var syntax = context.options[0] || 'function'; | ||
var extra = context.options[1] || {}; | ||
var matchNames = extra.matchNames !== false; | ||
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 report(node) { | ||
context.report(node, 'You should use the {{syntax}} syntax for DI', { | ||
syntax: syntax | ||
}); | ||
} | ||
} | ||
function checkDi(callee, fn) { | ||
if (!fn) { | ||
return; | ||
} | ||
var $injectProperties = {}; | ||
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; | ||
} | ||
} | ||
} else { | ||
if (fn.params.length === 0) { | ||
return; | ||
} | ||
report(fn); | ||
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 === 'function') { | ||
if (utils.isArrayType(fn.parent)) { | ||
report(fn); | ||
function checkDi(callee, fn) { | ||
if (!fn) { | ||
return; | ||
} | ||
} | ||
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) { | ||
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', {}); | ||
@@ -86,6 +70,6 @@ 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', {}); | ||
@@ -96,43 +80,62 @@ return; | ||
} else { | ||
if (fn.params.length === 0) { | ||
return; | ||
} | ||
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); | ||
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); | ||
} | ||
} | ||
} | ||
}; | ||
}); | ||
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); | ||
} | ||
}; | ||
}) | ||
}; |
@@ -18,42 +18,49 @@ /** | ||
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 | ||
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 { | ||
context.report(node, 'The {{directive}} directive should follow this pattern: {{prefix}}', { | ||
directive: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -18,90 +18,93 @@ /** | ||
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 | ||
}); | ||
} | ||
// Example RegExp for AE: /^A?E?$/ | ||
var restrictRegExp = new RegExp('^' + restrictChars.join('?') + '?$'); | ||
var foundDirectives = []; | ||
var checkedDirectives = []; | ||
var defaultRestrictions = ['AE', 'EA']; | ||
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') { | ||
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 | ||
}); | ||
} | ||
checkedDirectives.push(directiveNode); | ||
} | ||
}; | ||
}; | ||
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'] | ||
} | ||
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'); | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}]; | ||
}; |
@@ -13,14 +13,15 @@ /** | ||
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 | ||
]; |
@@ -16,58 +16,63 @@ /** | ||
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' | ||
}); | ||
} | ||
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' | ||
}); | ||
} | ||
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); | ||
}); | ||
// Validate the sorting order | ||
var lastValid; | ||
valid.forEach(function(statement) { | ||
if (!lastValid) { | ||
lastValid = statement.expression.left.name; | ||
return { | ||
'angular:inject': function(callExpression, fn) { | ||
if (!fn) { | ||
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 | ||
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 | ||
}); | ||
}); | ||
} | ||
}; | ||
}) | ||
}; |
@@ -15,29 +15,30 @@ /** | ||
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]; | ||
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); | ||
} | ||
} | ||
if (utils.isFunctionType(fn) && utils.isEmptyFunction(fn)) { | ||
report(node, name); | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; | ||
module.exports.schema = [ | ||
// JSON Schema for rule options goes here | ||
]; |
@@ -18,40 +18,49 @@ /** | ||
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); | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
isFactory = utils.isAngularFactoryDeclaration(node); | ||
if (isFactory) { | ||
var name = node.arguments[0].value; | ||
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 | ||
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 { | ||
context.report(node, 'The {{factory}} factory should follow this pattern: {{prefix}}', { | ||
factory: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -26,109 +26,116 @@ /** | ||
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.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; | ||
} | ||
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 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 | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
}; | ||
}()); | ||
}()) | ||
}; |
@@ -16,33 +16,41 @@ /** | ||
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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; | ||
@@ -12,14 +12,15 @@ /** | ||
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 | ||
]; |
@@ -18,56 +18,59 @@ /** | ||
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]; | ||
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]; | ||
if (context.options[1]) { | ||
angularObjectList = context.options[1]; | ||
} | ||
if (context.options[1]) { | ||
angularObjectList = context.options[1]; | ||
} | ||
function checkType(arg) { | ||
return utils.isCallExpression(arg) || | ||
(configType === 'named' && (utils.isIdentifierType(arg) || utils.isNamedInlineFunction(arg))) || | ||
(configType === 'anonymous' && utils.isFunctionType(arg) && !utils.isNamedInlineFunction(arg)); | ||
} | ||
function checkType(arg) { | ||
return utils.isCallExpression(arg) || | ||
(configType === 'named' && (utils.isIdentifierType(arg) || utils.isNamedInlineFunction(arg))) || | ||
(configType === 'anonymous' && utils.isFunctionType(arg) && !utils.isNamedInlineFunction(arg)); | ||
} | ||
return { | ||
return { | ||
CallExpression: function(node) { | ||
var callee = node.callee; | ||
var angularObjectName = callee.property && callee.property.name; | ||
var firstArgument = node.arguments[1]; | ||
CallExpression: function(node) { | ||
var callee = node.callee; | ||
var angularObjectName = callee.property && callee.property.name; | ||
var firstArgument = node.arguments[1]; | ||
if (utils.isAngularComponent(node) && callee.type === 'MemberExpression' && angularObjectList.indexOf(angularObjectName) >= 0) { | ||
if (checkType(firstArgument)) { | ||
return; | ||
} | ||
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; | ||
} | ||
if (utils.isArrayType(firstArgument)) { | ||
var last = firstArgument.elements[firstArgument.elements.length - 1]; | ||
if (checkType(last) || (!utils.isFunctionType(last) && !utils.isIdentifierType(last))) { | ||
return; | ||
} | ||
} | ||
context.report(node, message, {}); | ||
} | ||
context.report(node, message, {}); | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; | ||
module.exports.schema = [{ | ||
enum: [ | ||
'named', | ||
'anonymous' | ||
] | ||
}, { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
} | ||
}]; |
@@ -13,23 +13,24 @@ /** | ||
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 | ||
]; |
@@ -13,19 +13,20 @@ /** | ||
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 | ||
]; |
@@ -11,17 +11,18 @@ /** | ||
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 | ||
]; |
@@ -20,87 +20,127 @@ /** | ||
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; | ||
function checkLiteral(node) { | ||
if (node && node.type !== 'Literal') { | ||
context.report(node, 'Unexpected non-literal value'); | ||
return false; | ||
} | ||
if (!node) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
if (!node) { | ||
return false; | ||
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 | ||
}); | ||
} | ||
}); | ||
} | ||
return true; | ||
} | ||
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 isStandardModule(value) { | ||
return standard.indexOf(value) !== -1; | ||
} | ||
function isStandardModule(value) { | ||
return standard.indexOf(value) !== -1; | ||
} | ||
function isCustomModule(value) { | ||
return moduleRegex && moduleRegex.test(value); | ||
} | ||
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; | ||
} | ||
var value = node.value; | ||
if (lastCorrect === undefined) { | ||
lastCorrect = value; | ||
if (isCustomModule(value)) { | ||
group = 'custom'; | ||
} else if (standard.indexOf(value) === -1) { | ||
group = 'third party'; | ||
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}}', { | ||
@@ -113,74 +153,37 @@ current: 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 | ||
}); | ||
}); | ||
} | ||
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 { | ||
lastCorrect = value; | ||
checkCombined(deps); | ||
} | ||
} | ||
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 | ||
}); | ||
} | ||
} | ||
}); | ||
}; | ||
} | ||
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'] | ||
} | ||
} | ||
}]; |
@@ -16,7 +16,11 @@ /** | ||
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) || | ||
@@ -32,17 +36,14 @@ utils.isAngularServiceDeclaration(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 | ||
]; |
@@ -17,38 +17,45 @@ /** | ||
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 | ||
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 { | ||
context.report(node, 'The {{module}} module should follow this pattern: {{prefix}}', { | ||
module: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -16,27 +16,28 @@ /** | ||
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 | ||
]; |
@@ -33,20 +33,21 @@ /** | ||
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 | ||
]; |
@@ -14,15 +14,16 @@ /** | ||
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 | ||
]; |
@@ -13,15 +13,16 @@ /** | ||
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 | ||
]; |
@@ -17,87 +17,90 @@ /** | ||
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; | ||
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.'); | ||
} | ||
}); | ||
} | ||
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; | ||
} | ||
}); | ||
}, | ||
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; | ||
} | ||
// 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' | ||
} | ||
} | ||
}]; | ||
}; | ||
}) | ||
}; |
@@ -13,36 +13,41 @@ /** | ||
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)); | ||
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'; | ||
} | ||
} | ||
if (node.callee.type === 'Identifier') { | ||
return node.callee.name === '$http'; | ||
} | ||
} | ||
return { | ||
CallExpression: function(node) { | ||
if (node.callee.type !== 'MemberExpression') { | ||
return; | ||
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'); | ||
} | ||
} | ||
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'); | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -14,39 +14,42 @@ /** | ||
module.exports = function(context) { | ||
// Extracts any HTML tags. | ||
var regularTagPattern = /<(.+?)>/g; | ||
// Extracts self closing HTML tags. | ||
var selfClosingTagPattern = /<(.+?)\/>/g; | ||
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; | ||
var allowSimple = (context.options[0] && context.options[0].allowSimple) !== false; | ||
var allowSimple = (context.options[0] && context.options[0].allowSimple) !== false; | ||
function reportComplex(node) { | ||
context.report(node, 'Inline template is too complex. Use an external template instead'); | ||
} | ||
function reportComplex(node) { | ||
context.report(node, 'Inline template is too complex. Use an external template instead'); | ||
} | ||
return { | ||
Property: function(node) { | ||
if (node.key.name !== 'template' || node.value.type !== 'Literal') { | ||
return; | ||
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); | ||
} | ||
} | ||
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); | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; | ||
module.exports.schema = [{ | ||
allowSimple: { | ||
type: 'boolean' | ||
} | ||
}]; |
@@ -11,20 +11,21 @@ /** | ||
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 | ||
]; |
@@ -14,32 +14,35 @@ /** | ||
module.exports = function(context) { | ||
var options = context.options[0] || {}; | ||
var allowed = options.allow || []; | ||
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 || []; | ||
function check(node, name) { | ||
if (name.slice(0, 2) === '$$' && allowed.indexOf(name) < 0) { | ||
context.report(node, 'Using $$-prefixed Angular objects/methods are not recommended', {}); | ||
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 { | ||
return { | ||
Identifier: function(node) { | ||
check(node, node.name); | ||
} | ||
}; | ||
}; | ||
module.exports.schema = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
allow: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
} | ||
Identifier: function(node) { | ||
check(node, node.name); | ||
} | ||
}, | ||
additionalProperties: false | ||
}; | ||
} | ||
]; | ||
}; |
@@ -16,46 +16,49 @@ /** | ||
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); | ||
return { | ||
'angular:run': function(callExpression, fn) { | ||
if (!fn) { | ||
return; | ||
} | ||
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'); | ||
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' | ||
} | ||
} | ||
}]; | ||
} | ||
}; | ||
}) | ||
}; |
@@ -15,11 +15,16 @@ /** | ||
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()', {}); | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -18,80 +18,82 @@ /** | ||
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'; | ||
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 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; | ||
} | ||
angularObjectList = result; | ||
} | ||
function isSetBedService(serviceName, angularObjectName) { | ||
if (map) { | ||
return map[angularObjectName].indexOf(serviceName) >= 0; | ||
function isSetBedService(serviceName, angularObjectName) { | ||
if (map) { | ||
return map[angularObjectName].indexOf(serviceName) >= 0; | ||
} | ||
return badServices.indexOf(serviceName) >= 0; | ||
} | ||
return badServices.indexOf(serviceName) >= 0; | ||
} | ||
return { | ||
return { | ||
CallExpression: function(node) { | ||
var callee = node.callee; | ||
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.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 + ')', {}); | ||
} | ||
}); | ||
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' | ||
}]; |
@@ -11,52 +11,53 @@ /** | ||
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 | ||
]; |
@@ -11,72 +11,73 @@ /** | ||
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 (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 { | ||
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 | ||
]; |
@@ -14,118 +14,123 @@ /** | ||
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; | ||
function checkArgumentPositionInFunction(node) { | ||
if (!node.params || node.params.length < 2) { | ||
return; | ||
} | ||
var linesFound = []; | ||
node.params.forEach(reportMultipleItemsInOneLine.bind(null, node, linesFound)); | ||
} | ||
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 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 | ||
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); | ||
} | ||
}); | ||
} | ||
linesFound.push(currentLine); | ||
} | ||
function checkArgumentPositionArrayExpression(angularComponentNode, arrayNode) { | ||
var linesFound = []; | ||
function findFunctionDeclarationByDeclaration(body, fName) { | ||
return body.find(function(item) { | ||
return item.type === 'FunctionDeclaration' && item.id.name === fName; | ||
}); | ||
} | ||
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 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; | ||
} | ||
}); | ||
} | ||
}); | ||
return fn; | ||
} | ||
function findFunctionDeclarationByDeclaration(body, fName) { | ||
return body.find(function(item) { | ||
return item.type === 'FunctionDeclaration' && item.id.name === fName; | ||
}); | ||
} | ||
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 getFunctionDeclaration(node, fName) { | ||
if (node.type === 'BlockStatement' || node.type === 'Program') { | ||
if (node.body) { | ||
var fn = findFunctionDeclarationByDeclaration(node.body, fName); | ||
if (fn) { | ||
return fn; | ||
} | ||
}); | ||
} | ||
}); | ||
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; | ||
} | ||
} | ||
fn = findFunctionDeclarationByVariableDeclaration(node.body, fName); | ||
if (fn) { | ||
return fn; | ||
} | ||
} | ||
if (node.parent) { | ||
return getFunctionDeclaration(node.parent, fName); | ||
} | ||
} | ||
if (node.parent) { | ||
return getFunctionDeclaration(node.parent, fName); | ||
} | ||
} | ||
return { | ||
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) { | ||
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]); | ||
} | ||
} | ||
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]); | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -14,65 +14,68 @@ /* | ||
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; | ||
} | ||
}); | ||
}, | ||
AssignmentExpression: function(node) { | ||
// Only check for literal member property assignments. | ||
if (node.left.type !== 'MemberExpression') { | ||
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; | ||
} | ||
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 = []; | ||
}; | ||
}) | ||
}; |
@@ -18,40 +18,49 @@ /** | ||
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); | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
isProvider = utils.isAngularProviderDeclaration(node); | ||
if (isProvider) { | ||
var name = node.arguments[0].value; | ||
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 | ||
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 { | ||
context.report(node, 'The {{provider}} provider should follow this pattern: {{prefix}}', { | ||
provider: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -15,44 +15,47 @@ /** | ||
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'; | ||
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 { | ||
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] | ||
}); | ||
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' | ||
}]; |
@@ -47,49 +47,58 @@ /** | ||
module.exports = function(context) { | ||
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; | ||
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; | ||
} | ||
if (prefix === undefined) { | ||
return; | ||
} | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
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 (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 | ||
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 { | ||
context.report(node, 'The {{service}} service should follow this pattern: {{prefix}}', { | ||
service: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -13,23 +13,39 @@ /** | ||
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; | ||
} | ||
CallExpression: function(node) { | ||
if (node.callee.name === 'setTimeout') { | ||
context.report(node, message, {}); | ||
if (node.object.type === 'Identifier') { | ||
if ((node.object.name === 'window' || node.object.name === '$window')) { | ||
context.report(node, message, {}); | ||
} | ||
return; | ||
} | ||
// 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 | ||
]; |
@@ -14,29 +14,30 @@ /** | ||
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 | ||
]; |
@@ -14,25 +14,26 @@ /** | ||
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 | ||
]; |
@@ -14,25 +14,26 @@ /** | ||
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 | ||
]; |
@@ -14,26 +14,27 @@ /** | ||
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 | ||
]; |
@@ -14,24 +14,25 @@ /** | ||
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 | ||
]; |
@@ -14,25 +14,26 @@ /** | ||
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 | ||
]; |
@@ -200,3 +200,3 @@ 'use strict'; | ||
if (node.type === 'ArrayExpression') { | ||
node = node.elements[node.elements.length - 1]; | ||
node = node.elements[node.elements.length - 1] || {}; | ||
} | ||
@@ -209,2 +209,3 @@ if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') { | ||
} | ||
var func; | ||
@@ -211,0 +212,0 @@ scope.variables.some(function(variable) { |
'use strict'; | ||
var falseConfigValues = require('./false-values').config; | ||
var scopeProperties = [ | ||
@@ -258,3 +258,3 @@ '$id', | ||
node.arguments.length === 2 && | ||
isLiteralType(node.arguments[0]) && | ||
(isLiteralType(node.arguments[0]) || isIdentifierType(node.arguments[0])) && | ||
(isIdentifierType(node.arguments[1]) || | ||
@@ -491,3 +491,4 @@ isFunctionType(node.arguments[1]) || | ||
node.callee.property.type === 'Identifier' && | ||
node.callee.property.name === 'config'; | ||
node.callee.property.name === 'config' && | ||
falseConfigValues.indexOf(node.callee.object.name) < 0; | ||
} | ||
@@ -494,0 +495,0 @@ |
@@ -18,40 +18,47 @@ /** | ||
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; | ||
CallExpression: function(node) { | ||
var prefix = context.options[0]; | ||
var convertedPrefix; // convert string from JSON .eslintrc to regex | ||
var isValue; | ||
if (prefix === undefined) { | ||
return; | ||
} | ||
if (prefix === undefined) { | ||
return; | ||
} | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
isValue = utils.isAngularValueDeclaration(node); | ||
convertedPrefix = utils.convertPrefixToRegex(prefix); | ||
isValue = utils.isAngularValueDeclaration(node); | ||
if (isValue) { | ||
var name = node.arguments[0].value; | ||
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 | ||
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 { | ||
context.report(node, 'The {{value}} value should follow this pattern: {{prefix}}', { | ||
value: name, | ||
prefix: prefix.toString() | ||
}); | ||
} 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() | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
} | ||
}; |
@@ -13,23 +13,26 @@ /** | ||
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'] | ||
}]; |
@@ -13,14 +13,17 @@ /** | ||
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 = []; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
196141
75
4479
81579