UNPKG

7.15 kBJavaScriptView Raw
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
12const 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
28module.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 *
82 * @param {any} option - The option object or string value
83 * @param {Object} defaults - The defaults to use if options are not present
84 * @returns {Object} the resolved object definition
85 */
86 function optionToDefinition(option, defaults) {
87 if (!option) {
88 return defaults;
89 }
90
91 return typeof option === "string"
92 ? optionDefinitions[option]
93 : Object.assign({}, defaults, option);
94 }
95
96 const modes = (function(option) {
97 const defaults = optionToDefinition(option, optionDefinitions.before);
98
99 return {
100 named: optionToDefinition(option.named, defaults),
101 anonymous: optionToDefinition(option.anonymous, defaults),
102 method: optionToDefinition(option.method, defaults)
103 };
104 }(context.options[0] || {}));
105
106 const sourceCode = context.getSourceCode();
107
108 /**
109 * Checks if the given token is a star token or not.
110 *
111 * @param {Token} token - The token to check.
112 * @returns {boolean} `true` if the token is a star token.
113 */
114 function isStarToken(token) {
115 return token.value === "*" && token.type === "Punctuator";
116 }
117
118 /**
119 * Gets the generator star token of the given function node.
120 *
121 * @param {ASTNode} node - The function node to get.
122 * @returns {Token} Found star token.
123 */
124 function getStarToken(node) {
125 return sourceCode.getFirstToken(
126 (node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node,
127 isStarToken
128 );
129 }
130
131 /**
132 * capitalize a given string.
133 * @param {string} str the given string.
134 * @returns {string} the capitalized string.
135 */
136 function capitalize(str) {
137 return str[0].toUpperCase() + str.slice(1);
138 }
139
140 /**
141 * Checks the spacing between two tokens before or after the star token.
142 *
143 * @param {string} kind Either "named", "anonymous", or "method"
144 * @param {string} side Either "before" or "after".
145 * @param {Token} leftToken `function` keyword token if side is "before", or
146 * star token if side is "after".
147 * @param {Token} rightToken Star token if side is "before", or identifier
148 * token if side is "after".
149 * @returns {void}
150 */
151 function checkSpacing(kind, side, leftToken, rightToken) {
152 if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) {
153 const after = leftToken.value === "*";
154 const spaceRequired = modes[kind][side];
155 const node = after ? leftToken : rightToken;
156 const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`;
157
158 context.report({
159 node,
160 messageId,
161 fix(fixer) {
162 if (spaceRequired) {
163 if (after) {
164 return fixer.insertTextAfter(node, " ");
165 }
166 return fixer.insertTextBefore(node, " ");
167 }
168 return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
169 }
170 });
171 }
172 }
173
174 /**
175 * Enforces the spacing around the star if node is a generator function.
176 *
177 * @param {ASTNode} node A function expression or declaration node.
178 * @returns {void}
179 */
180 function checkFunction(node) {
181 if (!node.generator) {
182 return;
183 }
184
185 const starToken = getStarToken(node);
186 const prevToken = sourceCode.getTokenBefore(starToken);
187 const nextToken = sourceCode.getTokenAfter(starToken);
188
189 let kind = "named";
190
191 if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) {
192 kind = "method";
193 } else if (!node.id) {
194 kind = "anonymous";
195 }
196
197 // Only check before when preceded by `function`|`static` keyword
198 if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) {
199 checkSpacing(kind, "before", prevToken, starToken);
200 }
201
202 checkSpacing(kind, "after", starToken, nextToken);
203 }
204
205 return {
206 FunctionDeclaration: checkFunction,
207 FunctionExpression: checkFunction
208 };
209
210 }
211};