diff --git a/docs/rules/empty-brace-spaces.md b/docs/rules/empty-brace-spaces.md new file mode 100644 index 0000000000..8f02f2de95 --- /dev/null +++ b/docs/rules/empty-brace-spaces.md @@ -0,0 +1,28 @@ +# Enforce no spaces between braces + +This rule is fixable. + +## Fail + +```js +class Unicorn { +} +``` + +```js +try { + foo(); +} catch { } +``` + +## Pass + +```js +class Unicorn {} +``` + +```js +try { + foo(); +} catch {} +``` diff --git a/index.js b/index.js index e81861e43d..f09f19ed22 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,7 @@ module.exports = { 'unicorn/catch-error-name': 'error', 'unicorn/consistent-function-scoping': 'error', 'unicorn/custom-error-definition': 'off', + 'unicorn/empty-brace-spaces': 'error', 'unicorn/error-message': 'error', 'unicorn/escape-case': 'error', 'unicorn/expiring-todo-comments': 'error', diff --git a/readme.md b/readme.md index 8783afa6c0..ea474c3b97 100644 --- a/readme.md +++ b/readme.md @@ -37,6 +37,7 @@ Configure it in `package.json`. "unicorn/catch-error-name": "error", "unicorn/consistent-function-scoping": "error", "unicorn/custom-error-definition": "off", + "unicorn/empty-brace-spaces": "error", "unicorn/error-message": "error", "unicorn/escape-case": "error", "unicorn/expiring-todo-comments": "error", @@ -105,6 +106,7 @@ Configure it in `package.json`. - [catch-error-name](docs/rules/catch-error-name.md) - Enforce a specific parameter name in catch clauses. *(fixable)* - [consistent-function-scoping](docs/rules/consistent-function-scoping.md) - Move function definitions to the highest possible scope. - [custom-error-definition](docs/rules/custom-error-definition.md) - Enforce correct `Error` subclassing. *(fixable)* +- [empty-brace-spaces](docs/rules/empty-brace-spaces.md) - Enforce no spaces between braces. *(fixable)* - [error-message](docs/rules/error-message.md) - Enforce passing a `message` value when creating a built-in error. - [escape-case](docs/rules/escape-case.md) - Require escape sequences to use uppercase values. *(fixable)* - [expiring-todo-comments](docs/rules/expiring-todo-comments.md) - Add expiration conditions to TODO comments. diff --git a/rules/empty-brace-spaces.js b/rules/empty-brace-spaces.js new file mode 100644 index 0000000000..e704da7557 --- /dev/null +++ b/rules/empty-brace-spaces.js @@ -0,0 +1,51 @@ +'use strict'; +const getDocumentationUrl = require('./utils/get-documentation-url'); + +const MESSAGE_ID = 'empty-brace-spaces'; +const messages = { + [MESSAGE_ID]: 'Do not add spaces between braces.' +}; + +const selector = `:matches(${ + [ + 'BlockStatement[body.length=0]', + 'ClassBody[body.length=0]', + 'ObjectExpression[properties.length=0]' + ].join(', ') +})`; + +const create = context => { + const sourceCode = context.getSourceCode(); + return { + [selector](node) { + let [start, end] = node.range; + start += 1; + end -= 1; + + if (!/^\s+$/.test(sourceCode.text.slice(start, end))) { + return; + } + + context.report({ + loc: { + start: sourceCode.getLocFromIndex(start), + end: sourceCode.getLocFromIndex(end) + }, + messageId: MESSAGE_ID, + fix: fixer => fixer.replaceTextRange([start, end], '') + }); + } + }; +}; + +module.exports = { + create, + meta: { + type: 'layout', + docs: { + url: getDocumentationUrl(__filename) + }, + fixable: 'whitespace', + messages + } +}; diff --git a/test/empty-brace-spaces.js b/test/empty-brace-spaces.js new file mode 100644 index 0000000000..4bd43c3ae8 --- /dev/null +++ b/test/empty-brace-spaces.js @@ -0,0 +1,89 @@ +import {outdent} from 'outdent'; +import {flatten} from 'lodash'; +import {test} from './utils/test'; + +const SPACES_PLACEHOLDER = '/* */'; +const cases = [ + '{/* */}', + 'function foo(){/* */}', + 'if(foo) {/* */}', + 'if(foo) {} else if (bar) {/* */}', + 'if(foo) {} else {/* */}', + 'for(;;){/* */}', + 'for(foo in bar){/* */}', + 'for(foo of bar){/* */}', + 'switch (foo) {case bar: {/* */}}', + 'switch (foo) {default: {/* */}}', + 'try {/* */} catch(foo){}', + 'try {} catch(foo){/* */}', + 'try {} catch(foo){} finally {/* */}', + 'do {/* */} while (foo)', + 'while (foo){/* */}', + 'foo = () => {/* */}', + 'foo = function (){/* */}', + 'function foo(){/* */}', + 'foo = {/* */}', + 'class Foo {bar() {/* */}}', + 'foo = class {bar() {/* */}}' +]; +const classBodyCases = [ + 'class Foo {/* */}', + 'foo = class {/* */}' +]; +const allCases = [...cases, ...classBodyCases]; + +const ignoredCases = [ + 'switch (foo) {/* */}', + 'const {/* */} = foo', + 'import {/* */} from "foo"' +]; + +test({ + valid: [ + ...flatten([ + '', + '/* comment */', + '\n\t// comment \n' + ].map(body => allCases.map(code => code.replace(SPACES_PLACEHOLDER, body)))), + // Not empty + ...cases.map(code => code.replace(SPACES_PLACEHOLDER, 'unicorn')), + ...classBodyCases.map(code => code.replace(SPACES_PLACEHOLDER, 'bar() {}')), + // `with` + { + code: 'with (foo) {}', + parserOptions: {ecmaVersion: 5, sourceType: 'script'} + }, + // We don't check these cases + ...ignoredCases.map(code => code.replace(SPACES_PLACEHOLDER, ' ')) + ], + invalid: [ + ...flatten([ + ' ', + '\t', + ' \t \t ', + '\n\n', + '\r\n' + ].map(spaces => allCases.map(code => ({ + code: code.replace(SPACES_PLACEHOLDER, spaces), + output: code.replace(SPACES_PLACEHOLDER, ''), + errors: 1 + })))), + // `with` + { + code: 'with (foo) { }', + output: 'with (foo) {}', + errors: 1, + parserOptions: {ecmaVersion: 5, sourceType: 'script'} + } + ] +}); + +test.visualize([ + outdent` + try { + foo(); + } catch (error) { + \u0020\u0020\u0020\u0020\u0020\u0020\u0020 + } + ` +]); diff --git a/test/prevent-abbreviations.js b/test/prevent-abbreviations.js index 97e8580a2b..2433335c85 100644 --- a/test/prevent-abbreviations.js +++ b/test/prevent-abbreviations.js @@ -21,8 +21,7 @@ const browserES5RuleTester = avaRuleTester(test, { const noFixingTestCase = test => ({...test, output: test.code}); const createErrors = message => { - const error = { - }; + const error = {}; if (message) { error.message = message; diff --git a/test/snapshots/empty-brace-spaces.js.md b/test/snapshots/empty-brace-spaces.js.md new file mode 100644 index 0000000000..7673525330 --- /dev/null +++ b/test/snapshots/empty-brace-spaces.js.md @@ -0,0 +1,33 @@ +# Snapshot report for `test/empty-brace-spaces.js` + +The actual snapshot is saved in `empty-brace-spaces.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## empty-brace-spaces - #1 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | try {␊ + 2 | foo();␊ + 3 | } catch (error) {␊ + 4 | ␊ + 5 | }␊ + ␊ + Output:␊ + 1 | try {␊ + 2 | foo();␊ + 3 | } catch (error) {}␊ + ␊ + Error 1/1:␊ + 1 | try {␊ + 2 | foo();␊ + > 3 | } catch (error) {␊ + | ^␊ + > 4 | ␊ + | ^^^^^^^^␊ + > 5 | }␊ + | ^ Do not add spaces between braces.␊ + ` diff --git a/test/snapshots/empty-brace-spaces.js.snap b/test/snapshots/empty-brace-spaces.js.snap new file mode 100644 index 0000000000..13ed1185b6 Binary files /dev/null and b/test/snapshots/empty-brace-spaces.js.snap differ