From 0d3848eddf7417c36112874067305dc5b0d54a87 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 21 Oct 2024 02:23:23 +0300 Subject: [PATCH 1/2] feat: You can now ctrl+click on event name of methods like addListener, on for jumping into event-related listeners or triggers like `emit` --- typescript/src/definitions.ts | 4 ++ typescript/src/eventsReferences.ts | 62 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 typescript/src/eventsReferences.ts diff --git a/typescript/src/definitions.ts b/typescript/src/definitions.ts index ef749b4..57ab44e 100644 --- a/typescript/src/definitions.ts +++ b/typescript/src/definitions.ts @@ -1,6 +1,7 @@ import { join } from 'path-browserify' import { GetConfig } from './types' import { findChildContainingExactPosition } from './utils' +import { eventDefinitions } from './eventsReferences' export default (proxy: ts.LanguageService, languageService: ts.LanguageService, languageServiceHost: ts.LanguageServiceHost, c: GetConfig) => { proxy.getDefinitionAndBoundSpan = (fileName, position, ...props) => { @@ -26,6 +27,9 @@ export default (proxy: ts.LanguageService, languageService: ts.LanguageService, const noDefs = !prior?.definitions || prior.definitions.length === 0 const tryFileResolve = noDefs || ['?', '#'].some(x => prior.definitions?.[0]?.fileName?.includes(x)) + const eventDefs = eventDefinitions(languageService, fileName, position) + if (eventDefs) return eventDefs + // Definition fallbacks if (noDefs || tryFileResolve) { const node = getNode() diff --git a/typescript/src/eventsReferences.ts b/typescript/src/eventsReferences.ts new file mode 100644 index 0000000..1e384b6 --- /dev/null +++ b/typescript/src/eventsReferences.ts @@ -0,0 +1,62 @@ +import ts from 'typescript' +import { findChildContainingExactPosition, matchParents } from './utils' + +export const eventDefinitions = (languageService: ts.LanguageService, fileName: string, position: number): ts.DefinitionInfoAndBoundSpan | undefined => { + const program = languageService.getProgram()! + const sourceFile = program.getSourceFile(fileName)! + const node = findChildContainingExactPosition(sourceFile, position) + if (!node || !ts.isStringLiteral(node)) return + const eventName = node.text + const expr = matchParents(node, ['StringLiteral', 'CallExpression']) + if (!expr) return + if (!ts.isPropertyAccessExpression(expr.expression)) return + const parentAccessEndPos = expr.expression.expression.getEnd() + const method = expr.expression.name.text + let lookForMethods: string[] | undefined + const onMethods = ['on', 'once', 'off', 'addEventListener', 'removeEventListener', 'addListener', 'removeListener'] + const triggerMethods = ['trigger', 'emit', 'dispatchEvent'] + if (onMethods.includes(method)) { + lookForMethods = triggerMethods + } + if (triggerMethods.includes(method)) { + lookForMethods = onMethods + } + if (!lookForMethods) return + const references = languageService.findReferences(fileName, parentAccessEndPos) ?? [] + const defs: ts.DefinitionInfo[] = references + .flatMap(({ references }) => references) + .map(({ fileName, textSpan }): ts.DefinitionInfo | undefined => { + const sourceFile = program.getSourceFile(fileName)! + const node = findChildContainingExactPosition(sourceFile, textSpan.start) + if (!node) return + if (!ts.isPropertyAccessExpression(node.parent)) return + let upNode = node as ts.PropertyAccessExpression + while (ts.isPropertyAccessExpression(upNode.parent)) { + upNode = upNode.parent + } + if (!ts.isCallExpression(upNode.parent)) return + if (!ts.isPropertyAccessExpression(upNode.parent.expression)) return + const method = upNode.parent.expression.name.text + if (!lookForMethods!.includes(method)) return + const arg = upNode.parent.arguments[0] + if (!arg || !ts.isStringLiteral(arg)) return + const lastArgEnd = upNode.parent.arguments.at(-1)!.end + const span = ts.createTextSpanFromBounds(arg.pos, lastArgEnd) + if (arg.text !== eventName) return + + return { + kind: ts.ScriptElementKind.memberVariableElement, + name: method, + containerKind: ts.ScriptElementKind.variableElement, + containerName: method, + textSpan: span, + fileName, + } + }) + .filter(a => a !== undefined) + .map(a => a!) + return { + textSpan: ts.createTextSpanFromBounds(node.pos, node.end), + definitions: defs, + } +} From e71c7a615c87148bed0b19b9bc8496fcfacd10cf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 21 Oct 2024 02:26:29 +0300 Subject: [PATCH 2/2] fix lint --- typescript/src/eventsReferences.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/typescript/src/eventsReferences.ts b/typescript/src/eventsReferences.ts index 1e384b6..e7d9dfc 100644 --- a/typescript/src/eventsReferences.ts +++ b/typescript/src/eventsReferences.ts @@ -1,4 +1,3 @@ -import ts from 'typescript' import { findChildContainingExactPosition, matchParents } from './utils' export const eventDefinitions = (languageService: ts.LanguageService, fileName: string, position: number): ts.DefinitionInfoAndBoundSpan | undefined => {