diff --git a/eslint/babel-eslint-plugin/index.js b/eslint/babel-eslint-plugin/index.js index 43c631615e..7a3f7f4617 100644 --- a/eslint/babel-eslint-plugin/index.js +++ b/eslint/babel-eslint-plugin/index.js @@ -9,6 +9,7 @@ module.exports = { 'object-shorthand': require('./rules/object-shorthand'), 'arrow-parens': require('./rules/arrow-parens'), 'no-await-in-loop': require('./rules/no-await-in-loop'), + 'flow-object-type': require('./rules/flow-object-type'), }, rulesConfig: { 'generator-star-spacing': 0, @@ -18,5 +19,6 @@ module.exports = { 'object-shorthand': 0, 'arrow-parens': 0, 'no-await-in-loop': 0, + 'flow-object-type': 0, } }; diff --git a/eslint/babel-eslint-plugin/rules/flow-object-type.js b/eslint/babel-eslint-plugin/rules/flow-object-type.js new file mode 100644 index 0000000000..f452832d6b --- /dev/null +++ b/eslint/babel-eslint-plugin/rules/flow-object-type.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Enforces a choice between semicolons and commas in Flow object and class types. + * @author Nat Mote + */ +"use strict"; + +var SEMICOLON = { + char: ';', + name: 'semicolon', +} + +var COMMA = { + char: ',', + name: 'comma', +}; + +module.exports = function(context) { + var GOOD; + var BAD; + if (context.options[0] === undefined || context.options[0] === SEMICOLON.name) { + GOOD = SEMICOLON; + BAD = COMMA; + } else { + GOOD = COMMA; + BAD = SEMICOLON; + } + function requireProperPunctuation(node) { + var tokens = context.getSourceCode().getTokens(node); + var lastToken = tokens[tokens.length - 1]; + if (lastToken.type === 'Punctuator') { + if (lastToken.value === BAD.char) { + context.report({ + message: 'Prefer ' + GOOD.name + 's to ' + BAD.name + 's in object and class types', + node: lastToken, + fix: function(fixer) { + return fixer.replaceText(lastToken, GOOD.char); + }, + }); + } + } + } + + return { + ObjectTypeProperty: requireProperPunctuation, + }; +}; + +module.exports.schema = [ + { + 'enum': ['semicolon', 'comma'], + } +]; diff --git a/eslint/babel-eslint-plugin/tests/flow-object-type.js b/eslint/babel-eslint-plugin/tests/flow-object-type.js new file mode 100644 index 0000000000..f98c632664 --- /dev/null +++ b/eslint/babel-eslint-plugin/tests/flow-object-type.js @@ -0,0 +1,69 @@ +/** + * @fileoverview Tests for flow-object-type. + * @author Nat Mote + */ + +"use strict"; + +var rule = require("../rules/flow-object-type"), + RuleTester = require('eslint').RuleTester; + +var features = { +}; + +function test(code, options, errors, output){ + var result = { + code: code, + parser: 'babel-eslint', + ecmaFeatures: features, + }; + if (options != null) { + result.options = options; + } + if (errors != null) { + result.errors = errors; + } + if (output != null) { + result.output = output; + } + return result; +} + +var commaMessage = 'Prefer commas to semicolons in object and class types'; +var semiMessage = 'Prefer semicolons to commas in object and class types'; + +function ok(code, commaOrSemi) { + return test(code, [commaOrSemi]); +} + +function err(code, commaOrSemi, errorMessage, output) { + return test(code, [commaOrSemi], [errorMessage], output); +} + +var cases = [ + ok('type Foo = { a: Foo; b: Bar }', 'semicolon'), + err('type Foo = { a: Foo, b: Bar }', 'semicolon', semiMessage, 'type Foo = { a: Foo; b: Bar }'), + + ok('type Foo = { a: Foo, b: Bar }', 'comma'), + err('type Foo = { a: Foo; b: Bar }', 'comma', commaMessage, 'type Foo = { a: Foo, b: Bar }'), + + ok('declare class Foo { a: Foo; }', 'semicolon'), + err('declare class Foo { a: Foo, }', 'semicolon', semiMessage, 'declare class Foo { a: Foo; }'), + + ok('declare class Foo { a: Foo, }', 'comma'), + err('declare class Foo { a: Foo; }', 'comma', commaMessage, 'declare class Foo { a: Foo, }'), +]; + +function hasError(testCase) { + return testCase.errors != null && testCase.errors.length > 0; +} + +function hasNoError(testCase) { + return !hasError(testCase); +} + +var ruleTester = new RuleTester(); +ruleTester.run('flow-object-type', rule, { + valid: cases.filter(hasNoError), + invalid: cases.filter(hasError), +});