UNPKG

5.99 kBJavaScriptView Raw
1/**
2 * disallow any other directive restrict than 'A' or 'E'
3 *
4 * Not all directive restrictions may be desirable.
5 * Also it might be desirable to define default restrictions, or explicitly not.
6 * The default configuration limits the restrictions `AE` and disallows explicitly specifying a default.
7 * ("directive-restrict": [0, {"restrict": "AE", "explicit": "never"}])
8 *
9 * @styleguideReference {johnpapa} `y074` Restrict to Elements and Attributes
10 * @version 0.12.0
11 * @category bestPractice
12 * @sinceAngularVersion 1.x
13 */
14'use strict';
15
16var utils = require('./utils/utils');
17
18module.exports = {
19 meta: {
20 docs: {
21 url: 'https://github.com/Gillespie59/eslint-plugin-angular/blob/master/docs/rules/directive-restrict.md'
22 },
23 schema: [{
24 type: 'object',
25 properties: {
26 restrict: {
27 type: 'string',
28 pattern: '^A|C|E|(AC)|(CA)|(AE)|(EA)|(EC)|(CE)|(AEC)|(ACE)|(EAC)|(CAE)|(ACE)|(AEC)|(CAE)|(ACE)|(AEC)$'
29 },
30 explicit: {
31 enum: ['always', 'never']
32 }
33 }
34 }]
35 },
36 create: function(context) {
37 var options = context.options[0] || {};
38 var restrictOpt = options.restrict || 'AE';
39 var explicitRestrict = options.explicit === 'always';
40 var restrictChars = restrictOpt.split('');
41
42 // Example RegExp for AE: /^A?E?$/
43 var restrictRegExp = new RegExp('^' + restrictChars.join('?') + '?$');
44 var foundDirectives = [];
45 var checkedDirectives = [];
46 var defaultRestrictions = ['AE', 'EA'];
47
48 function checkLiteralNode(node) {
49 if (node.type !== 'Literal') {
50 return;
51 }
52 var directiveNode;
53 context.getAncestors().some(function(ancestor) {
54 if (utils.isAngularDirectiveDeclaration(ancestor)) {
55 directiveNode = ancestor;
56 return true;
57 }
58 });
59
60 if (!directiveNode) {
61 // Try to find an ancestor function used as definition for one of the found directives
62 context.getAncestors().some(function(ancestor) {
63 if (isFunctionDeclaration(ancestor)) {
64 if (!ancestor.id) {
65 return false;
66 }
67
68 var fnName = ancestor.id.name;
69
70 var correspondingDirective = foundDirectives.find(function(directive) {
71 var directiveFnName = getDirectiveFunctionName(directive);
72 return directiveFnName === fnName;
73 });
74
75 directiveNode = correspondingDirective;
76 return true;
77 }
78 });
79 }
80
81 // The restrict property was not defined inside of a directive.
82 if (!directiveNode) {
83 return;
84 }
85 if (!explicitRestrict && defaultRestrictions.indexOf(node.value) !== -1) {
86 context.report(node, 'No need to explicitly specify a default directive restriction');
87 return;
88 }
89
90 if (!restrictRegExp.test(node.value)) {
91 context.report(directiveNode, 'Disallowed directive restriction. It must be one of {{allowed}} in that order', {
92 allowed: restrictOpt
93 });
94 }
95
96 checkedDirectives.push(directiveNode);
97 }
98
99 function isFunctionDeclaration(node) {
100 return node.type === 'FunctionDeclaration';
101 }
102
103 function getDirectiveFunctionName(node) {
104 var directiveArg = node.arguments[1];
105
106 // Three ways of creating a directive function: function expression,
107 // variable name that references a function, and an array with a function
108 // as the last item
109 if (utils.isFunctionType(directiveArg)) {
110 return directiveArg.id.name;
111 }
112
113 if (utils.isArrayType(directiveArg)) {
114 directiveArg = directiveArg.elements[directiveArg.elements.length - 1];
115
116 if (utils.isIdentifierType(directiveArg)) {
117 return directiveArg.name;
118 }
119 return directiveArg.id.name;
120 }
121
122 if (utils.isIdentifierType(directiveArg)) {
123 return directiveArg.name;
124 }
125 }
126
127 return {
128 CallExpression: function(node) {
129 if (utils.isAngularDirectiveDeclaration(node)) {
130 foundDirectives.push(node);
131 }
132 },
133 AssignmentExpression: function(node) {
134 // Only check for literal member property assignments.
135 if (node.left.type !== 'MemberExpression') {
136 return;
137 }
138 // Only check setting properties named 'restrict'.
139 if (node.left.property.name !== 'restrict') {
140 return;
141 }
142 checkLiteralNode(node.right);
143 },
144 Property: function(node) {
145 // This only checks for objects which have defined a literal restrict property.
146 if (node.key.name !== 'restrict') {
147 return;
148 }
149 checkLiteralNode(node.value);
150 },
151 'Program:exit': function() {
152 if (explicitRestrict) {
153 foundDirectives.filter(function(directive) {
154 return checkedDirectives.indexOf(directive) < 0;
155 }).forEach(function(directiveNode) {
156 context.report(directiveNode, 'Missing directive restriction');
157 });
158 }
159 }
160 };
161 }
162};