UNPKG

@neo4j-cypher/language-support

Version:
193 lines (168 loc) 5.56 kB
import { SignatureHelp, SignatureInformation, } from 'vscode-languageserver-types'; import { ParseTreeWalker } from 'antlr4'; import CypherParser, { CallClauseContext, ExpressionContext, FunctionInvocationContext, } from './generated-parser/CypherCmdParser'; import { Token } from 'antlr4-c3'; import { DbSchema } from './dbSchema'; import CypherCmdParserListener from './generated-parser/CypherCmdParserListener'; import { findCaret, isDefined } from './helpers'; import { parserWrapper } from './parserWrapper'; import { Neo4jFunction, Neo4jProcedure } from './types'; export const emptyResult: SignatureHelp = { signatures: [], activeSignature: undefined, activeParameter: undefined, }; export enum MethodType { function = 'function', procedure = 'procedure', } interface ParsedMethod { methodName: string; activeParameter: number; methodType: MethodType; } export function toSignatureInformation( curr: Neo4jFunction | Neo4jProcedure, ): SignatureInformation { const { name, argumentDescription, description } = curr; return SignatureInformation.create( name, description, ...argumentDescription.map((arg) => ({ label: arg.name, documentation: arg.description, })), ); } function toSignatureHelp( methodSignatures: Record<string, Neo4jFunction | Neo4jProcedure> = {}, parsedMethod: ParsedMethod, ): SignatureHelp { const methodName = parsedMethod.methodName; const method = methodSignatures[methodName]; const signatures = method ? [toSignatureInformation(method)] : []; const signatureHelp: SignatureHelp = { signatures: signatures, activeSignature: method ? 0 : undefined, activeParameter: parsedMethod.activeParameter, }; return signatureHelp; } class SignatureHelper extends CypherCmdParserListener { result: ParsedMethod; constructor(private tokens: Token[], private caretToken: Token) { super(); } exitExpression = (ctx: ExpressionContext) => { // If the caret is at ( if (this.caretToken.type === CypherParser.LPAREN) { /* We need to compute the next token that is not a space following the expression Example: in the case 'RETURN apoc.do.when (' the expression finishes before the ( and we would have a collection of spaces between apoc.do.when and the left parenthesis */ let index = ctx.stop.tokenIndex + 1; let nextToken = this.tokens[index]; while ( nextToken.type === CypherParser.SPACE && index < this.tokens.length ) { index++; nextToken = this.tokens[index]; } if ( this.caretToken.start === nextToken?.start && this.caretToken.stop === nextToken?.stop ) { const methodName = ctx.getText(); const numMethodArgs = 0; this.result = { methodName: methodName, activeParameter: numMethodArgs, methodType: MethodType.function, }; } } }; exitFunctionInvocation = (ctx: FunctionInvocationContext) => { if ( ctx.start.start <= this.caretToken.start && this.caretToken.stop <= ctx.stop.stop && // We need to check we have opened the left parenthesis // and we won't offer the signature help on just the name isDefined(ctx.LPAREN()) ) { const methodName = ctx.functionName().getText(); const previousArguments = ctx.COMMA_list().filter((arg) => { return arg.symbol.stop <= this.caretToken.start; }); this.result = { methodName: methodName, activeParameter: previousArguments.length, methodType: MethodType.function, }; } }; exitCallClause = (ctx: CallClauseContext) => { if ( ctx.start.start <= this.caretToken.start && this.caretToken.stop <= ctx.stop.stop && // We need to check we have opened the left parenthesis // and we won't offer the signature help on just the name isDefined(ctx.LPAREN()) ) { const methodName = ctx.procedureName().getText(); const previousArguments = ctx.COMMA_list().filter((arg) => { return arg.symbol.stop <= this.caretToken.start; }); this.result = { methodName: methodName, activeParameter: previousArguments.length, methodType: MethodType.procedure, }; } }; } export function signatureHelp( query: string, dbSchema: DbSchema, caretPosition: number = query.length, ): SignatureHelp { let result: SignatureHelp = emptyResult; /* We need the token immediately before the caret CALL something( ^ because in this case what gives us information on where we are in the procedure is not the space at the caret, but the opening ( */ const prevCaretPosition = caretPosition - 1; if (prevCaretPosition > 0) { const parserResult = parserWrapper.parse(query); const caret = findCaret(parserResult, prevCaretPosition); if (caret) { const statement = caret.statement; const signatureHelper = new SignatureHelper( statement.tokens, caret.token, ); ParseTreeWalker.DEFAULT.walk(signatureHelper, statement.ctx); const method = signatureHelper.result; if (method !== undefined) { if (method.methodType === MethodType.function) { result = toSignatureHelp(dbSchema.functions, method); } else { result = toSignatureHelp(dbSchema.procedures, method); } } } } return result; }