plaxtony
Version:
Static code analysis of SC2 Galaxy Script
134 lines (111 loc) • 4.94 kB
text/typescript
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;
}
}