New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update: Add configurability to generator star spacing #8985
Changes from 2 commits
d1eddb2
bcd64ee
fdad986
1c638ed
5071a45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,25 @@ An example of shorthand configuration: | |
"generator-star-spacing": ["error", "after"] | ||
``` | ||
|
||
Additionally, this rule allows further configurability via overrides per function type | ||
|
||
* `named` provides overrides for named functions | ||
* `anonymous` provides overrides for anonymous functions | ||
* `method` provides overrides for class methods or property function shorthand | ||
* `static` provides overrides for static class methods | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed there's an additional option for At the moment I'm thinking the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I included it simply because it is, technically, its own distinct case. I could imagine someone wishing to have methods as
but static methods as
I don't necessarily feel strongly about it, however. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One possible solution would be to support "static" as an override to the "method" override.. an override of an override. Starts to make the schema pretty complex, though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not opposed, though the case I described above would not be possible. I don't expect to see anyone necessarily asking for that specific combination of configs, so we can probably ignore that. |
||
|
||
An example of a configuration with overrides: | ||
|
||
```json | ||
"generator-star-spacing": ["error", { | ||
"before": false, | ||
"after": true, | ||
"anonymous": "neither", | ||
"method": "neither", | ||
"static": {"before": true, "after": true} | ||
}] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you update the documentation to explain what this configuration does? In particular, I'm unsure about what happens when |
||
``` | ||
|
||
## Examples | ||
|
||
### before | ||
|
@@ -137,6 +156,27 @@ var anonymous = function*() {}; | |
var shorthand = { *generator() {} }; | ||
``` | ||
|
||
Examples of **correct** code for this rule with overrides present | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this line end with a colon? |
||
|
||
```js | ||
/*eslint generator-star-spacing: ["error", { | ||
"before": false, | ||
"after": true, | ||
"anonymous": "neither", | ||
"method": "neither", | ||
"static": {"before": true, "after": true} | ||
}]*/ | ||
/*eslint-env es6*/ | ||
|
||
function* generator() {} | ||
|
||
var anonymous = function*() {}; | ||
|
||
var shorthand = { *generator() {} }; | ||
|
||
class Class { static * method() {} } | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If your project will not be using generators or you are not concerned with spacing consistency, you do not need this rule. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,67 @@ module.exports = { | |
type: "object", | ||
properties: { | ||
before: { type: "boolean" }, | ||
after: { type: "boolean" } | ||
after: { type: "boolean" }, | ||
named: { | ||
oneOf: [ | ||
{ | ||
enum: ["before", "after", "both", "neither"] | ||
}, | ||
{ | ||
type: "object", | ||
properties: { | ||
before: { type: "boolean" }, | ||
after: { type: "boolean" } | ||
}, | ||
additionalProperties: false | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: This schema is repeated several times. Can you extract the object into a constant? const SPECIFIC_CONFIG_SCHEMA = {
oneOf: [
// ...
]
}; |
||
] | ||
}, | ||
anonymous: { | ||
oneOf: [ | ||
{ | ||
enum: ["before", "after", "both", "neither"] | ||
}, | ||
{ | ||
type: "object", | ||
properties: { | ||
before: { type: "boolean" }, | ||
after: { type: "boolean" } | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
method: { | ||
oneOf: [ | ||
{ | ||
enum: ["before", "after", "both", "neither"] | ||
}, | ||
{ | ||
type: "object", | ||
properties: { | ||
before: { type: "boolean" }, | ||
after: { type: "boolean" } | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
static: { | ||
oneOf: [ | ||
{ | ||
enum: ["before", "after", "both", "neither"] | ||
}, | ||
{ | ||
type: "object", | ||
properties: { | ||
before: { type: "boolean" }, | ||
after: { type: "boolean" } | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
|
@@ -40,16 +100,41 @@ module.exports = { | |
|
||
create(context) { | ||
|
||
const mode = (function(option) { | ||
if (!option || typeof option === "string") { | ||
return { | ||
before: { before: true, after: false }, | ||
after: { before: false, after: true }, | ||
both: { before: true, after: true }, | ||
neither: { before: false, after: false } | ||
}[option || "before"]; | ||
const optionDefinitions = { | ||
before: { before: true, after: false }, | ||
after: { before: false, after: true }, | ||
both: { before: true, after: true }, | ||
neither: { before: false, after: false } | ||
}; | ||
|
||
/** | ||
* Returns resolved option definitions based on an option and defaults | ||
* | ||
* @param {any} option - The option object or string value | ||
* @param {Object} defaults - The defaults to use if options are not present | ||
* @returns {Object} the resolved object definition | ||
*/ | ||
function optionToDefinition(option, defaults) { | ||
if (!option) { | ||
return defaults; | ||
} | ||
return option; | ||
|
||
return typeof option === "string" ? optionDefinitions[option] : { | ||
before: "before" in option ? option.before : defaults.before, | ||
after: "after" in option ? option.after : defaults.after | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: I think this line would be more readable if it used return typeof option === "string"
? optionDefinitions[options]
: Object.assign({}, defaults, option); |
||
} | ||
|
||
const modes = (function(option) { | ||
option = option || {}; | ||
const defaults = optionToDefinition(option, optionDefinitions.before); | ||
|
||
return { | ||
named: optionToDefinition(option.named, defaults), | ||
anonymous: optionToDefinition(option.anonymous, defaults), | ||
method: optionToDefinition(option.method, defaults), | ||
static: optionToDefinition(option.static, defaults) | ||
}; | ||
}(context.options[0])); | ||
|
||
const sourceCode = context.getSourceCode(); | ||
|
@@ -79,17 +164,19 @@ module.exports = { | |
|
||
/** | ||
* Checks the spacing between two tokens before or after the star token. | ||
* | ||
* @param {string} kind Either "named", "anonymous", "method", or "static" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment should probably be updated to remove "static". |
||
* @param {string} side Either "before" or "after". | ||
* @param {Token} leftToken `function` keyword token if side is "before", or | ||
* star token if side is "after". | ||
* @param {Token} rightToken Star token if side is "before", or identifier | ||
* token if side is "after". | ||
* @returns {void} | ||
*/ | ||
function checkSpacing(side, leftToken, rightToken) { | ||
if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) { | ||
function checkSpacing(kind, side, leftToken, rightToken) { | ||
if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) { | ||
const after = leftToken.value === "*"; | ||
const spaceRequired = mode[side]; | ||
const spaceRequired = modes[kind][side]; | ||
const node = after ? leftToken : rightToken; | ||
const type = spaceRequired ? "Missing" : "Unexpected"; | ||
const message = "{{type}} space {{side}} *."; | ||
|
@@ -117,6 +204,7 @@ module.exports = { | |
|
||
/** | ||
* Enforces the spacing around the star if node is a generator function. | ||
* | ||
* @param {ASTNode} node A function expression or declaration node. | ||
* @returns {void} | ||
*/ | ||
|
@@ -126,17 +214,23 @@ module.exports = { | |
} | ||
|
||
const starToken = getStarToken(node); | ||
|
||
// Only check before when preceded by `function`|`static` keyword | ||
const prevToken = sourceCode.getTokenBefore(starToken); | ||
const nextToken = sourceCode.getTokenAfter(starToken); | ||
|
||
if (prevToken.value === "function" || prevToken.value === "static") { | ||
checkSpacing("before", prevToken, starToken); | ||
let kind = "named"; | ||
|
||
if (node.type === "FunctionExpression" && (node.parent.type === "MethodDefinition" || node.parent.type === "Property")) { | ||
kind = prevToken.value === "static" ? "static" : "method"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this check for methods is correct. It's possible for objects to have function properties that aren't methods: var foo = {
bar: function* () {
}
}; That should fall under Instead, I think it would be better to check the |
||
} else if (nextToken.value === "(") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to check for anonymous functions by verifying that |
||
kind = "anonymous"; | ||
} | ||
|
||
const nextToken = sourceCode.getTokenAfter(starToken); | ||
// Only check before when preceded by `function`|`static` keyword | ||
if (kind !== "method") { | ||
checkSpacing(kind, "before", prevToken, starToken); | ||
} | ||
|
||
checkSpacing("after", starToken, nextToken); | ||
checkSpacing(kind, "after", starToken, nextToken); | ||
} | ||
|
||
return { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -404,6 +404,24 @@ ruleTester.run("generator-star-spacing", rule, { | |
options: [{ before: false, after: false }] | ||
}, | ||
|
||
// full configurability | ||
{ | ||
code: "function * foo(){}", | ||
options: [{ before: false, after: false, named: "both" }] | ||
}, | ||
{ | ||
code: "var foo = function * (){};", | ||
options: [{ before: false, after: false, anonymous: "both" }] | ||
}, | ||
{ | ||
code: "class Foo { * foo(){} }", | ||
options: [{ before: false, after: false, method: "both" }] | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add tests for a method on an object? ({ *foo() {} }) |
||
{ | ||
code: "class Foo { static * foo(){} }", | ||
options: [{ before: false, after: false, static: "both" }] | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add tests where a specific override is used in object form (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add tests where an override is used, but that override doesn't apply to the given code? e.g. {
code: "function* foo() {}",
options: [{ before: false, after: true, method: "before" }]
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add tests where an override is used and top-level {
code: "function *foo() {}",
options: [{ method: "after" }]
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add tests where the object option is specified, but only one of {
code: "function * foo(){}",
options: [{ before: true, after: false, named: { after: true } }]
} |
||
|
||
// https://github.com/eslint/eslint/issues/7101#issuecomment-246080531 | ||
{ | ||
code: "async function foo() { }", | ||
|
@@ -1168,6 +1186,53 @@ ruleTester.run("generator-star-spacing", rule, { | |
message: "Unexpected space after *.", | ||
type: "Punctuator" | ||
}] | ||
}, | ||
|
||
// full configurability | ||
{ | ||
code: "function*foo(){}", | ||
output: "function * foo(){}", | ||
options: [{ before: false, after: false, named: "both" }], | ||
errors: [{ | ||
message: "Missing space before *.", | ||
type: "Punctuator" | ||
}, { | ||
message: "Missing space after *.", | ||
type: "Punctuator" | ||
}] | ||
}, | ||
{ | ||
code: "var foo = function*(){};", | ||
output: "var foo = function * (){};", | ||
options: [{ before: false, after: false, anonymous: "both" }], | ||
errors: [{ | ||
message: "Missing space before *.", | ||
type: "Punctuator" | ||
}, { | ||
message: "Missing space after *.", | ||
type: "Punctuator" | ||
}] | ||
}, | ||
{ | ||
code: "class Foo { *foo(){} }", | ||
output: "class Foo { * foo(){} }", | ||
options: [{ before: false, after: false, method: "both" }], | ||
errors: [{ | ||
message: "Missing space after *.", | ||
type: "Punctuator" | ||
}] | ||
}, | ||
{ | ||
code: "class Foo { static*foo(){} }", | ||
output: "class Foo { static * foo(){} }", | ||
options: [{ before: false, after: false, static: "both" }], | ||
errors: [{ | ||
message: "Missing space before *.", | ||
type: "Punctuator" | ||
}, { | ||
message: "Missing space after *.", | ||
type: "Punctuator" | ||
}] | ||
} | ||
|
||
] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this line end with a punctuation mark (either period/full stop or colon)?