Skip to content

Commit

Permalink
feat(eslint-plugin): Add func-call-spacing (#448)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher authored and JamesHenry committed Apr 24, 2019
1 parent 8c88dff commit 92e65ec
Show file tree
Hide file tree
Showing 7 changed files with 551 additions and 2 deletions.
3 changes: 2 additions & 1 deletion packages/eslint-plugin/README.md
Expand Up @@ -120,9 +120,10 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | |
| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | |
| [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | |
| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Spacing between function identifiers and their invocations | | :wrench: | |
| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation (`indent` from TSLint) | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | :heavy_check_mark: | | |
| [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: |
| [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | |
| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | |
| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `<Type>` assertions (`no-angle-bracket-type-assertion` from TSLint) | :heavy_check_mark: | | |
Expand Down
26 changes: 26 additions & 0 deletions packages/eslint-plugin/docs/rules/func-call-spacing.md
@@ -0,0 +1,26 @@
# require or disallow spacing between function identifiers and their invocations (func-call-spacing)

When calling a function, developers may insert optional whitespace between the function’s name and the parentheses that invoke it.
This rule requires or disallows spaces between the function name and the opening parenthesis that calls it.

## Rule Details

This rule extends the base [eslint/func-call-spacing](https://eslint.org/docs/rules/func-call-spacing) rule.
It supports all options and features of the base rule.
This version adds support for generic type parameters on function calls.

## How to use

```cjson
{
// note you must disable the base rule as it can report incorrect errors
"func-call-spacing": "off",
"@typescript-eslint/func-call-spacing": ["error"]
}
```

## Options

See [eslint/func-call-spacing options](https://eslint.org/docs/rules/func-call-spacing#options).

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/func-call-spacing.md)</sup>
153 changes: 153 additions & 0 deletions packages/eslint-plugin/src/rules/func-call-spacing.ts
@@ -0,0 +1,153 @@
import { TSESTree } from '@typescript-eslint/typescript-estree';
import { isOpeningParenToken } from 'eslint-utils';
import * as util from '../util';

export type Options = [
'never' | 'always',
{
allowNewlines?: boolean;
}?
];
export type MessageIds = 'unexpected' | 'missing';

export default util.createRule<Options, MessageIds>({
name: 'func-call-spacing',
meta: {
type: 'layout',
docs: {
description:
'require or disallow spacing between function identifiers and their invocations',
category: 'Stylistic Issues',
recommended: false,
},
fixable: 'whitespace',
schema: {
anyOf: [
{
type: 'array',
items: [
{
enum: ['never'],
},
],
minItems: 0,
maxItems: 1,
},
{
type: 'array',
items: [
{
enum: ['always'],
},
{
type: 'object',
properties: {
allowNewlines: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
minItems: 0,
maxItems: 2,
},
],
},

messages: {
unexpected:
'Unexpected space or newline between function name and paren.',
missing: 'Missing space between function name and paren.',
},
},
defaultOptions: ['never', {}],
create(context, [option, config]) {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText();

/**
* Check if open space is present in a function name
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkSpacing(
node: TSESTree.CallExpression | TSESTree.NewExpression,
): void {
const closingParenToken = sourceCode.getLastToken(node)!;
const lastCalleeTokenWithoutPossibleParens = sourceCode.getLastToken(
node.typeParameters || node.callee,
)!;
const openingParenToken = sourceCode.getFirstTokenBetween(
lastCalleeTokenWithoutPossibleParens,
closingParenToken,
isOpeningParenToken,
);
if (!openingParenToken || openingParenToken.range[1] >= node.range[1]) {
// new expression with no parens...
return;
}
const lastCalleeToken = sourceCode.getTokenBefore(openingParenToken)!;

const textBetweenTokens = text
.slice(lastCalleeToken.range[1], openingParenToken.range[0])
.replace(/\/\*.*?\*\//gu, '');
const hasWhitespace = /\s/u.test(textBetweenTokens);
const hasNewline =
hasWhitespace && util.LINEBREAK_MATCHER.test(textBetweenTokens);

if (option === 'never') {
if (hasWhitespace) {
return context.report({
node,
loc: lastCalleeToken.loc.start,
messageId: 'unexpected',
fix(fixer) {
/*
* Only autofix if there is no newline
* https://github.com/eslint/eslint/issues/7787
*/
if (!hasNewline) {
return fixer.removeRange([
lastCalleeToken.range[1],
openingParenToken.range[0],
]);
}

return null;
},
});
}
} else {
if (!hasWhitespace) {
context.report({
node,
loc: lastCalleeToken.loc.start,
messageId: 'missing',
fix(fixer) {
return fixer.insertTextBefore(openingParenToken, ' ');
},
});
} else if (!config!.allowNewlines && hasNewline) {
context.report({
node,
loc: lastCalleeToken.loc.start,
messageId: 'unexpected',
fix(fixer) {
return fixer.replaceTextRange(
[lastCalleeToken.range[1], openingParenToken.range[0]],
' ',
);
},
});
}
}
}

return {
CallExpression: checkSpacing,
NewExpression: checkSpacing,
};
},
});
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/util/astUtils.ts
@@ -0,0 +1 @@
export const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/;
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/util/index.ts
@@ -1,4 +1,5 @@
export * from './applyDefault';
export * from './astUtils';
export * from './createRule';
export * from './deepMerge';
export * from './getParserServices';
Expand Down

0 comments on commit 92e65ec

Please sign in to comment.