]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | * @param {string} property Name of the property to return. | |
15 | * @param {ASTNode} node The ObjectExpression node. | |
16 | * @returns {ASTNode} The Property node or null if not found. | |
17 | */ | |
18 | function getPropertyFromObject(property, node) { | |
19 | const properties = node.properties; | |
20 | ||
21 | if (!Array.isArray(properties)) { | |
22 | ||
23 | return null; | |
24 | } | |
25 | ||
26 | for (let i = 0; i < properties.length; i++) { | |
27 | if (properties[i].key.name === property) { | |
28 | return properties[i]; | |
29 | } | |
30 | } | |
31 | ||
32 | return null; | |
33 | } | |
34 | ||
35 | /** | |
36 | * Extracts the `meta` property from the ObjectExpression that all rules export. | |
37 | * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. | |
38 | * @returns {ASTNode} The `meta` Property node or null if not found. | |
39 | */ | |
40 | function getMetaPropertyFromExportsNode(exportsNode) { | |
41 | return getPropertyFromObject("meta", exportsNode); | |
42 | } | |
43 | ||
44 | /** | |
45 | * Whether this `meta` ObjectExpression has a `docs` property defined or not. | |
46 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. | |
47 | * @returns {boolean} `true` if a `docs` property exists. | |
48 | */ | |
49 | function hasMetaDocs(metaPropertyNode) { | |
50 | return Boolean(getPropertyFromObject("docs", metaPropertyNode.value)); | |
51 | } | |
52 | ||
53 | /** | |
54 | * Whether this `meta` ObjectExpression has a `docs.description` property defined or not. | |
55 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. | |
56 | * @returns {boolean} `true` if a `docs.description` property exists. | |
57 | */ | |
58 | function hasMetaDocsDescription(metaPropertyNode) { | |
59 | const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value); | |
60 | ||
61 | return metaDocs && getPropertyFromObject("description", metaDocs.value); | |
62 | } | |
63 | ||
64 | /** | |
65 | * Whether this `meta` ObjectExpression has a `docs.category` property defined or not. | |
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 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. | |
78 | * @returns {boolean} `true` if a `docs.recommended` property exists. | |
79 | */ | |
80 | function hasMetaDocsRecommended(metaPropertyNode) { | |
81 | const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value); | |
82 | ||
83 | return metaDocs && getPropertyFromObject("recommended", metaDocs.value); | |
84 | } | |
85 | ||
86 | /** | |
87 | * Whether this `meta` ObjectExpression has a `schema` property defined or not. | |
88 | * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. | |
89 | * @returns {boolean} `true` if a `schema` property exists. | |
90 | */ | |
91 | function hasMetaSchema(metaPropertyNode) { | |
92 | return getPropertyFromObject("schema", metaPropertyNode.value); | |
93 | } | |
94 | ||
95 | /** | |
96 | * Checks the validity of the meta definition of this rule and reports any errors found. | |
97 | * @param {RuleContext} context The ESLint rule context. | |
98 | * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. | |
99 | * @returns {void} | |
100 | */ | |
101 | function checkMetaValidity(context, exportsNode) { | |
102 | const metaProperty = getMetaPropertyFromExportsNode(exportsNode); | |
103 | ||
104 | if (!metaProperty) { | |
105 | context.report({ node: exportsNode, messageId: "missingMeta" }); | |
106 | return; | |
107 | } | |
108 | ||
109 | if (!hasMetaDocs(metaProperty)) { | |
110 | context.report({ node: metaProperty, messageId: "missingMetaDocs" }); | |
111 | return; | |
112 | } | |
113 | ||
114 | if (!hasMetaDocsDescription(metaProperty)) { | |
115 | context.report({ node: metaProperty, messageId: "missingMetaDocsDescription" }); | |
116 | return; | |
117 | } | |
118 | ||
119 | if (!hasMetaDocsCategory(metaProperty)) { | |
120 | context.report({ node: metaProperty, messageId: "missingMetaDocsCategory" }); | |
121 | return; | |
122 | } | |
123 | ||
124 | if (!hasMetaDocsRecommended(metaProperty)) { | |
125 | context.report({ node: metaProperty, messageId: "missingMetaDocsRecommended" }); | |
126 | return; | |
127 | } | |
128 | ||
129 | if (!hasMetaSchema(metaProperty)) { | |
130 | context.report({ node: metaProperty, messageId: "missingMetaSchema" }); | |
131 | } | |
132 | } | |
133 | ||
134 | /** | |
135 | * Whether this node is the correct format for a rule definition or not. | |
136 | * @param {ASTNode} node node that the rule exports. | |
137 | * @returns {boolean} `true` if the exported node is the correct format for a rule definition | |
138 | */ | |
139 | function isCorrectExportsFormat(node) { | |
140 | return node.type === "ObjectExpression"; | |
141 | } | |
142 | ||
143 | //------------------------------------------------------------------------------ | |
144 | // Rule Definition | |
145 | //------------------------------------------------------------------------------ | |
146 | ||
147 | module.exports = { | |
148 | meta: { | |
149 | docs: { | |
150 | description: "enforce correct use of `meta` property in core rules", | |
151 | category: "Internal", | |
152 | recommended: false | |
153 | }, | |
154 | type: "problem", | |
155 | schema: [], | |
156 | messages: { | |
157 | missingMeta: "Rule is missing a meta property.", | |
158 | missingMetaDocs: "Rule is missing a meta.docs property.", | |
159 | missingMetaDocsDescription: "Rule is missing a meta.docs.description property.", | |
160 | missingMetaDocsCategory: "Rule is missing a meta.docs.category property.", | |
161 | missingMetaDocsRecommended: "Rule is missing a meta.docs.recommended property.", | |
162 | missingMetaSchema: "Rule is missing a meta.schema property.", | |
163 | noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format.", | |
164 | incorrectExport: "Rule does not export an Object. Make sure the rule follows the new rule format." | |
165 | } | |
166 | }, | |
167 | ||
168 | create(context) { | |
169 | let exportsNode; | |
170 | ||
171 | return { | |
172 | AssignmentExpression(node) { | |
173 | if (node.left && | |
174 | node.right && | |
175 | node.left.type === "MemberExpression" && | |
176 | node.left.object.name === "module" && | |
177 | node.left.property.name === "exports") { | |
178 | ||
179 | exportsNode = node.right; | |
180 | } | |
181 | }, | |
182 | ||
183 | "Program:exit"(node) { | |
184 | if (!exportsNode) { | |
185 | context.report({ | |
186 | node, | |
187 | messageId: "noExport" | |
188 | }); | |
189 | } else if (!isCorrectExportsFormat(exportsNode)) { | |
190 | context.report({ | |
191 | node: exportsNode, | |
192 | messageId: "incorrectExport" | |
193 | }); | |
194 | } else { | |
195 | checkMetaValidity(context, exportsNode); | |
196 | } | |
197 | } | |
198 | }; | |
199 | } | |
200 | }; |