]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to check the spacing around the * in generator functions. | |
3 | * @author Jamund Ferguson | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Rule Definition | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const OVERRIDE_SCHEMA = { | |
13 | oneOf: [ | |
14 | { | |
15 | enum: ["before", "after", "both", "neither"] | |
16 | }, | |
17 | { | |
18 | type: "object", | |
19 | properties: { | |
20 | before: { type: "boolean" }, | |
21 | after: { type: "boolean" } | |
22 | }, | |
23 | additionalProperties: false | |
24 | } | |
25 | ] | |
26 | }; | |
27 | ||
28 | module.exports = { | |
29 | meta: { | |
30 | type: "layout", | |
31 | ||
32 | docs: { | |
33 | description: "enforce consistent spacing around `*` operators in generator functions", | |
eb39fafa DC |
34 | recommended: false, |
35 | url: "https://eslint.org/docs/rules/generator-star-spacing" | |
36 | }, | |
37 | ||
38 | fixable: "whitespace", | |
39 | ||
40 | schema: [ | |
41 | { | |
42 | oneOf: [ | |
43 | { | |
44 | enum: ["before", "after", "both", "neither"] | |
45 | }, | |
46 | { | |
47 | type: "object", | |
48 | properties: { | |
49 | before: { type: "boolean" }, | |
50 | after: { type: "boolean" }, | |
51 | named: OVERRIDE_SCHEMA, | |
52 | anonymous: OVERRIDE_SCHEMA, | |
53 | method: OVERRIDE_SCHEMA | |
54 | }, | |
55 | additionalProperties: false | |
56 | } | |
57 | ] | |
58 | } | |
59 | ], | |
60 | ||
61 | messages: { | |
62 | missingBefore: "Missing space before *.", | |
63 | missingAfter: "Missing space after *.", | |
64 | unexpectedBefore: "Unexpected space before *.", | |
65 | unexpectedAfter: "Unexpected space after *." | |
66 | } | |
67 | }, | |
68 | ||
69 | create(context) { | |
70 | ||
71 | const optionDefinitions = { | |
72 | before: { before: true, after: false }, | |
73 | after: { before: false, after: true }, | |
74 | both: { before: true, after: true }, | |
75 | neither: { before: false, after: false } | |
76 | }; | |
77 | ||
78 | /** | |
79 | * Returns resolved option definitions based on an option and defaults | |
80 | * @param {any} option The option object or string value | |
81 | * @param {Object} defaults The defaults to use if options are not present | |
82 | * @returns {Object} the resolved object definition | |
83 | */ | |
84 | function optionToDefinition(option, defaults) { | |
85 | if (!option) { | |
86 | return defaults; | |
87 | } | |
88 | ||
89 | return typeof option === "string" | |
90 | ? optionDefinitions[option] | |
91 | : Object.assign({}, defaults, option); | |
92 | } | |
93 | ||
94 | const modes = (function(option) { | |
95 | const defaults = optionToDefinition(option, optionDefinitions.before); | |
96 | ||
97 | return { | |
98 | named: optionToDefinition(option.named, defaults), | |
99 | anonymous: optionToDefinition(option.anonymous, defaults), | |
100 | method: optionToDefinition(option.method, defaults) | |
101 | }; | |
102 | }(context.options[0] || {})); | |
103 | ||
104 | const sourceCode = context.getSourceCode(); | |
105 | ||
106 | /** | |
107 | * Checks if the given token is a star token or not. | |
108 | * @param {Token} token The token to check. | |
109 | * @returns {boolean} `true` if the token is a star token. | |
110 | */ | |
111 | function isStarToken(token) { | |
112 | return token.value === "*" && token.type === "Punctuator"; | |
113 | } | |
114 | ||
115 | /** | |
116 | * Gets the generator star token of the given function node. | |
117 | * @param {ASTNode} node The function node to get. | |
118 | * @returns {Token} Found star token. | |
119 | */ | |
120 | function getStarToken(node) { | |
121 | return sourceCode.getFirstToken( | |
122 | (node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node, | |
123 | isStarToken | |
124 | ); | |
125 | } | |
126 | ||
127 | /** | |
128 | * capitalize a given string. | |
129 | * @param {string} str the given string. | |
130 | * @returns {string} the capitalized string. | |
131 | */ | |
132 | function capitalize(str) { | |
133 | return str[0].toUpperCase() + str.slice(1); | |
134 | } | |
135 | ||
136 | /** | |
137 | * Checks the spacing between two tokens before or after the star token. | |
138 | * @param {string} kind Either "named", "anonymous", or "method" | |
139 | * @param {string} side Either "before" or "after". | |
140 | * @param {Token} leftToken `function` keyword token if side is "before", or | |
141 | * star token if side is "after". | |
142 | * @param {Token} rightToken Star token if side is "before", or identifier | |
143 | * token if side is "after". | |
144 | * @returns {void} | |
145 | */ | |
146 | function checkSpacing(kind, side, leftToken, rightToken) { | |
147 | if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) { | |
148 | const after = leftToken.value === "*"; | |
149 | const spaceRequired = modes[kind][side]; | |
150 | const node = after ? leftToken : rightToken; | |
151 | const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`; | |
152 | ||
153 | context.report({ | |
154 | node, | |
155 | messageId, | |
156 | fix(fixer) { | |
157 | if (spaceRequired) { | |
158 | if (after) { | |
159 | return fixer.insertTextAfter(node, " "); | |
160 | } | |
161 | return fixer.insertTextBefore(node, " "); | |
162 | } | |
163 | return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); | |
164 | } | |
165 | }); | |
166 | } | |
167 | } | |
168 | ||
169 | /** | |
170 | * Enforces the spacing around the star if node is a generator function. | |
171 | * @param {ASTNode} node A function expression or declaration node. | |
172 | * @returns {void} | |
173 | */ | |
174 | function checkFunction(node) { | |
175 | if (!node.generator) { | |
176 | return; | |
177 | } | |
178 | ||
179 | const starToken = getStarToken(node); | |
180 | const prevToken = sourceCode.getTokenBefore(starToken); | |
181 | const nextToken = sourceCode.getTokenAfter(starToken); | |
182 | ||
183 | let kind = "named"; | |
184 | ||
185 | if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) { | |
186 | kind = "method"; | |
187 | } else if (!node.id) { | |
188 | kind = "anonymous"; | |
189 | } | |
190 | ||
191 | // Only check before when preceded by `function`|`static` keyword | |
192 | if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) { | |
193 | checkSpacing(kind, "before", prevToken, starToken); | |
194 | } | |
195 | ||
196 | checkSpacing(kind, "after", starToken, nextToken); | |
197 | } | |
198 | ||
199 | return { | |
200 | FunctionDeclaration: checkFunction, | |
201 | FunctionExpression: checkFunction | |
202 | }; | |
203 | ||
204 | } | |
205 | }; |