Skip to content

Commit

Permalink
Update: Adding new key-spacing option (fixes #5613) (#5907)
Browse files Browse the repository at this point in the history
`align` is now available as a stand-alone configurations.
This allows specifying how spacing should work when aligning
values in a group.
  • Loading branch information
Kyle-Mendes authored and nzakas committed Jul 7, 2016
1 parent 10c3e91 commit e951303
Show file tree
Hide file tree
Showing 3 changed files with 628 additions and 26 deletions.
135 changes: 126 additions & 9 deletions docs/rules/key-spacing.md
Expand Up @@ -12,16 +12,17 @@ This rule enforces consistent spacing between keys and values in object literal

This rule has an object option:

* `"beforeColon": false` (default) disallows spaces between the key and the colon in object literals
* `"beforeColon": true` requires at least one space between the key and the colon in object literals
* `"afterColon": true` (default) requires at least one space between the colon and the value in object literals
* `"afterColon": false` disallows spaces between the colon and the value in object literals
* `"mode": strict` (default) enforces exactly one space before or after colons in object literals
* `"mode": minimum` enforces one or more spaces before or after colons in object literals
* `"align": "value"` enforces horizontal alignment of values in object literals
* `"beforeColon": false` (default) disallows spaces between the key and the colon in object literals.
* `"beforeColon": true` requires at least one space between the key and the colon in object literals.
* `"afterColon": true` (default) requires at least one space between the colon and the value in object literals.
* `"afterColon": false` disallows spaces between the colon and the value in object literals.
* `"mode": strict` (default) enforces exactly one space before or after colons in object literals.
* `"mode": minimum` enforces one or more spaces before or after colons in object literals.
* `"align": "value"` enforces horizontal alignment of values in object literals.
* `"align": "colon"` enforces horizontal alignment of both colons and values in object literals.
* `"singleLine"` specifies a spacing style for single-line object literals
* `"multiLine"` specifies a spacing style for multi-line object literals
* `"align"` with an object value allows for fine-grained spacing when values are being aligned in object literals.
* `"singleLine"` specifies a spacing style for single-line object literals.
* `"multiLine"` specifies a spacing style for multi-line object literals.

Please note that you can either use the top-level options or the grouped options (`singleLine` and `multiLine`) but not both.

Expand Down Expand Up @@ -183,6 +184,122 @@ call({
});
```

### align

The `align` option can take additional configuration through the `beforeColon`, `afterColon`, `mode`, and `on` options.

If `align` is defined as an object, but not all of the parameters are provided, undefined parameters will default to the following:

```js
// Defaults
align: {
"beforeColon": false,
"afterColon": true,
"on": "colon",
"mode": "strict"
}
```

Examples of **correct** code for this rule with sample `{ "align": { } }` options:

```js
/*eslint key-spacing: ["error", {
"align": {
"beforeColon": true,
"afterColon": true,
"on": "colon"
}
}]*/

var obj = {
"one" : 1,
"seven" : 7
}
```

```js
/*eslint key-spacing: ["error", {
"align": {
"beforeColon": false,
"afterColon": false,
"on": "value"
}
}]*/

var obj = {
"one": 1,
"seven":7
}
```

### align and multiLine

The `multiLine` and `align` options can differ, which allows for fine-tuned control over the `key-spacing` of your files. `align` will **not** inherit from `multiLine` if `align` is configured as an object.

`multiLine` is used any time an object literal spans multiple lines. The `align` configuration is used when there is a group of properties in the the same object. For example:

```javascript
var myObj = {
key1: 1, // uses multiLine

key2: 2, // uses align (when defined)
key3: 3, // uses align (when defined)

key4: 4 // uses multiLine
}

```

Examples of **incorrect** code for this rule with sample `{ "align": { }, "multiLine": { } }` options:

```js
/*eslint key-spacing: ["error", {
"multiLine": {
"beforeColon": false,
"afterColon":true
},
"align": {
"beforeColon": true,
"afterColon": true,
"on": "colon"
}
}]*/

var obj = {
"myObjectFunction": function() {
// Do something
},
"one" : 1,
"seven" : 7
}
```

Examples of **correct** code for this rule with sample `{ "align": { }, "multiLine": { } }` options:

```js
/*eslint key-spacing: ["error", {
"multiLine": {
"beforeColon": false,
"afterColon": true
},
"align": {
"beforeColon": true,
"afterColon": true,
"on": "colon"
}
}]*/

var obj = {
"myObjectFunction": function() {
// Do something
//
}, // These are two separate groups, so no alignment between `myObjectFuction` and `one`
"one" : 1,
"seven" : 7 // `one` and `seven` are in their own group, and therefore aligned
}
```

### singleLine and multiLine

Examples of **correct** code for this rule with sample `{ "singleLine": { }, "multiLine": { } }` options:
Expand Down
183 changes: 166 additions & 17 deletions lib/rules/key-spacing.js
Expand Up @@ -71,19 +71,15 @@ function isSingleLine(node) {
return (node.loc.end.line === node.loc.start.line);
}

/** Sets option values from the configured options with defaults
/**
* Initializes a single option property from the configuration with defaults for undefined values
* @param {Object} toOptions Object to be initialized
* @param {Object} fromOptions Object to be initialized from
* @returns {Object} The object with correctly initialized options and values
*/
function initOptions(toOptions, fromOptions) {
function initOptionProperty(toOptions, fromOptions) {
toOptions.mode = fromOptions.mode || "strict";

// Set align if exists - multiLine case
if (typeof fromOptions.align !== "undefined") {
toOptions.align = fromOptions.align;
}

// Set value of beforeColon
if (typeof fromOptions.beforeColon !== "undefined") {
toOptions.beforeColon = +fromOptions.beforeColon;
Expand All @@ -98,6 +94,55 @@ function initOptions(toOptions, fromOptions) {
toOptions.afterColon = 1;
}

// Set align if exists
if (typeof fromOptions.align !== "undefined") {
if (typeof fromOptions.align === "object") {
toOptions.align = fromOptions.align;
} else { // "string"
toOptions.align = {
on: fromOptions.align,
mode: toOptions.mode,
beforeColon: toOptions.beforeColon,
afterColon: toOptions.afterColon
};
}
}

return toOptions;
}

/**
* Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values
* @param {Object} toOptions Object to be initialized
* @param {Object} fromOptions Object to be initialized from
* @returns {Object} The object with correctly initialized options and values
*/
function initOptions(toOptions, fromOptions) {
if (typeof fromOptions.align === "object") {

// Initialize the alignment configuration
toOptions.align = initOptionProperty({}, fromOptions.align);
toOptions.align.on = fromOptions.align.on || "colon";
toOptions.align.mode = fromOptions.align.mode || "strict";

toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));
toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));

} else { // string or undefined
toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));
toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));

// If alignment options are defined in multiLine, pull them out into the general align configuration
if (toOptions.multiLine.align) {
toOptions.align = {
on: toOptions.multiLine.align.on,
mode: toOptions.multiLine.mode,
beforeColon: toOptions.multiLine.align.beforeColon,
afterColon: toOptions.multiLine.align.afterColon
};
}
}

return toOptions;
}

Expand Down Expand Up @@ -126,7 +171,29 @@ module.exports = {
type: "object",
properties: {
align: {
enum: ["colon", "value"]
anyOf: [
{
enum: ["colon", "value"]
},
{
type: "object",
properties: {
mode: {
enum: ["strict", "minimum"]
},
on: {
enum: ["colon", "value"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
mode: {
enum: ["strict", "minimum"]
Expand Down Expand Up @@ -162,7 +229,29 @@ module.exports = {
type: "object",
properties: {
align: {
enum: ["colon", "value"]
anyOf: [
{
enum: ["colon", "value"]
},
{
type: "object",
properties: {
mode: {
enum: ["strict", "minimum"]
},
on: {
enum: ["colon", "value"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
}
]
},
mode: {
enum: ["strict", "minimum"]
Expand All @@ -178,6 +267,57 @@ module.exports = {
}
},
additionalProperties: false
},
{
type: "object",
properties: {
singleLine: {
type: "object",
properties: {
mode: {
enum: ["strict", "minimum"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
},
multiLine: {
type: "object",
properties: {
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
},
align: {
type: "object",
properties: {
mode: {
enum: ["strict", "minimum"]
},
on: {
enum: ["colon", "value"]
},
beforeColon: {
type: "boolean"
},
afterColon: {
type: "boolean"
}
},
additionalProperties: false
}
},
additionalProperties: false
}
]
}]
Expand All @@ -193,10 +333,11 @@ module.exports = {
* align: "colon" // Optional, or "value"
* }
*/

var options = context.options[0] || {},
multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options));
ruleOptions = initOptions({}, options),
multiLineOptions = ruleOptions.multiLine,
singleLineOptions = ruleOptions.singleLine,
alignmentOptions = ruleOptions.align || null;

var sourceCode = context.getSourceCode();

Expand Down Expand Up @@ -397,11 +538,19 @@ module.exports = {
var length = properties.length,
widths = properties.map(getKeyWidth), // Width of keys, including quotes
targetWidth = Math.max.apply(null, widths),
align = alignmentOptions.on, // "value" or "colon"
i, property, whitespace, width,
align = multiLineOptions.align,
beforeColon = multiLineOptions.beforeColon,
afterColon = multiLineOptions.afterColon,
mode = multiLineOptions.mode;
beforeColon, afterColon, mode;

if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration.
beforeColon = alignmentOptions.beforeColon;
afterColon = alignmentOptions.afterColon;
mode = alignmentOptions.mode;
} else {
beforeColon = multiLineOptions.beforeColon;
afterColon = multiLineOptions.afterColon;
mode = alignmentOptions.mode;
}

// Conditionally include one space before or after colon
targetWidth += (align === "colon" ? beforeColon : afterColon);
Expand Down Expand Up @@ -466,7 +615,7 @@ module.exports = {
// Public API
//--------------------------------------------------------------------------

if (multiLineOptions.align) { // Verify vertical alignment
if (alignmentOptions) { // Verify vertical alignment

return {
ObjectExpression: function(node) {
Expand Down

0 comments on commit e951303

Please sign in to comment.