From 51d6ba733f5169ad1aaf72987201972461e21b66 Mon Sep 17 00:00:00 2001 From: Jason Quense Date: Thu, 16 Jul 2015 09:17:20 -0400 Subject: [PATCH] Merge pull request babel/eslint-plugin-babel#4 from mathieumg/objectcurlyspacing_export Added support for experimental exports in the `object-curly-spacing` rule --- eslint/babel-eslint-plugin/README.md | 2 + eslint/babel-eslint-plugin/index.js | 1 + .../rules/object-curly-spacing.js | 236 +++++++++ .../tests/object-curly-spacing.js | 500 ++++++++++++++++++ 4 files changed, 739 insertions(+) create mode 100644 eslint/babel-eslint-plugin/rules/object-curly-spacing.js create mode 100644 eslint/babel-eslint-plugin/tests/object-curly-spacing.js diff --git a/eslint/babel-eslint-plugin/README.md b/eslint/babel-eslint-plugin/README.md index 997a6148aa..d6507bf782 100644 --- a/eslint/babel-eslint-plugin/README.md +++ b/eslint/babel-eslint-plugin/README.md @@ -28,6 +28,7 @@ Finally enable all the rules you like to use (remember to disable the originals "babel/generator-star": 1, "babel/generator-star-spacing": 1, "babel/new-cap": 1, + "babel/object-curly-spacing": 1, "babel/space-in-brackets": 1, } } @@ -41,4 +42,5 @@ Each rule cooresponds to a core eslint rule, and has the same options. - `babel/generator-star`: Handles async/await functions correctly - `babel/generator-star-spacing`: Handles async/await functions correctly - `babel/new-cap`: Ignores capitalized decorators (`@Decorator`) +- `babel/object-curly-spacing`: doesn't complain about `export x from "mod";` or `export * as x from "mod";` - `babel/space-in-brackets`: doesn't complain about `export x from "mod";` or `export * as x from "mod";` diff --git a/eslint/babel-eslint-plugin/index.js b/eslint/babel-eslint-plugin/index.js index a486a4c120..8ff696b7a7 100644 --- a/eslint/babel-eslint-plugin/index.js +++ b/eslint/babel-eslint-plugin/index.js @@ -7,6 +7,7 @@ module.exports = { 'generator-star-spacing': require('./rules/generator-star-spacing'), 'generator-star': require('./rules/generator-star'), 'new-cap': require('./rules/new-cap'), + 'object-curly-spacing': require('./rules/object-curly-spacing'), 'space-in-brackets': require('./rules/space-in-brackets'), }, rulesConfig: { diff --git a/eslint/babel-eslint-plugin/rules/object-curly-spacing.js b/eslint/babel-eslint-plugin/rules/object-curly-spacing.js new file mode 100644 index 0000000000..d498211a99 --- /dev/null +++ b/eslint/babel-eslint-plugin/rules/object-curly-spacing.js @@ -0,0 +1,236 @@ +/** + * @fileoverview Disallows or enforces spaces inside of object literals. + * @author Jamund Ferguson + * @copyright 2014 Brandyn Bennett. All rights reserved. + * @copyright 2014 Michael Ficarra. No rights reserved. + * @copyright 2014 Vignesh Anand. All rights reserved. + * @copyright 2015 Jamund Ferguson. All rights reserved. + * @copyright 2015 Mathieu M-Gosselin. All rights reserved. + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + var spaced = context.options[0] === "always"; + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param {Object} option - The option to exclude. + * @returns {boolean} Whether or not the property is excluded. + */ + function isOptionSet(option) { + return context.options[1] != null ? context.options[1][option] === !spaced : false; + } + + var options = { + spaced: spaced, + arraysInObjectsException: isOptionSet("arraysInObjects"), + objectsInObjectsException: isOptionSet("objectsInObjects") + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines whether two adjacent tokens are have whitespace between them. + * @param {Object} left - The left token object. + * @param {Object} right - The right token object. + * @returns {boolean} Whether or not there is space between the tokens. + */ + function isSpaced(left, right) { + return left.range[1] < right.range[0]; + } + + /** + * Determines whether two adjacent tokens are on the same line. + * @param {Object} left - The left token object. + * @param {Object} right - The right token object. + * @returns {boolean} Whether or not the tokens are on the same line. + */ + function isSameLine(left, right) { + return left.loc.start.line === right.loc.start.line; + } + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + context.report(node, token.loc.start, + "There should be no space after '" + token.value + "'"); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + context.report(node, token.loc.start, + "There should be no space before '" + token.value + "'"); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report(node, token.loc.start, + "A space is required after '" + token.value + "'"); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node - The node to report in the event of an error. + * @param {Token} token - The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report(node, token.loc.start, + "A space is required before '" + token.value + "'"); + } + + /** + * Determines if spacing in curly braces is valid. + * @param {ASTNode} node The AST node to check. + * @param {Token} first The first token to check (should be the opening brace) + * @param {Token} second The second token to check (should be first after the opening brace) + * @param {Token} penultimate The penultimate token to check (should be last before closing brace) + * @param {Token} last The last token to check (should be closing brace) + * @returns {void} + */ + function validateBraceSpacing(node, first, second, penultimate, last) { + var closingCurlyBraceMustBeSpaced = + options.arraysInObjectsException && penultimate.value === "]" || + options.objectsInObjectsException && penultimate.value === "}" + ? !options.spaced : options.spaced; + + if (isSameLine(first, second)) { + if (options.spaced && !isSpaced(first, second)) { + reportRequiredBeginningSpace(node, first); + } + if (!options.spaced && isSpaced(first, second)) { + reportNoBeginningSpace(node, first); + } + } + + if (isSameLine(penultimate, last)) { + if (closingCurlyBraceMustBeSpaced && !isSpaced(penultimate, last)) { + reportRequiredEndingSpace(node, last); + } + if (!closingCurlyBraceMustBeSpaced && isSpaced(penultimate, last)) { + reportNoEndingSpace(node, 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); + + 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; + } + + validateBraceSpacing(node, first, second, penultimate, last); + + }, + + // var y = {x: 'y'} + ObjectExpression: function(node) { + if (node.properties.length === 0) { + return; + } + + 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); + } + + }; + +}; + +module.exports.schema = [ + { + "enum": ["always", "never"] + }, + { + "type": "object", + "properties": { + "arraysInObjects": { + "type": "boolean" + }, + "objectsInObjects": { + "type": "boolean" + } + }, + "additionalProperties": false + } +]; diff --git a/eslint/babel-eslint-plugin/tests/object-curly-spacing.js b/eslint/babel-eslint-plugin/tests/object-curly-spacing.js new file mode 100644 index 0000000000..fb97a70130 --- /dev/null +++ b/eslint/babel-eslint-plugin/tests/object-curly-spacing.js @@ -0,0 +1,500 @@ +/* eslint-disable */ + +/** + * @fileoverview Disallows or enforces spaces inside of object literals. + * @author Jamund Ferguson + * @copyright 2014 Vignesh Anand. All rights reserved. + * @copyright 2015 Jamund Ferguson. All rights reserved. + * @copyright 2015 Mathieu M-Gosselin. All rights reserved. + */ + +var linter = require('eslint').linter + , ESLintTester = require('eslint-tester') + , eslintTester = new ESLintTester(linter); + +eslintTester.addRuleTest("rules/object-curly-spacing", { + + valid: [ + + // always - object literals + { code: "var obj = { foo: bar, baz: qux };", options: ["always"] }, + { code: "var obj = { foo: { bar: quxx }, baz: qux };", options: ["always"] }, + { code: "var obj = {\nfoo: bar,\nbaz: qux\n};", options: ["always"] }, + + // always - destructuring + { code: "var { x } = y", options: ["always"], ecmaFeatures: { destructuring: true } }, + { code: "var { x, y } = y", options: ["always"], ecmaFeatures: { destructuring: true } }, + { code: "var { x,y } = y", options: ["always"], ecmaFeatures: { destructuring: true } }, + { code: "var {\nx,y } = y", options: ["always"], ecmaFeatures: { destructuring: true } }, + { code: "var {\nx,y\n} = z", options: ["always"], ecmaFeatures: { destructuring: true } }, + { code: "var { x = 10, y } = y", options: ["always"], ecmaFeatures: { destructuring: true } }, + { 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 } }, + + // always - import / export + { 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: "export { door }", options: ["always"], ecmaFeatures: { modules: true } }, + { code: "import 'room'", options: ["always"], ecmaFeatures: { modules: true } }, + + // always - empty object + { code: "var foo = {};", options: ["always"] }, + + // always - objectsInObjects + { code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }};", options: ["always", {"objectsInObjects": false}] }, + + // always - arraysInObjects + { code: "var obj = { 'foo': [ 1, 2 ]};", options: ["always", {"arraysInObjects": false}] }, + + // always - arraysInObjects, objectsInObjects + { code: "var obj = { 'qux': [ 1, 2 ], 'foo': { 'bar': 1, 'baz': 2 }};", options: ["always", {"arraysInObjects": false, "objectsInObjects": false}] }, + + // always - arraysInObjects, objectsInObjects (reverse) + { code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 }, 'qux': [ 1, 2 ]};", options: ["always", {"arraysInObjects": false, "objectsInObjects": false}] }, + + // never + { code: "var obj = {foo: bar,\nbaz: qux\n};", options: ["never"] }, + { code: "var obj = {\nfoo: bar,\nbaz: qux};", options: ["never"] }, + + // never - object literals + { code: "var obj = {foo: bar, baz: qux};", options: ["never"] }, + { code: "var obj = {foo: {bar: quxx}, baz: qux};", options: ["never"] }, + { code: "var obj = {foo: {\nbar: quxx}, baz: qux\n};", options: ["never"] }, + { code: "var obj = {foo: {\nbar: quxx\n}, baz: qux};", options: ["never"] }, + { code: "var obj = {\nfoo: bar,\nbaz: qux\n};", options: ["never"] }, + + // never - destructuring + { code: "var {x} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {x, y} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {x,y} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {\nx,y\n} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {x = 10} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {x = 10, y} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { code: "var {x: {z}, y} = y", options: ["never"], ecmaFeatures: { destructuring: true } }, + { 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 } }, + + // never - import / export + { 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 } }, + { code: "export {\ndoor\n} from 'room'", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import {house,mouse} from 'caravan'", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import {house, mouse} from 'caravan'", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "export {door}", options: ["never"], ecmaFeatures: { modules: true } }, + { code: "import 'room'", options: ["never"], ecmaFeatures: { modules: true } }, + + // never - empty object + { code: "var foo = {};", options: ["never"] }, + + // never - objectsInObjects + { code: "var obj = {'foo': {'bar': 1, 'baz': 2} };", options: ["never", {"objectsInObjects": 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 } }, + ], + + invalid: [ + { + code: "import {bar} from 'foo.js';", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required after '{'", + type: "ImportDeclaration", + line: 1, + column: 7 + }, + { + message: "A space is required before '}'", + type: "ImportDeclaration", + line: 1, + column: 11 + } + ] + }, + { + code: "export {bar};", + options: ["always"], + ecmaFeatures: { + modules: true + }, + errors: [ + { + message: "A space is required after '{'", + type: "ExportNamedDeclaration", + line: 1, + column: 7 + }, + { + message: "A space is required before '}'", + type: "ExportNamedDeclaration", + line: 1, + column: 11 + } + ] + }, + + // always - arraysInObjects + { + code: "var obj = { 'foo': [ 1, 2 ] };", + options: ["always", {"arraysInObjects": false}], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectExpression" + } + ] + }, + { + code: "var obj = { 'foo': [ 1, 2 ] , 'bar': [ 'baz', 'qux' ] };", + options: ["always", {"arraysInObjects": false}], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectExpression" + } + ] + }, + + // always-objectsInObjects + { + code: "var obj = { 'foo': { 'bar': 1, 'baz': 2 } };", + options: ["always", {"objectsInObjects": false}], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectExpression", + line: 1, + column: 42 + } + ] + }, + { + code: "var obj = { 'foo': [ 1, 2 ] , 'bar': { 'baz': 1, 'qux': 2 } };", + options: ["always", {"objectsInObjects": false}], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectExpression", + line: 1, + column: 60 + } + ] + }, + + // always-destructuring trailing comma + { + code: "var { a,} = x;", + options: ["always"], + ecmaFeatures: { destructuring: true }, + errors: [ + { + message: "A space is required before '}'", + type: "ObjectPattern", + line: 1, + column: 8 + } + ] + }, + { + code: "var {a, } = x;", + options: ["never"], + ecmaFeatures: { destructuring: true }, + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectPattern", + line: 1, + column: 8 + } + ] + }, + + // never-objectsInObjects + { + code: "var obj = {'foo': {'bar': 1, 'baz': 2}};", + options: ["never", {"objectsInObjects": true}], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectExpression", + line: 1, + column: 38 + } + ] + }, + { + code: "var obj = {'foo': [1, 2] , 'bar': {'baz': 1, 'qux': 2}};", + options: ["never", {"objectsInObjects": true}], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectExpression", + line: 1, + column: 54 + } + ] + }, + + // always & never + { + code: "var obj = {foo: bar, baz: qux};", + options: ["always"], + errors: [ + { + message: "A space is required after '{'", + type: "ObjectExpression", + line: 1, + column: 10 + }, + { + message: "A space is required before '}'", + type: "ObjectExpression", + line: 1, + column: 29 + } + ] + }, + { + code: "var obj = {foo: bar, baz: qux };", + options: ["always"], + errors: [ + { + message: "A space is required after '{'", + type: "ObjectExpression", + line: 1, + column: 10 + } + ] + }, + { + code: "var obj = { foo: bar, baz: qux};", + options: ["always"], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectExpression", + line: 1, + column: 30 + } + ] + }, + { + code: "var obj = { foo: bar, baz: qux };", + options: ["never"], + errors: [ + { + message: "There should be no space after '{'", + type: "ObjectExpression", + line: 1, + column: 10 + }, + { + message: "There should be no space before '}'", + type: "ObjectExpression", + line: 1, + column: 31 + } + ] + }, + { + code: "var obj = {foo: bar, baz: qux };", + options: ["never"], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectExpression", + line: 1, + column: 30 + } + ] + }, + { + code: "var obj = { foo: bar, baz: qux};", + options: ["never"], + errors: [ + { + message: "There should be no space after '{'", + type: "ObjectExpression", + line: 1, + column: 10 + } + ] + }, + { + code: "var obj = { foo: { bar: quxx}, baz: qux};", + options: ["never"], + errors: [ + { + message: "There should be no space after '{'", + type: "ObjectExpression", + line: 1, + column: 10 + }, + { + message: "There should be no space after '{'", + type: "ObjectExpression", + line: 1, + column: 17 + } + ] + }, + { + code: "var obj = {foo: {bar: quxx }, baz: qux };", + options: ["never"], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectExpression", + line: 1, + column: 27 + }, + { + message: "There should be no space before '}'", + type: "ObjectExpression", + line: 1, + column: 39 + } + ] + }, + { + code: "export const thing = {value: 1 };", + ecmaFeatures: { + modules: true, + blockBindings: true + }, + options: ["always"], + errors: [ + { + message: "A space is required after '{'", + type: "ObjectExpression", + line: 1, + column: 21 + } + ] + }, + + // destructuring + { + code: "var {x, y} = y", + ecmaFeatures: {destructuring: true}, + options: ["always"], + errors: [ + { + message: "A space is required after '{'", + type: "ObjectPattern", + line: 1, + column: 4 + }, + { + message: "A space is required before '}'", + type: "ObjectPattern", + line: 1, + column: 9 + } + ] + }, + { + code: "var { x, y} = y", + ecmaFeatures: {destructuring: true}, + options: ["always"], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectPattern", + line: 1, + column: 10 + } + ] + }, + { + code: "var { x, y } = y", + ecmaFeatures: {destructuring: true}, + options: ["never"], + errors: [ + { + message: "There should be no space after '{'", + type: "ObjectPattern", + line: 1, + column: 4 + }, + { + message: "There should be no space before '}'", + type: "ObjectPattern", + line: 1, + column: 11 + } + ] + }, + { + code: "var {x, y } = y", + ecmaFeatures: {destructuring: true}, + options: ["never"], + errors: [ + { + message: "There should be no space before '}'", + type: "ObjectPattern", + line: 1, + column: 10 + } + ] + }, + { + code: "var { x=10} = y", + ecmaFeatures: {destructuring: true}, + options: ["always"], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectPattern", + line: 1, + column: 10 + } + ] + }, + { + code: "var {x=10 } = y", + ecmaFeatures: {destructuring: true}, + options: ["always"], + errors: [ + { + message: "A space is required after '{'", + type: "ObjectPattern", + line: 1, + column: 4 + } + ] + }, + + // never - arraysInObjects + { + code: "var obj = {'foo': [1, 2]};", + options: ["never", {"arraysInObjects": true}], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectExpression" + } + ] + }, + { + code: "var obj = {'foo': [1, 2] , 'bar': ['baz', 'qux']};", + options: ["never", {"arraysInObjects": true}], + errors: [ + { + message: "A space is required before '}'", + type: "ObjectExpression" + } + ] + } + ] +});