2020-05-19 11:43:42 +03:00

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');
});
}
}
};
}
};