UNPKG

7.75 kBJavaScriptView Raw
1/**
2 * @fileoverview Internal rule to prevent missing or invalid meta property in core rules.
3 * @author Vitor Balocco
4 */
5
6"use strict";
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 */
19function 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 */
37function 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 */
47function 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 */
57function 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 */
69function 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 */
81function 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 */
93function 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 */
103function 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 */
115function 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 */
159function isCorrectExportsFormat(node) {
160 return node.type === "ObjectExpression";
161}
162
163//------------------------------------------------------------------------------
164// Rule Definition
165//------------------------------------------------------------------------------
166
167module.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};