1 | /**
|
2 | * @fileoverview Internal rule to prevent missing or invalid meta property in core rules.
|
3 | * @author Vitor Balocco
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Helpers
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | /**
|
13 | * Gets the property of the Object node passed in that has the name specified.
|
14 | *
|
15 | * @param {string} property Name of the property to return.
|
16 | * @param {ASTNode} node The ObjectExpression node.
|
17 | * @returns {ASTNode} The Property node or null if not found.
|
18 | */
|
19 | function getPropertyFromObject(property, node) {
|
20 | const properties = node.properties;
|
21 |
|
22 | for (let i = 0; i < properties.length; i++) {
|
23 | if (properties[i].key.name === property) {
|
24 | return properties[i];
|
25 | }
|
26 | }
|
27 |
|
28 | return null;
|
29 | }
|
30 |
|
31 | /**
|
32 | * Extracts the `meta` property from the ObjectExpression that all rules export.
|
33 | *
|
34 | * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
|
35 | * @returns {ASTNode} The `meta` Property node or null if not found.
|
36 | */
|
37 | function getMetaPropertyFromExportsNode(exportsNode) {
|
38 | return getPropertyFromObject("meta", exportsNode);
|
39 | }
|
40 |
|
41 | /**
|
42 | * Whether this `meta` ObjectExpression has a `docs` property defined or not.
|
43 | *
|
44 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
45 | * @returns {boolean} `true` if a `docs` property exists.
|
46 | */
|
47 | function hasMetaDocs(metaPropertyNode) {
|
48 | return Boolean(getPropertyFromObject("docs", metaPropertyNode.value));
|
49 | }
|
50 |
|
51 | /**
|
52 | * Whether this `meta` ObjectExpression has a `docs.description` property defined or not.
|
53 | *
|
54 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
55 | * @returns {boolean} `true` if a `docs.description` property exists.
|
56 | */
|
57 | function hasMetaDocsDescription(metaPropertyNode) {
|
58 | const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
|
59 |
|
60 | return metaDocs && getPropertyFromObject("description", metaDocs.value);
|
61 | }
|
62 |
|
63 | /**
|
64 | * Whether this `meta` ObjectExpression has a `docs.category` property defined or not.
|
65 | *
|
66 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
67 | * @returns {boolean} `true` if a `docs.category` property exists.
|
68 | */
|
69 | function hasMetaDocsCategory(metaPropertyNode) {
|
70 | const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
|
71 |
|
72 | return metaDocs && getPropertyFromObject("category", metaDocs.value);
|
73 | }
|
74 |
|
75 | /**
|
76 | * Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not.
|
77 | *
|
78 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
79 | * @returns {boolean} `true` if a `docs.recommended` property exists.
|
80 | */
|
81 | function hasMetaDocsRecommended(metaPropertyNode) {
|
82 | const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
|
83 |
|
84 | return metaDocs && getPropertyFromObject("recommended", metaDocs.value);
|
85 | }
|
86 |
|
87 | /**
|
88 | * Whether this `meta` ObjectExpression has a `schema` property defined or not.
|
89 | *
|
90 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
91 | * @returns {boolean} `true` if a `schema` property exists.
|
92 | */
|
93 | function hasMetaSchema(metaPropertyNode) {
|
94 | return getPropertyFromObject("schema", metaPropertyNode.value);
|
95 | }
|
96 |
|
97 | /**
|
98 | * Whether this `meta` ObjectExpression has a `fixable` property defined or not.
|
99 | *
|
100 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
|
101 | * @returns {boolean} `true` if a `fixable` property exists.
|
102 | */
|
103 | function hasMetaFixable(metaPropertyNode) {
|
104 | return getPropertyFromObject("fixable", metaPropertyNode.value);
|
105 | }
|
106 |
|
107 | /**
|
108 | * Checks the validity of the meta definition of this rule and reports any errors found.
|
109 | *
|
110 | * @param {RuleContext} context The ESLint rule context.
|
111 | * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
|
112 | * @param {boolean} ruleIsFixable whether the rule is fixable or not.
|
113 | * @returns {void}
|
114 | */
|
115 | function checkMetaValidity(context, exportsNode, ruleIsFixable) {
|
116 | const metaProperty = getMetaPropertyFromExportsNode(exportsNode);
|
117 |
|
118 | if (!metaProperty) {
|
119 | context.report(exportsNode, "Rule is missing a meta property.");
|
120 | return;
|
121 | }
|
122 |
|
123 | if (!hasMetaDocs(metaProperty)) {
|
124 | context.report(metaProperty, "Rule is missing a meta.docs property.");
|
125 | return;
|
126 | }
|
127 |
|
128 | if (!hasMetaDocsDescription(metaProperty)) {
|
129 | context.report(metaProperty, "Rule is missing a meta.docs.description property.");
|
130 | return;
|
131 | }
|
132 |
|
133 | if (!hasMetaDocsCategory(metaProperty)) {
|
134 | context.report(metaProperty, "Rule is missing a meta.docs.category property.");
|
135 | return;
|
136 | }
|
137 |
|
138 | if (!hasMetaDocsRecommended(metaProperty)) {
|
139 | context.report(metaProperty, "Rule is missing a meta.docs.recommended property.");
|
140 | return;
|
141 | }
|
142 |
|
143 | if (!hasMetaSchema(metaProperty)) {
|
144 | context.report(metaProperty, "Rule is missing a meta.schema property.");
|
145 | return;
|
146 | }
|
147 |
|
148 | if (ruleIsFixable && !hasMetaFixable(metaProperty)) {
|
149 | context.report(metaProperty, "Rule is fixable, but is missing a meta.fixable property.");
|
150 | }
|
151 | }
|
152 |
|
153 | /**
|
154 | * Whether this node is the correct format for a rule definition or not.
|
155 | *
|
156 | * @param {ASTNode} node node that the rule exports.
|
157 | * @returns {boolean} `true` if the exported node is the correct format for a rule definition
|
158 | */
|
159 | function isCorrectExportsFormat(node) {
|
160 | return node.type === "ObjectExpression";
|
161 | }
|
162 |
|
163 | //------------------------------------------------------------------------------
|
164 | // Rule Definition
|
165 | //------------------------------------------------------------------------------
|
166 |
|
167 | module.exports = {
|
168 | meta: {
|
169 | docs: {
|
170 | description: "enforce correct use of `meta` property in core rules",
|
171 | category: "Internal",
|
172 | recommended: false
|
173 | },
|
174 |
|
175 | schema: []
|
176 | },
|
177 |
|
178 | create(context) {
|
179 | let exportsNode;
|
180 | let ruleIsFixable = false;
|
181 |
|
182 | return {
|
183 | AssignmentExpression(node) {
|
184 | if (node.left &&
|
185 | node.right &&
|
186 | node.left.type === "MemberExpression" &&
|
187 | node.left.object.name === "module" &&
|
188 | node.left.property.name === "exports") {
|
189 |
|
190 | exportsNode = node.right;
|
191 | }
|
192 | },
|
193 |
|
194 | CallExpression(node) {
|
195 |
|
196 | // If the rule has a call for `context.report` and a property `fix`
|
197 | // is being passed in, then we consider that the rule is fixable.
|
198 | //
|
199 | // Note that we only look for context.report() calls in the new
|
200 | // style (with single MessageDescriptor argument), because only
|
201 | // calls in the new style can specify a fix.
|
202 | if (node.callee.type === "MemberExpression" &&
|
203 | node.callee.object.type === "Identifier" &&
|
204 | node.callee.object.name === "context" &&
|
205 | node.callee.property.type === "Identifier" &&
|
206 | node.callee.property.name === "report" &&
|
207 | node.arguments.length === 1 &&
|
208 | node.arguments[0].type === "ObjectExpression") {
|
209 |
|
210 | if (getPropertyFromObject("fix", node.arguments[0])) {
|
211 | ruleIsFixable = true;
|
212 | }
|
213 | }
|
214 | },
|
215 |
|
216 | "Program:exit"() {
|
217 | if (!isCorrectExportsFormat(exportsNode)) {
|
218 | context.report(exportsNode, "Rule does not export an Object. Make sure the rule follows the new rule format.");
|
219 | return;
|
220 | }
|
221 |
|
222 | checkMetaValidity(context, exportsNode, ruleIsFixable);
|
223 | }
|
224 | };
|
225 | }
|
226 | };
|