UNPKG

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