Skip to content

Commit

Permalink
Update: Add configurability to generator-star-spacing (#8985)
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanRutherford authored and not-an-aardvark committed Aug 31, 2017
1 parent 8ed779c commit 8fbaf0a
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 19 deletions.
61 changes: 61 additions & 0 deletions docs/rules/generator-star-spacing.md
Expand Up @@ -75,6 +75,27 @@ 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

An example of a configuration with overrides:

```json
"generator-star-spacing": ["error", {
"before": false,
"after": true,
"anonymous": "neither",
"method": {"before": true, "after": true}
}]
```

In the example configuration above, the top level "before" and "after" options define the default behavior of
the rule, while the "anonymous", "method", and "static" options override the default behavior.
Overrides can be either an object with "before" and "after", or a shorthand string as above.

## Examples

### before
Expand Down Expand Up @@ -137,6 +158,46 @@ var anonymous = function*() {};
var shorthand = { *generator() {} };
```

Examples of **incorrect** code for this rule with overrides present:

```js
/*eslint generator-star-spacing: ["error", {
"before": false,
"after": true,
"anonymous": "neither",
"method": {"before": true, "after": true}
}]*/
/*eslint-env es6*/

function * generator() {}

var anonymous = function* () {};

var shorthand = { *generator() {} };

class Class { static* method() {} }
```

Examples of **correct** code for this rule with overrides present:

```js
/*eslint generator-star-spacing: ["error", {
"before": false,
"after": true,
"anonymous": "neither",
"method": {"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.
Expand Down
89 changes: 70 additions & 19 deletions lib/rules/generator-star-spacing.js
Expand Up @@ -9,6 +9,22 @@
// Rule Definition
//------------------------------------------------------------------------------

const OVERRIDE_SCHEMA = {
oneOf: [
{
enum: ["before", "after", "both", "neither"]
},
{
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" }
},
additionalProperties: false
}
]
};

module.exports = {
meta: {
docs: {
Expand All @@ -29,7 +45,10 @@ module.exports = {
type: "object",
properties: {
before: { type: "boolean" },
after: { type: "boolean" }
after: { type: "boolean" },
named: OVERRIDE_SCHEMA,
anonymous: OVERRIDE_SCHEMA,
method: OVERRIDE_SCHEMA
},
additionalProperties: false
}
Expand All @@ -40,16 +59,39 @@ 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]
: 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)
};
}(context.options[0]));

const sourceCode = context.getSourceCode();
Expand Down Expand Up @@ -79,17 +121,19 @@ module.exports = {

/**
* Checks the spacing between two tokens before or after the star token.
*
* @param {string} kind Either "named", "anonymous", or "method"
* @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}} *.";
Expand Down Expand Up @@ -117,6 +161,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}
*/
Expand All @@ -126,17 +171,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.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) {
kind = "method";
} else if (!node.id) {
kind = "anonymous";
}

const nextToken = sourceCode.getTokenAfter(starToken);
// Only check before when preceded by `function`|`static` keyword
if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) {
checkSpacing(kind, "before", prevToken, starToken);
}

checkSpacing("after", starToken, nextToken);
checkSpacing(kind, "after", starToken, nextToken);
}

return {
Expand Down

0 comments on commit 8fbaf0a

Please sign in to comment.