Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin): sync getFunctionHeadLoc implementation with upst…
…ream (#7260)
- Loading branch information
1 parent
38101b9
commit f813147
Showing
3 changed files
with
340 additions
and
208 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,203 @@ | ||
// adapted from https://github.com/eslint/eslint/blob/5bdaae205c3a0089ea338b382df59e21d5b06436/lib/rules/utils/ast-utils.js#L1668-L1787 | ||
|
||
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; | ||
import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; | ||
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'; | ||
|
||
import { isArrowToken, isOpeningParenToken } from './astUtils'; | ||
|
||
type FunctionNode = | ||
| TSESTree.ArrowFunctionExpression | ||
| TSESTree.FunctionDeclaration | ||
| TSESTree.FunctionExpression; | ||
|
||
/** | ||
* Creates a report location for the given function. | ||
* The location only encompasses the "start" of the function, and not the body | ||
* | ||
* eg. | ||
* | ||
* ``` | ||
* function foo(args) {} | ||
* ^^^^^^^^^^^^^^^^^^ | ||
* | ||
* get y(args) {} | ||
* ^^^^^^^^^^^ | ||
* | ||
* const x = (args) => {} | ||
* ^^^^^^^^^ | ||
* ``` | ||
* Gets the `(` token of the given function node. | ||
* @param node The function node to get. | ||
* @param sourceCode The source code object to get tokens. | ||
* @returns `(` token. | ||
*/ | ||
export function getFunctionHeadLoc( | ||
function getOpeningParenOfParams( | ||
node: FunctionNode, | ||
sourceCode: TSESLint.SourceCode, | ||
): TSESTree.SourceLocation { | ||
function getLocStart(): TSESTree.Position { | ||
if (node.parent.type === AST_NODE_TYPES.MethodDefinition) { | ||
// return the start location for class method | ||
|
||
if (node.parent.decorators && node.parent.decorators.length > 0) { | ||
// exclude decorators | ||
return sourceCode.getTokenAfter( | ||
node.parent.decorators[node.parent.decorators.length - 1], | ||
)!.loc.start; | ||
} | ||
): TSESTree.Token { | ||
// If the node is an arrow function and doesn't have parens, this returns the identifier of the first param. | ||
if ( | ||
node.type === AST_NODE_TYPES.ArrowFunctionExpression && | ||
node.params.length === 1 | ||
) { | ||
const argToken = ESLintUtils.nullThrows( | ||
sourceCode.getFirstToken(node.params[0]), | ||
ESLintUtils.NullThrowsReasons.MissingToken('parameter', 'arrow function'), | ||
); | ||
const maybeParenToken = sourceCode.getTokenBefore(argToken); | ||
|
||
return node.parent.loc.start; | ||
} | ||
return maybeParenToken && isOpeningParenToken(maybeParenToken) | ||
? maybeParenToken | ||
: argToken; | ||
} | ||
|
||
if (node.parent.type === AST_NODE_TYPES.Property && node.parent.method) { | ||
// return the start location for object method shorthand | ||
return node.parent.loc.start; | ||
} | ||
// Otherwise, returns paren. | ||
return node.id != null | ||
? ESLintUtils.nullThrows( | ||
sourceCode.getTokenAfter(node.id, isOpeningParenToken), | ||
ESLintUtils.NullThrowsReasons.MissingToken('id', 'function'), | ||
) | ||
: ESLintUtils.nullThrows( | ||
sourceCode.getFirstToken(node, isOpeningParenToken), | ||
ESLintUtils.NullThrowsReasons.MissingToken( | ||
'opening parenthesis', | ||
'function', | ||
), | ||
); | ||
} | ||
|
||
// return the start location for a regular function | ||
return node.loc.start; | ||
} | ||
/** | ||
* Gets the location of the given function node for reporting. | ||
* | ||
* - `function foo() {}` | ||
* ^^^^^^^^^^^^ | ||
* - `(function foo() {})` | ||
* ^^^^^^^^^^^^ | ||
* - `(function() {})` | ||
* ^^^^^^^^ | ||
* - `function* foo() {}` | ||
* ^^^^^^^^^^^^^ | ||
* - `(function* foo() {})` | ||
* ^^^^^^^^^^^^^ | ||
* - `(function*() {})` | ||
* ^^^^^^^^^ | ||
* - `() => {}` | ||
* ^^ | ||
* - `async () => {}` | ||
* ^^ | ||
* - `({ foo: function foo() {} })` | ||
* ^^^^^^^^^^^^^^^^^ | ||
* - `({ foo: function() {} })` | ||
* ^^^^^^^^^^^^^ | ||
* - `({ ['foo']: function() {} })` | ||
* ^^^^^^^^^^^^^^^^^ | ||
* - `({ [foo]: function() {} })` | ||
* ^^^^^^^^^^^^^^^ | ||
* - `({ foo() {} })` | ||
* ^^^ | ||
* - `({ foo: function* foo() {} })` | ||
* ^^^^^^^^^^^^^^^^^^ | ||
* - `({ foo: function*() {} })` | ||
* ^^^^^^^^^^^^^^ | ||
* - `({ ['foo']: function*() {} })` | ||
* ^^^^^^^^^^^^^^^^^^ | ||
* - `({ [foo]: function*() {} })` | ||
* ^^^^^^^^^^^^^^^^ | ||
* - `({ *foo() {} })` | ||
* ^^^^ | ||
* - `({ foo: async function foo() {} })` | ||
* ^^^^^^^^^^^^^^^^^^^^^^^ | ||
* - `({ foo: async function() {} })` | ||
* ^^^^^^^^^^^^^^^^^^^ | ||
* - `({ ['foo']: async function() {} })` | ||
* ^^^^^^^^^^^^^^^^^^^^^^^ | ||
* - `({ [foo]: async function() {} })` | ||
* ^^^^^^^^^^^^^^^^^^^^^ | ||
* - `({ async foo() {} })` | ||
* ^^^^^^^^^ | ||
* - `({ get foo() {} })` | ||
* ^^^^^^^ | ||
* - `({ set foo(a) {} })` | ||
* ^^^^^^^ | ||
* - `class A { constructor() {} }` | ||
* ^^^^^^^^^^^ | ||
* - `class A { foo() {} }` | ||
* ^^^ | ||
* - `class A { *foo() {} }` | ||
* ^^^^ | ||
* - `class A { async foo() {} }` | ||
* ^^^^^^^^^ | ||
* - `class A { ['foo']() {} }` | ||
* ^^^^^^^ | ||
* - `class A { *['foo']() {} }` | ||
* ^^^^^^^^ | ||
* - `class A { async ['foo']() {} }` | ||
* ^^^^^^^^^^^^^ | ||
* - `class A { [foo]() {} }` | ||
* ^^^^^ | ||
* - `class A { *[foo]() {} }` | ||
* ^^^^^^ | ||
* - `class A { async [foo]() {} }` | ||
* ^^^^^^^^^^^ | ||
* - `class A { get foo() {} }` | ||
* ^^^^^^^ | ||
* - `class A { set foo(a) {} }` | ||
* ^^^^^^^ | ||
* - `class A { static foo() {} }` | ||
* ^^^^^^^^^^ | ||
* - `class A { static *foo() {} }` | ||
* ^^^^^^^^^^^ | ||
* - `class A { static async foo() {} }` | ||
* ^^^^^^^^^^^^^^^^ | ||
* - `class A { static get foo() {} }` | ||
* ^^^^^^^^^^^^^^ | ||
* - `class A { static set foo(a) {} }` | ||
* ^^^^^^^^^^^^^^ | ||
* - `class A { foo = function() {} }` | ||
* ^^^^^^^^^^^^^^ | ||
* - `class A { static foo = function() {} }` | ||
* ^^^^^^^^^^^^^^^^^^^^^ | ||
* - `class A { foo = (a, b) => {} }` | ||
* ^^^^^^ | ||
* @param node The function node to get. | ||
* @param sourceCode The source code object to get tokens. | ||
* @returns The location of the function node for reporting. | ||
*/ | ||
export function getFunctionHeadLoc( | ||
node: FunctionNode, | ||
sourceCode: TSESLint.SourceCode, | ||
): TSESTree.SourceLocation { | ||
const parent = node.parent; | ||
let start = null; | ||
let end = null; | ||
|
||
function getLocEnd(): TSESTree.Position { | ||
if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { | ||
// find the end location for arrow function expression | ||
return sourceCode.getTokenBefore( | ||
node.body, | ||
token => | ||
token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=>', | ||
)!.loc.end; | ||
if ( | ||
parent.type === AST_NODE_TYPES.MethodDefinition || | ||
parent.type === AST_NODE_TYPES.PropertyDefinition | ||
) { | ||
// the decorator's range is included within the member | ||
// however it's usually irrelevant to the member itself - so we don't want | ||
// to highlight it ever. | ||
if (parent.decorators.length > 0) { | ||
const lastDecorator = parent.decorators[parent.decorators.length - 1]; | ||
const firstTokenAfterDecorator = ESLintUtils.nullThrows( | ||
sourceCode.getTokenAfter(lastDecorator), | ||
ESLintUtils.NullThrowsReasons.MissingToken( | ||
'modifier or member name', | ||
'class member', | ||
), | ||
); | ||
start = firstTokenAfterDecorator.loc.start; | ||
} else { | ||
start = parent.loc.start; | ||
} | ||
end = getOpeningParenOfParams(node, sourceCode).loc.start; | ||
} else if (parent.type === AST_NODE_TYPES.Property) { | ||
start = parent.loc.start; | ||
end = getOpeningParenOfParams(node, sourceCode).loc.start; | ||
} else if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { | ||
const arrowToken = ESLintUtils.nullThrows( | ||
sourceCode.getTokenBefore(node.body, isArrowToken), | ||
ESLintUtils.NullThrowsReasons.MissingToken( | ||
'arrow token', | ||
'arrow function', | ||
), | ||
); | ||
|
||
// return the end location for a regular function | ||
return sourceCode.getTokenBefore(node.body)!.loc.end; | ||
start = arrowToken.loc.start; | ||
end = arrowToken.loc.end; | ||
} else { | ||
start = node.loc.start; | ||
end = getOpeningParenOfParams(node, sourceCode).loc.start; | ||
} | ||
|
||
return { | ||
start: getLocStart(), | ||
end: getLocEnd(), | ||
start: Object.assign({}, start), | ||
end: Object.assign({}, end), | ||
}; | ||
} |
Oops, something went wrong.