eslint-plugin-angular
Advanced tools
Comparing version
{ | ||
"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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
196141
6.08%75
1.35%4479
6.06%