Skip to content

Commit

Permalink
Add no-console-spaces rule (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
MrHen authored and sindresorhus committed Oct 28, 2018
1 parent b44534b commit 5dd529f
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 2 deletions.
38 changes: 38 additions & 0 deletions docs/rules/no-console-spaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Do not use leading/trailing space between `console.log` parameters

The [`console.log()` method](https://developer.mozilla.org/en-US/docs/Web/API/Console/log) and similar methods joins the parameters with a space, so adding a leading/trailing space to a parameter, results in two spaces being added.


## Fail

```js
console.log('abc ', 'def');
console.log('abc', ' def');

console.log("abc ", " def");
console.log(`abc `, ` def`);

console.debug('abc ', 'def');
console.info('abc ', 'def');
console.warn('abc ', 'def');
console.error('abc ', 'def');
```


## Pass

```js
console.log('abc');
console.log('abc', 'def');

console.log('abc ');
console.log(' abc');

console.log('abc ', 'def');
console.log('abc\t', 'def');
console.log('abc\n', 'def');

console.log(`
abc
`);
```
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ module.exports = {
'unicorn/error-message': 'error',
'unicorn/no-unsafe-regex': 'off',
'unicorn/prefer-add-event-listener': 'error',
'unicorn/prefer-exponentiation-operator': 'error'
'unicorn/prefer-exponentiation-operator': 'error',
'unicorn/no-console-spaces': 'error'
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ Configure it in `package.json`.
"unicorn/prefer-spread": "error",
"unicorn/error-message": "error",
"unicorn/no-unsafe-regex": "off",
"unicorn/prefer-add-event-listener": "error"
"unicorn/prefer-add-event-listener": "error",
"unicorn/no-console-spaces": "error"
}
}
}
Expand Down Expand Up @@ -86,6 +87,7 @@ Configure it in `package.json`.
- [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions.
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `addEventListener` over `on`-functions. *(fixable)*
- [prefer-exponentiation-operator](docs/rules/prefer-exponentiation-operator.md) - Prefer the exponentiation operator over `Math.pow()` *(fixable)*
- [no-console-spaces](docs/rules/no-console-spaces.md) - Do not use leading/trailing space between `console.log` parameters. *(fixable)*


## Recommended config
Expand Down
141 changes: 141 additions & 0 deletions rules/no-console-spaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';
const getDocsUrl = require('./utils/get-docs-url');

const getConsoleMethod = node => {
const methods = [
'log',
'debug',
'info',
'warn',
'error'
];

const {callee} = node;

if (
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === 'console' &&
callee.property.type === 'Identifier' &&
methods.includes(callee.property.name)
) {
return callee.property.name;
}
};

const getArgumentValue = (context, nodeArgument) => {
let value = null;

if (nodeArgument.type === 'Literal' && typeof nodeArgument.value === 'string') {
value = nodeArgument.value;
}

if (nodeArgument.type === 'TemplateLiteral') {
const sourceCode = context.getSourceCode();
value = sourceCode.getText(nodeArgument);
// Strip off backticks
value = value.substring(1, value.length - 1);
}

return value;
};

const fixValue = (value, {
fixLeading = true,
fixTrailing = true
}) => {
if (!value) {
return value;
}

// Allow exactly one space
if (value.length <= 1) {
return value;
}

let fixed = value;

// Find exactly one leading space
if (fixLeading && fixed.startsWith(' ') && !fixed.startsWith(' ')) {
fixed = fixed.slice(1);
}

// Find exactly one trailing space
if (fixTrailing && fixed.endsWith(' ') && !fixed.endsWith(' ')) {
fixed = fixed.slice(0, -1);
}

return fixed;
};

const getFixableArguments = (context, node) => {
const {
arguments: args
} = node;

const fixables = args.map((nodeArgument, i) => {
const fixLeading = i !== 0;
const fixTrailing = i !== (args.length - 1);

const value = getArgumentValue(context, nodeArgument);
const fixed = fixValue(value, {fixLeading, fixTrailing});

return {
nodeArgument,
value,
fixed,
fixable: value !== fixed
};
});

return fixables.filter(fixable => fixable.fixable);
};

const fixArg = (context, fixable, fixer) => {
const {
nodeArgument,
fixed
} = fixable;

// Ignore quotes and backticks
const range = [
nodeArgument.range[0] + 1,
nodeArgument.range[1] - 1
];

return fixer.replaceTextRange(range, fixed);
};

const buildErrorMessage = method => {
return `Do not use leading/trailing space between \`console.${method}\` parameters.`;
};

const create = context => {
return {
CallExpression(node) {
const method = getConsoleMethod(node);
if (!method) {
return;
}

const fixables = getFixableArguments(context, node);
for (const fixable of fixables) {
context.report({
node: fixable.nodeArgument,
message: buildErrorMessage(method),
fix: fixer => fixArg(context, fixable, fixer)
});
}
}
};
};

module.exports = {
create,
meta: {
docs: {
url: getDocsUrl(__filename)
},
fixable: 'code'
}
};
141 changes: 141 additions & 0 deletions test/no-console-spaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import test from 'ava';
import avaRuleTester from 'eslint-ava-rule-tester';
import rule from '../rules/no-console-spaces';

const ruleTester = avaRuleTester(test, {
parserOptions: {
ecmaVersion: 2016
}
});

function buildError({method, column, line}) {
const error = {
ruleId: 'no-console-spaces',
message: `Do not use leading/trailing space between \`console.${method}\` parameters.`
};

if (column) {
error.column = column;
}

if (line) {
error.line = line;
}

return error;
}

ruleTester.run('no-console-spaces', rule, {
valid: [
'console.log("abc");',
'console.log("abc", "def");',
'console.log(\'abc\', "def");',
'console.log(`abc`, "def");',
'console.log("abc", "def");',
'console.log(`\nabc\ndef\n`);',

'console.log(\' \', "def");',
'console.log(\' \', "def");',
'console.log("abc ", "def");',
'console.log("abc\\t", "def");',
'console.log("abc\\n", "def");',
'console.log(" abc", "def");',

'console.log(" abc", "def");',
'console.log("abc", "def ");',

'console.log();',
'console.log("");',
'console.log(123);',
'console.log(null);',
'console.log(undefined);',

'console.dir("abc ");'
],
invalid: [
{
code: 'console.log("abc ", "def");',
errors: [buildError({method: 'log'})],
output: 'console.log("abc", "def");'
},
{
code: 'console.log("abc", " def");',
errors: [buildError({method: 'log'})],
output: 'console.log("abc", "def");'
},
{
code: 'console.log(" abc ", "def");',
errors: [buildError({method: 'log'})],
output: 'console.log(" abc", "def");'
},
{
code: 'console.debug("abc ", "def");',
errors: [buildError({method: 'debug'})],
output: 'console.debug("abc", "def");'
},
{
code: 'console.info("abc ", "def");',
errors: [buildError({method: 'info'})],
output: 'console.info("abc", "def");'
},
{
code: 'console.warn("abc ", "def");',
errors: [buildError({method: 'warn'})],
output: 'console.warn("abc", "def");'
},
{
code: 'console.error("abc ", "def");',
errors: [buildError({method: 'error'})],
output: 'console.error("abc", "def");'
},
{
code: 'console.log("abc", " def ", "ghi");',
errors: [buildError({method: 'log'})],
output: 'console.log("abc", "def", "ghi");'
},
{
code: 'console.log("abc ", "def ", "ghi");',
errors: [
buildError({method: 'log', column: 13}),
buildError({method: 'log', column: 21})
],
output: 'console.log("abc", "def", "ghi");'
},
{
code: 'console.log(\'abc \', "def");',
errors: [buildError({method: 'log'})],
output: 'console.log(\'abc\', "def");'
},
{
code: 'console.log(`abc `, "def");',
errors: [buildError({method: 'log'})],
output: 'console.log(`abc`, "def");'
},
{
// eslint-disable-next-line no-template-curly-in-string
code: 'console.log(`abc ${1 + 2} `, "def");',
errors: [buildError({method: 'log'})],
// eslint-disable-next-line no-template-curly-in-string
output: 'console.log(`abc ${1 + 2}`, "def");'
},
{
code: `
console.log(
'abc',
'def ',
'ghi'
);
`,
errors: [
buildError({method: 'log', column: 6, line: 4})
],
output: `
console.log(
'abc',
'def',
'ghi'
);
`
}
]
});

0 comments on commit 5dd529f

Please sign in to comment.