Skip to content

Commit

Permalink
feat(eslint-plugin): sync getFunctionHeadLoc implementation with upst…
Browse files Browse the repository at this point in the history
…ream (#7260)
  • Loading branch information
bradzacher committed Jul 18, 2023
1 parent 38101b9 commit f813147
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 208 deletions.
230 changes: 181 additions & 49 deletions packages/eslint-plugin/src/util/getFunctionHeadLoc.ts
@@ -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),
};
}

0 comments on commit f813147

Please sign in to comment.