diff --git a/eslint/babel-eslint-plugin/rules/object-curly-spacing.js b/eslint/babel-eslint-plugin/rules/object-curly-spacing.js index 93e3ac17a4..781d41562b 100644 --- a/eslint/babel-eslint-plugin/rules/object-curly-spacing.js +++ b/eslint/babel-eslint-plugin/rules/object-curly-spacing.js @@ -6,6 +6,8 @@ * @copyright 2014 Vignesh Anand. All rights reserved. * @copyright 2015 Jamund Ferguson. All rights reserved. * @copyright 2015 Mathieu M-Gosselin. All rights reserved. + * @copyright 2015 Toru Nagashima. All rights reserved. + * See LICENSE file in root directory for full license. */ "use strict"; @@ -14,7 +16,8 @@ //------------------------------------------------------------------------------ module.exports = function(context) { - var spaced = context.options[0] === "always"; + var spaced = context.options[0] === "always", + sourceCode = context.getSourceCode(); /** * Determines whether an option is set, relative to the spacing option. @@ -44,7 +47,7 @@ module.exports = function(context) { * @returns {boolean} Whether or not there is space between the tokens. */ function isSpaced(left, right) { - return left.range[1] < right.range[0]; + return sourceCode.isSpaceBetweenTokens(left, right); } /** @@ -64,7 +67,7 @@ module.exports = function(context) { * @returns {void} */ function reportNoBeginningSpace(node, token) { - context.report(node, token.loc.start, + context.report(node, token.loc.end, "There should be no space after '" + token.value + "'"); } @@ -86,7 +89,7 @@ module.exports = function(context) { * @returns {void} */ function reportRequiredBeginningSpace(node, token) { - context.report(node, token.loc.start, + context.report(node, token.loc.end, "A space is required after '" + token.value + "'"); } @@ -135,96 +138,115 @@ module.exports = function(context) { } } + /** + * Reports a given object node if spacing in curly braces is invalid. + * @param {ASTNode} node - An ObjectExpression or ObjectPattern node to check. + * @returns {void} + */ + function checkForObject(node) { + if (node.properties.length === 0) { + return; + } + + var firstSpecifier = node.properties[0], + lastSpecifier = node.properties[node.properties.length - 1]; + + var first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier); + + // support trailing commas + if (last.value === ",") { + last = sourceCode.getTokenAfter(last); + } + + var second = sourceCode.getTokenAfter(first), + penultimate = sourceCode.getTokenBefore(last); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + /** + * Reports a given import node if spacing in curly braces is invalid. + * @param {ASTNode} node - An ImportDeclaration node to check. + * @returns {void} + */ + function checkForImport(node) { + if (node.specifiers.length === 0) { + return; + } + + var firstSpecifier = node.specifiers[0], + lastSpecifier = node.specifiers[node.specifiers.length - 1]; + + if (lastSpecifier.type !== "ImportSpecifier") { + return; + } + if (firstSpecifier.type !== "ImportSpecifier") { + firstSpecifier = node.specifiers[1]; + } + + var first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier); + + // to support a trailing comma. + if (last.value === ",") { + last = sourceCode.getTokenAfter(last); + } + + var second = sourceCode.getTokenAfter(first), + penultimate = sourceCode.getTokenBefore(last); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + /** + * Reports a given export node if spacing in curly braces is invalid. + * @param {ASTNode} node - An ExportNamedDeclaration node to check. + * @returns {void} + */ + function checkForExport(node) { + if (node.specifiers.length === 0) { + return; + } + + var firstSpecifier = node.specifiers[0], + lastSpecifier = node.specifiers[node.specifiers.length - 1], + first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter(lastSpecifier); + + // export * as x from '...'; + // export x from '...'; + if (first.value === "export") { + return; + } + + // to support a trailing comma. + if (last.value === ",") { + last = sourceCode.getTokenAfter(last); + } + + var second = sourceCode.getTokenAfter(first), + penultimate = sourceCode.getTokenBefore(last); + + validateBraceSpacing(node, first, second, penultimate, last); + } + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { - // var {x} = y; - ObjectPattern: function(node) { - var firstSpecifier = node.properties[0], - lastSpecifier = node.properties[node.properties.length - 1]; - - var first = context.getTokenBefore(firstSpecifier), - second = context.getFirstToken(firstSpecifier), - penultimate = context.getLastToken(lastSpecifier), - last = context.getTokenAfter(lastSpecifier); - - // support trailing commas - if (last.value === ",") { - penultimate = last; - last = context.getTokenAfter(last); - } - - validateBraceSpacing(node, first, second, penultimate, last); - }, - - // import {y} from 'x'; - ImportDeclaration: function(node) { - - var firstSpecifier = node.specifiers[0], - lastSpecifier = node.specifiers[node.specifiers.length - 1]; - - // don't do anything for namespace or default imports - if (firstSpecifier && lastSpecifier && firstSpecifier.type === "ImportSpecifier" && lastSpecifier.type === "ImportSpecifier") { - var first = context.getTokenBefore(firstSpecifier), - second = context.getFirstToken(firstSpecifier), - penultimate = context.getLastToken(lastSpecifier), - last = context.getTokenAfter(lastSpecifier); - - // support trailing commas - if (last.value === ",") { - penultimate = last; - last = context.getTokenAfter(last); - } - - validateBraceSpacing(node, first, second, penultimate, last); - } - - }, - - // export {name} from 'yo'; - ExportNamedDeclaration: function(node) { - if (!node.specifiers.length) { - return; - } - - var firstSpecifier = node.specifiers[0], - lastSpecifier = node.specifiers[node.specifiers.length - 1], - first = context.getTokenBefore(firstSpecifier), - second = context.getFirstToken(firstSpecifier), - penultimate = context.getLastToken(lastSpecifier), - last = context.getTokenAfter(lastSpecifier); - - if (first.value === "export") { - return; - } - - // support trailing commas - if (last.value === ",") { - penultimate = last; - last = context.getTokenAfter(last); - } - - validateBraceSpacing(node, first, second, penultimate, last); - - }, + ObjectPattern: checkForObject, // var y = {x: 'y'} - ObjectExpression: function(node) { - if (node.properties.length === 0) { - return; - } + ObjectExpression: checkForObject, - var first = context.getFirstToken(node), - second = context.getFirstToken(node, 1), - penultimate = context.getLastToken(node, 1), - last = context.getLastToken(node); - - validateBraceSpacing(node, first, second, penultimate, last); - } + // import {y} from 'x'; + ImportDeclaration: checkForImport, + // export {name} from 'yo'; + ExportNamedDeclaration: checkForExport }; }; diff --git a/eslint/babel-eslint-plugin/tests/object-curly-spacing.js b/eslint/babel-eslint-plugin/tests/object-curly-spacing.js index 83332d4539..a94312a4c3 100644 --- a/eslint/babel-eslint-plugin/tests/object-curly-spacing.js +++ b/eslint/babel-eslint-plugin/tests/object-curly-spacing.js @@ -31,18 +31,28 @@ ruleTester.run('babel/object-curly-spacing', rule, { { code: "var { x: { z }, y } = y", options: ["always"], ecmaFeatures: { destructuring: true } }, { code: "var {\ny,\n} = x", options: ["always"], ecmaFeatures: { destructuring: true } }, { code: "var { y, } = x", options: ["always"], ecmaFeatures: { destructuring: true } }, + { code: "var { y: x } = x", options: ["always"], ecmaFeatures: { destructuring: true } }, // always - import / export + { code: "import door from 'room'", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import * as door from 'room'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "import { door } from 'room'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "import {\ndoor } from 'room'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "export { door } from 'room'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "import { house, mouse } from 'caravan'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "import {\nhouse,\nmouse\n} from 'caravan'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "import {\nhouse,\nmouse,\n} from 'caravan'", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import house, { mouse } from 'caravan'", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import door, { house, mouse } from 'caravan'", options: ["always"], ecmaFeatures: { modules: true } }, { code: "export { door }", options: ["always"], ecmaFeatures: { modules: true } }, { code: "export {\ndoor,\nhouse\n}", options: ["always"], ecmaFeatures: { modules: true } }, { code: "export {\ndoor,\nhouse,\n}", options: ["always"], ecmaFeatures: { modules: true } }, { code: "import 'room'", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import { bar as x } from 'foo';", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import { x, } from 'foo';", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import {\nx,\n} from 'foo';", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "export { x, } from 'foo';", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "export {\nx,\n} from 'foo';", options: ["always"], ecmaFeatures: { modules: true } }, // always - empty object { code: "var foo = {};", options: ["always"] }, @@ -81,8 +91,11 @@ ruleTester.run('babel/object-curly-spacing', rule, { { code: "var {\nx: {z\n}, y} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, { code: "var {\ny,\n} = x", options: ["never"], ecmaFeatures: { destructuring: true } }, { code: "var {y,} = x", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {y:x} = x", options: ["never"], ecmaFeatures: { destructuring: true } }, // never - import / export + { code: "import door from 'room'", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import * as door from 'room'", options: ["never"], ecmaFeatures: { modules: true } }, { code: "import {door} from 'room'", options: ["never"], ecmaFeatures: { modules: true } }, { code: "export {door} from 'room'", options: ["never"], ecmaFeatures: { modules: true } }, { code: "import {\ndoor} from 'room'", options: ["never"], ecmaFeatures: { modules: true } }, @@ -95,6 +108,14 @@ ruleTester.run('babel/object-curly-spacing', rule, { { code: "export {\ndoor,\nmouse\n}", options: ["never"], ecmaFeatures: { modules: true } }, { code: "export {\ndoor,\nmouse,\n}", options: ["never"], ecmaFeatures: { modules: true } }, { code: "import 'room'", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import x, {bar} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import x, {bar, baz} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import {bar as y} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import {x,} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import {\nx,\n} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "export {x,} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "export {\nx,\n} from 'foo';", options: ["never"], ecmaFeatures: { modules: true } }, + // never - empty object { code: "var foo = {};", options: ["never"] }, @@ -102,9 +123,32 @@ ruleTester.run('babel/object-curly-spacing', rule, { // never - objectsInObjects { code: "var obj = {'foo': {'bar': 1, 'baz': 2} };", options: ["never", {"objectsInObjects": true}]}, + // https://github.com/eslint/eslint/issues/3658 + // Empty cases. + { code: "var {} = foo;", ecmaFeatures: { destructuring: true }}, + { code: "var [] = foo;", ecmaFeatures: { destructuring: true }}, + { code: "var {a: {}} = foo;", ecmaFeatures: { destructuring: true }}, + { code: "var {a: []} = foo;", ecmaFeatures: { destructuring: true }}, + { code: "import {} from 'foo';", ecmaFeatures: { modules: true }}, + { code: "export {} from 'foo';", ecmaFeatures: { modules: true }}, + { code: "export {};", ecmaFeatures: { modules: true }}, + { code: "var {} = foo;", options: ["never"], ecmaFeatures: { destructuring: true }}, + { code: "var [] = foo;", options: ["never"], ecmaFeatures: { destructuring: true }}, + { code: "var {a: {}} = foo;", options: ["never"], ecmaFeatures: { destructuring: true }}, + { code: "var {a: []} = foo;", options: ["never"], ecmaFeatures: { destructuring: true }}, + { code: "import {} from 'foo';", options: ["never"], ecmaFeatures: { modules: true }}, + { code: "export {} from 'foo';", options: ["never"], ecmaFeatures: { modules: true }}, + { code: "export {};", options: ["never"], ecmaFeatures: { modules: true }}, + // Babel test cases. { code: "export * as x from \"mod\";", parser: "babel-eslint", ecmaFeatures: { modules: true } }, { code: "export x from \"mod\";", parser: "babel-eslint", ecmaFeatures: { modules: true } }, + + // always - destructuring typed object param + { code: "function fn({ a,b }:Object){}", options: ["always"], parser: "babel-eslint", ecmaFeatures: { destructuring: true } }, + + // never - destructuring typed object param + { code: "function fn({a,b}: Object){}", options: ["never"], parser: "babel-eslint", ecmaFeatures: { destructuring: true } }, ], invalid: [ @@ -119,7 +163,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ImportDeclaration", line: 1, - column: 8 + column: 9 }, { message: "A space is required before '}'", @@ -129,6 +173,175 @@ ruleTester.run('babel/object-curly-spacing', rule, { } ] }, + { + code: "import { bar as y} from 'foo.js';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 18 + } + ] + }, + { + code: "import {bar as y} from 'foo.js';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required after '{'", + type: "ImportDeclaration", + line: 1, + column: 9 + }, + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 17 + } + ] + }, + { + code: "import { bar} from 'foo.js';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 13 + } + ] + }, + { + code: "import x, { bar} from 'foo';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 16 + } + + ] + }, + { + code: "import x, { bar, baz} from 'foo';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 21 + } + + ] + }, + { + code: "import x, {bar} from 'foo';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required after '{'", + type: "ImportDeclaration", + line: 1, + column: 12 + }, + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 15 + } + + ] + }, + { + code: "import x, {bar, baz} from 'foo';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required after '{'", + type: "ImportDeclaration", + line: 1, + column: 12 + }, + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 20 + } + ] + }, + { + code: "import {bar,} from 'foo';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required after '{'", + type: "ImportDeclaration", + line: 1, + column: 9 + }, + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 13 + } + + ] + }, + { + code: "import { bar, } from 'foo';", + options: ["never"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "There should be no space after '{'", + type: "ImportDeclaration", + line: 1, + column: 9 + }, + { + message: "There should be no space before '}'", + type: "ImportDeclaration", + line: 1, + column: 15 + } + ] + }, { code: "export {bar};", options: ["always"], @@ -140,7 +353,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ExportNamedDeclaration", line: 1, - column: 8 + column: 9 }, { message: "A space is required before '}'", @@ -226,6 +439,38 @@ ruleTester.run('babel/object-curly-spacing', rule, { } ] }, + { + code: "var {a:b } = x;", + options: ["never"], + ecmaFeatures: { destructuring: true }, + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectPattern", + line: 1, + column: 10 + } + ] + }, + { + code: "var { a:b } = x;", + options: ["never"], + ecmaFeatures: { destructuring: true }, + errors: [ + { + message: "There should be no space after '{'", + type: "ObjectPattern", + line: 1, + column: 6 + }, + { + message: "There should be no space before '}'", + type: "ObjectPattern", + line: 1, + column: 11 + } + ] + }, // never-objectsInObjects { @@ -262,7 +507,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ObjectExpression", line: 1, - column: 11 + column: 12 }, { message: "A space is required before '}'", @@ -280,7 +525,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ObjectExpression", line: 1, - column: 11 + column: 12 } ] }, @@ -304,7 +549,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "There should be no space after '{'", type: "ObjectExpression", line: 1, - column: 11 + column: 12 }, { message: "There should be no space before '}'", @@ -334,7 +579,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "There should be no space after '{'", type: "ObjectExpression", line: 1, - column: 11 + column: 12 } ] }, @@ -346,13 +591,13 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "There should be no space after '{'", type: "ObjectExpression", line: 1, - column: 11 + column: 12 }, { message: "There should be no space after '{'", type: "ObjectExpression", line: 1, - column: 18 + column: 19 } ] }, @@ -386,7 +631,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ObjectExpression", line: 1, - column: 22 + column: 23 } ] }, @@ -394,6 +639,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { // destructuring { code: "var {x, y} = y", + utput: "var { x, y = y", ecmaFeatures: {destructuring: true}, options: ["always"], errors: [ @@ -401,7 +647,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ObjectPattern", line: 1, - column: 5 + column: 6 }, { message: "A space is required before '}'", @@ -413,6 +659,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { }, { code: "var { x, y} = y", + ouput: "var { x, y } y", ecmaFeatures: {destructuring: true}, options: ["always"], errors: [ @@ -426,6 +673,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { }, { code: "var { x, y } = y", + ouput: "var {x, y} = ", ecmaFeatures: {destructuring: true}, options: ["never"], errors: [ @@ -433,7 +681,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "There should be no space after '{'", type: "ObjectPattern", line: 1, - column: 5 + column: 6 }, { message: "There should be no space before '}'", @@ -478,7 +726,7 @@ ruleTester.run('babel/object-curly-spacing', rule, { message: "A space is required after '{'", type: "ObjectPattern", line: 1, - column: 5 + column: 6 } ] },