Skip to content

Commit

Permalink
Add rule empty-brace-space
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Dec 11, 2020
1 parent 898fcb4 commit db4003e
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 0 deletions.
17 changes: 17 additions & 0 deletions docs/rules/empty-brace-space.md
@@ -0,0 +1,17 @@
# Enforce no spaces between braces.

<!-- More detailed description. Remove this comment. -->

This rule is fixable.

## Fail

```js
const foo = 'unicorn';
```

## Pass

```js
const foo = '🦄';
```
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -21,6 +21,7 @@ module.exports = {
'unicorn/catch-error-name': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/custom-error-definition': 'off',
'unicorn/empty-brace-space': 'error',
'unicorn/error-message': 'error',
'unicorn/escape-case': 'error',
'unicorn/expiring-todo-comments': 'error',
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -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-space": "error",
"unicorn/error-message": "error",
"unicorn/escape-case": "error",
"unicorn/expiring-todo-comments": "error",
Expand Down Expand Up @@ -104,6 +105,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-space](docs/rules/empty-brace-space.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.
Expand Down
54 changes: 54 additions & 0 deletions rules/empty-brace-space.js
@@ -0,0 +1,54 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');

const MESSAGE_ID = 'empty-brace-space';
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 = start + 1;
end = 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], '')
});
}
}
};

const schema = [];

module.exports = {
create,
meta: {
type: 'layout',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'whitespace',
schema,
messages
}
};
86 changes: 86 additions & 0 deletions test/empty-brace-space.js
@@ -0,0 +1,86 @@
import {outdent} from 'outdent';
import {test} from './utils/test';

const MESSAGE_ID = 'empty-brace-space';

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 casesIgnored = [
'switch (foo) {/* */}',
'const {/* */} = foo',
'import {/* */} from "foo"'
];

test({
valid: [
// We don't check these cases
...casesIgnored.map(code => code.replace(SPACES_PLACEHOLDER, ' ')),
// No space
...allCases.map(code => code.replace(SPACES_PLACEHOLDER, '')),
// Comments
...allCases.map(code => code.replace(SPACES_PLACEHOLDER, '/* comment */')),
...allCases.map(code => code.replace(SPACES_PLACEHOLDER, '\n\t// comment \n')),
// 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'}
}
],
invalid: [
...[' ', '\t', ' \t \t ', '\n\n', '\r\n'].map(space =>
allCases.map(code => ({
code: code.replace(SPACES_PLACEHOLDER, space),
output: code.replace(SPACES_PLACEHOLDER, ''),
errors: 1
}))
).flat(),
// `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
}
`
]);
33 changes: 33 additions & 0 deletions test/snapshots/empty-brace-space.js.md
@@ -0,0 +1,33 @@
# Snapshot report for `test/empty-brace-space.js`

The actual snapshot is saved in `empty-brace-space.js.snap`.

Generated by [AVA](https://avajs.dev).

## empty-brace-space - #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.␊
`
Binary file added test/snapshots/empty-brace-space.js.snap
Binary file not shown.

0 comments on commit db4003e

Please sign in to comment.