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