UNPKG

plaxtony

Version:

Static code analysis of SC2 Galaxy Script

134 lines (111 loc) 4.94 kB
import * as gt from '../compiler/types'; import * as lsp from 'vscode-languageserver'; import { SyntaxKind, Symbol, Node, SourceFile, CallExpression, Identifier, FunctionDeclaration, Expression } from '../compiler/types'; import { findAncestor, getSourceFileOfNode } from '../compiler/utils'; import { Printer } from '../compiler/printer'; import { TypeChecker } from '../compiler/checker'; import { AbstractProvider } from './provider'; import { getTokenAtPosition } from './utils'; import { getDocumentationOfSymbol } from './s2meta'; export class SignaturesProvider extends AbstractProvider { private printer: Printer = new Printer(); private evaluateActiveParameter(callExpr: CallExpression, position: number): number | null { let activeParam: number | null = null; let prevArg: gt.Node; callExpr.arguments.some((argument: Expression, index: number, args) => { if (argument.pos <= position) { activeParam = index; prevArg = argument; // exit early when confirmed it is in bounds // in other case keep going to acomodate whitespaces if (argument.end >= position) { return true; } // offset is after last existing arg, it means the current argument wasn't yet parsed if (index === args.length - 1 && position > args[index].end) { activeParam++; } } // offset is before bounds of next param node, yet we got here - we must be in whitespace else if (prevArg) { activeParam = index; return true; } return; }); return activeParam; } public getSignatureOfFunction(functionSymbol: gt.Symbol): lsp.SignatureInformation { const functionDeclaration = <FunctionDeclaration>functionSymbol.declarations[0]; let code = this.printer.printNode(Object.assign({}, functionDeclaration, {body: null})).trim(); // strip ; if (code.substr(code.length - 1, 1) === ';') { code = code.substr(0, code.length - 1); } const signatureInfo = <lsp.SignatureInformation>{ label: code, parameters: [], }; const docStr = getDocumentationOfSymbol(this.store, functionSymbol, false); if (docStr) { signatureInfo.documentation = { kind: lsp.MarkupKind.Markdown, value: docStr, }; } const argsDoc = this.store.s2metadata ? this.store.s2metadata.getFunctionArgumentsDoc(functionSymbol.escapedName) : null; for (const [index, paramDeclaration] of functionDeclaration.parameters.entries()) { const paramInfo = <lsp.ParameterInformation>{ label: this.printer.printNode(paramDeclaration), }; if (argsDoc && argsDoc[index]) { paramInfo.documentation = { kind: lsp.MarkupKind.Markdown, value: argsDoc[index], }; } signatureInfo.parameters.push(paramInfo); } return signatureInfo; } public getSignatureAt(uri: string, position: number): lsp.SignatureHelp { const signatureHelp = <lsp.SignatureHelp>{ signatures: [], activeSignature: null, activeParameter: null, }; const sourceFile = this.store.documents.get(uri); if (!sourceFile) return; const currentToken = getTokenAtPosition(position, sourceFile, true); if (!currentToken) { return null; } let node: Node = currentToken.parent; const callNode = <CallExpression>findAncestor(node, (element: Node): boolean => { if (element.kind !== SyntaxKind.CallExpression) { return false; } // we don't want to provide signature for left side of CallExpression if ((<CallExpression>element).arguments.pos > position) { return false; } // skip if goes over range - we must've hit CloseParenToken if (element.end <= position) { return false; } return true; }) if (!callNode) { return null; } const checker = new TypeChecker(this.store); const type = checker.getTypeOfNode(callNode.expression, true); if (type.flags & gt.TypeFlags.Function) { const signatureInfo = this.getSignatureOfFunction((<gt.FunctionType>type).symbol); signatureHelp.activeSignature = 0; signatureHelp.activeParameter = this.evaluateActiveParameter(callNode, position); signatureHelp.signatures.push(signatureInfo); } return signatureHelp; } }