如何用正则或其他方法获取光标位置处的JavaScript函数名?
Got it, let's tackle this problem since you already have code splitting in place—half the battle is done! Your goal is to grab the function name at the cursor (like function in object.subobject.function(...) from your example) to pull up its definition. Here's how to approach this step by step:
1. Map Tokens to Positions First
Since you can split code into tokens, make sure each token stores its start/end character positions in the original code. This lets you quickly find which token (or token range) the cursor is sitting in. For example, if your cursor is inside the () of the function call, you'll first need to trace back to the left parenthesis (.
2. Trace Back to the Function Identifier
JavaScript function calls follow patterns like func() or obj.sub.func(). Here's how to extract the target name:
- If cursor is inside
(): First, find the matching left parenthesis((you'll need a simple bracket-matching function to handle nested parentheses). Once you have that(token, look at the token immediately before it. - Handle chained calls: If the token before
(is a., keep moving left, collecting identifiers and dots until you hit a non-identifier/non-dot character (like a space,=, or another punctuation). For your example, this would collectobject.subobject.function. - Extract the final function name: Split the collected chain by
.and take the last element—this is the function name you need (in your case,function).
3. Use AST Parsing for Robustness (Optional but Recommended)
If you want to avoid edge cases (like nested calls, arrow functions, or complex expressions), use a lightweight JavaScript parser like Acorn or Esprima to generate an Abstract Syntax Tree (AST):
- Parse the current code into an AST.
- Use the cursor position to find the corresponding AST node (most parsers let you map character positions to nodes).
- If the node is a
CallExpression, look at itscalleeproperty:- For direct calls like
func(), thecalleeis anIdentifier—itsnameis your target. - For chained calls like
obj.sub.func(), thecalleeis aMemberExpression. Traverse thepropertychain until you reach the finalIdentifier(e.g.,funcin the chain).
- For direct calls like
4. Handle Edge Cases
Don’t forget to account for tricky scenarios:
- Nested calls: If the cursor is in
foo(bar(baz())), make sure you targetbazinstead ofbarorfoo. - Non-function parentheses: Skip cases like
if(...)orfor(...)—check if the token before(is a valid identifier or member expression. - Anonymous functions: If the call is to an anonymous function (like
(function(){})()), there’s no name to fetch, so you can skip showing a definition.
Quick Example Pseudo-Code
Here’s a simplified snippet to illustrate the token-based approach:
function getTargetFunctionName(tokens, cursorPos) { // Find the token or bracket containing the cursor let currentToken = tokens.find(t => cursorPos >= t.start && cursorPos <= t.end); // If cursor is in closing ), find matching ( if (currentToken?.type === 'punctuator' && currentToken.value === ')') { const openParenIndex = findMatchingOpenParen(tokens, currentToken.index); currentToken = tokens[openParenIndex - 1]; } // Collect chained identifiers let nameChain = ''; let idx = currentToken.index; while (idx >= 0) { const token = tokens[idx]; if (token.type === 'identifier') { nameChain = token.value + nameChain; } else if (token.type === 'punctuator' && token.value === '.') { nameChain = '.' + nameChain; } else { break; } idx--; } // Return the final function name in the chain return nameChain.split('.').pop(); } // Helper to match opening parenthesis function findMatchingOpenParen(tokens, closeParenIndex) { let depth = 1; for (let i = closeParenIndex - 1; i >= 0; i--) { const token = tokens[i]; if (token.value === ')') depth++; if (token.value === '(') depth--; if (depth === 0) return i; } return -1; }
内容的提问来源于stack exchange,提问作者maltaannon

