UNPKG

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