Skip to content
Permalink
Browse files

Update: Support multiLine and singleLine options (fixes #4697)

Besides default (existing) options, we can specify ‘singleLine’ and
‘multiLine’ options.
  • Loading branch information...
rpatil26 committed Jan 9, 2016
1 parent 535eab1 commit b1360da6aeb50e1d4a24f0a7f2a3744f4df4b2df
Showing with 293 additions and 47 deletions.
  1. +30 −0 docs/rules/key-spacing.md
  2. +121 −47 lib/rules/key-spacing.js
  3. +142 −0 tests/lib/rules/key-spacing.js
@@ -162,6 +162,36 @@ var obj = {
};
```

#### Fine-grained control

You can specify these options separately for single-line and multi-line configurations by organizing the options this way:

```js
"key-spacing": [2, {
"singleLine": {
"beforeColon": false,
"afterColon": true
},
"multiLine": {
"beforeColon": true,
"afterColon": true,
"align": "colon"
}
}]
```

The following patterns are considered valid:

```js
var obj = {one: 1, "two": 2, three: 3}; /* valid due to `singleLine:{ beforeColon: false }`*/
var obj2 = {
"two" : 2,
three : 3
};
```

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

## When Not To Use It

If you have another convention for property spacing that might not be consistent with the available options, or if you want to permit multiple styles concurrently you can safely disable this rule.
@@ -72,6 +72,36 @@ function isSingleLine(node) {
return (node.loc.end.line === node.loc.start.line);
}

/** Sets option values from the configured options with defaults
* @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) {
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;
} else {
toOptions.beforeColon = 0;
}

// Set value of afterColon
if (typeof fromOptions.afterColon !== "undefined") {
toOptions.afterColon = +fromOptions.afterColon;
} else {
toOptions.afterColon = 1;
}

return toOptions;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -93,10 +123,8 @@ module.exports = function(context) {
*/

var options = context.options[0] || {},
align = options.align,
mode = options.mode || "strict",
beforeColon = +!!options.beforeColon, // Defaults to false
afterColon = +!(options.afterColon === false); // Defaults to true
multiLineOptions = initOptions({}, (options.multiLine || options)),
singleLineOptions = initOptions({}, (options.singleLine || options));

/**
* Determines if the given property is key-value property.
@@ -166,9 +194,10 @@ module.exports = function(context) {
* @param {string} side Side being verified - either "key" or "value".
* @param {string} whitespace Actual whitespace string.
* @param {int} expected Expected whitespace length.
* @param {string} mode Value of the mode as "strict" or "minimum"
* @returns {void}
*/
function report(property, side, whitespace, expected) {
function report(property, side, whitespace, expected, mode) {
var diff = whitespace.length - expected,
key = property.key,
firstTokenAfterColon = context.getTokenAfter(getNextColon(key)),
@@ -240,7 +269,9 @@ module.exports = function(context) {
}

return groups;
}, [[]]);
}, [
[]
]);
}

/**
@@ -252,27 +283,28 @@ module.exports = function(context) {
var length = properties.length,
widths = properties.map(getKeyWidth), // Width of keys, including quotes
targetWidth = Math.max.apply(null, widths),
i, property, whitespace, width;
i, property, whitespace, width,
align = multiLineOptions.align,
beforeColon = multiLineOptions.beforeColon,
afterColon = multiLineOptions.afterColon,
mode = multiLineOptions.mode;

// Conditionally include one space before or after colon
targetWidth += (align === "colon" ? beforeColon : afterColon);

for (i = 0; i < length; i++) {
property = properties[i];
whitespace = getPropertyWhitespace(property);

if (!whitespace) {
continue; // Object literal getters/setters lack a colon
}

width = widths[i];

if (align === "value") {
report(property, "key", whitespace.beforeColon, beforeColon);
report(property, "value", whitespace.afterColon, targetWidth - width);
} else { // align = "colon"
report(property, "key", whitespace.beforeColon, targetWidth - width);
report(property, "value", whitespace.afterColon, afterColon);
if (whitespace) { // Object literal getters/setters lack a colon
width = widths[i];

if (align === "value") {
report(property, "key", whitespace.beforeColon, beforeColon, mode);
report(property, "value", whitespace.afterColon, targetWidth - width, mode);
} else { // align = "colon"
report(property, "key", whitespace.beforeColon, targetWidth - width, mode);
report(property, "value", whitespace.afterColon, afterColon, mode);
}
}
}
}
@@ -291,13 +323,14 @@ module.exports = function(context) {
/**
* Verifies spacing of property conforms to specified options.
* @param {ASTNode} node Property node being evaluated.
* @param {Object} lineOptions Configured singleLine or multiLine options
* @returns {void}
*/
function verifySpacing(node) {
var whitespace = getPropertyWhitespace(node);
if (whitespace) { // Object literal getters/setters lack colons
report(node, "key", whitespace.beforeColon, beforeColon);
report(node, "value", whitespace.afterColon, afterColon);
function verifySpacing(node, lineOptions) {
var actual = getPropertyWhitespace(node);
if (actual) { // Object literal getters/setters lack colons
report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
}
}

@@ -310,15 +343,15 @@ module.exports = function(context) {
var length = properties.length;

for (var i = 0; i < length; i++) {
verifySpacing(properties[i]);
verifySpacing(properties[i], singleLineOptions);
}
}

//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------

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

return {
"ObjectExpression": function(node) {
@@ -330,35 +363,76 @@ module.exports = function(context) {
}
};

} else { // Strictly obey beforeColon and afterColon in each property
} else { // Obey beforeColon and afterColon in each property as configured

return {
"Property": function(node) {
verifySpacing(node);
verifySpacing(node, isSingleLine(node) ? singleLineOptions : multiLineOptions);
}
};

}

};

module.exports.schema = [
{
"type": "object",
"properties": {
"align": {
"enum": ["colon", "value"]
},
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
module.exports.schema = [{
"anyOf": [
{
"type": "object",
"properties": {
"align": {
"enum": ["colon", "value"]
},
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
},
"afterColon": {
"type": "boolean"
}
},
"afterColon": {
"type": "boolean"
}
"additionalProperties": false
},
"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": {
"align": {
"enum": ["colon", "value"]
},
"mode": {
"enum": ["strict", "minimum"]
},
"beforeColon": {
"type": "boolean"
},
"afterColon": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}];
Oops, something went wrong.

0 comments on commit b1360da

Please sign in to comment.
You can’t perform that action at this time.