163 lines
5.8 KiB
JavaScript
163 lines
5.8 KiB
JavaScript
/**
|
|
* disallow any other directive restrict than 'A' or 'E'
|
|
*
|
|
* Not all directive restrictions may be desirable.
|
|
* Also it might be desirable to define default restrictions, or explicitly not.
|
|
* The default configuration limits the restrictions `AE` and disallows explicitly specifying a default.
|
|
* ("directive-restrict": [0, {"restrict": "AE", "explicit": "never"}])
|
|
*
|
|
* @styleguideReference {johnpapa} `y074` Restrict to Elements and Attributes
|
|
* @version 0.12.0
|
|
* @category bestPractice
|
|
* @sinceAngularVersion 1.x
|
|
*/
|
|
'use strict';
|
|
|
|
var utils = require('./utils/utils');
|
|
|
|
module.exports = {
|
|
meta: {
|
|
docs: {
|
|
url: 'https://github.com/Gillespie59/eslint-plugin-angular/blob/master/docs/rules/directive-restrict.md'
|
|
},
|
|
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']
|
|
}
|
|
}
|
|
}]
|
|
},
|
|
create: 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;
|
|
}
|
|
});
|
|
|
|
if (!directiveNode) {
|
|
// Try to find an ancestor function used as definition for one of the found directives
|
|
context.getAncestors().some(function(ancestor) {
|
|
if (isFunctionDeclaration(ancestor)) {
|
|
if (!ancestor.id) {
|
|
return false;
|
|
}
|
|
|
|
var fnName = ancestor.id.name;
|
|
|
|
var correspondingDirective = foundDirectives.find(function(directive) {
|
|
var directiveFnName = getDirectiveFunctionName(directive);
|
|
return directiveFnName === fnName;
|
|
});
|
|
|
|
directiveNode = correspondingDirective;
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
function isFunctionDeclaration(node) {
|
|
return node.type === 'FunctionDeclaration';
|
|
}
|
|
|
|
function getDirectiveFunctionName(node) {
|
|
var directiveArg = node.arguments[1];
|
|
|
|
// Three ways of creating a directive function: function expression,
|
|
// variable name that references a function, and an array with a function
|
|
// as the last item
|
|
if (utils.isFunctionType(directiveArg)) {
|
|
return directiveArg.id.name;
|
|
}
|
|
|
|
if (utils.isArrayType(directiveArg)) {
|
|
directiveArg = directiveArg.elements[directiveArg.elements.length - 1];
|
|
|
|
if (utils.isIdentifierType(directiveArg)) {
|
|
return directiveArg.name;
|
|
}
|
|
return directiveArg.id.name;
|
|
}
|
|
|
|
if (utils.isIdentifierType(directiveArg)) {
|
|
return directiveArg.name;
|
|
}
|
|
}
|
|
|
|
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');
|
|
});
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|