plaxtony
Version:
Static code analysis of SC2 Galaxy Script
558 lines • 24.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompletionsProvider = exports.CompletionFunctionExpand = void 0;
const checker_1 = require("../compiler/checker");
const provider_1 = require("./provider");
const scanner_1 = require("../compiler/scanner");
const utils_1 = require("../compiler/utils");
const utils_2 = require("./utils");
const printer_1 = require("../compiler/printer");
const lsp = require("vscode-languageserver");
const s2meta_1 = require("./s2meta");
function isInComment(sourceFile, pos) {
const comment = sourceFile.commentsLineMap.get(utils_2.getLineAndCharacterOfPosition(sourceFile, pos).line);
return comment && pos >= comment.pos;
}
var CompletionFunctionExpand;
(function (CompletionFunctionExpand) {
CompletionFunctionExpand[CompletionFunctionExpand["None"] = 0] = "None";
CompletionFunctionExpand[CompletionFunctionExpand["Parenthesis"] = 1] = "Parenthesis";
CompletionFunctionExpand[CompletionFunctionExpand["ArgumentsNull"] = 2] = "ArgumentsNull";
CompletionFunctionExpand[CompletionFunctionExpand["ArgumentsDefault"] = 3] = "ArgumentsDefault";
})(CompletionFunctionExpand = exports.CompletionFunctionExpand || (exports.CompletionFunctionExpand = {}));
;
;
var CompletionItemDataFlags;
(function (CompletionItemDataFlags) {
CompletionItemDataFlags[CompletionItemDataFlags["CanExpand"] = 2] = "CanExpand";
CompletionItemDataFlags[CompletionItemDataFlags["CanAppendSemicolon"] = 4] = "CanAppendSemicolon";
})(CompletionItemDataFlags || (CompletionItemDataFlags = {}));
;
;
class CompletionsProvider extends provider_1.AbstractProvider {
constructor() {
super();
this.printer = new printer_1.Printer();
this.config = {
functionExpand: 0 /* None */,
};
}
expandFunctionArguments(decl) {
let args = [];
let funcElement;
if (this.store.s2metadata && this.config.functionExpand === 3 /* ArgumentsDefault */) {
funcElement = this.store.s2metadata.findElementByName(decl.name.name);
}
function isStringLikeParam(param) {
switch (param.valueType) {
case 'gamelink':
case 'string':
return true;
default:
return false;
}
}
for (const [key, param] of decl.parameters.entries()) {
let paramElement;
if (funcElement) {
const index = key - (funcElement.flags & 16 /* Event */ ? 1 : 0);
if (index >= 0) {
const paramDef = funcElement.getParameters()[index];
if (paramDef.default) {
paramElement = paramDef.default.resolve();
}
}
}
if (!paramElement) {
if (param.type.kind === 74 /* IntKeyword */ || param.type.kind === 72 /* ByteKeyword */) {
args.push(`0`);
}
else if (param.type.kind === 75 /* FixedKeyword */) {
args.push(`0.0`);
}
else if (param.type.kind === 76 /* StringKeyword */) {
args.push(`""`);
}
else if (param.type.kind === 71 /* BoolKeyword */) {
args.push(`false`);
}
else {
args.push(`null`);
}
}
else {
if (paramElement.value) {
if (isStringLikeParam(paramElement)) {
args.push(`"${paramElement.value}"`);
}
else {
args.push(paramElement.value);
}
}
else if (paramElement.preset) {
const presetVal = paramElement.preset.resolve();
if (presetVal.value) {
args.push(presetVal.value);
}
else {
const presetDef = this.store.s2metadata.findPresetDef(presetVal);
if (presetDef) {
args.push(this.store.s2metadata.getNameOfPresetValue(presetDef, presetVal));
}
else {
args.push(this.store.s2metadata.getElementSymbolName(presetVal));
}
}
}
else if (paramElement.valueElement) {
args.push(this.store.s2metadata.getElementSymbolName(paramElement.valueElement.resolve().values[0].resolve()));
}
else if (paramElement.functionCall) {
const fcallDef = paramElement.functionCall.resolve().functionDef.resolve();
const fcallSymbol = this.store.resolveGlobalSymbol(this.store.s2metadata.getElementSymbolName(fcallDef));
args.push(this.store.s2metadata.getElementSymbolName(fcallDef)
+ '(' + this.expandFunctionArguments(fcallSymbol.declarations[0]).join(', ') + ')');
}
else {
args.push('null');
}
}
}
return args;
}
buildFromSymbolDecl(symbol) {
const node = (symbol.declarations[0]);
if (node.name === undefined) {
return;
}
const item = {
label: node.name.name,
};
switch (node.kind) {
case 140 /* StructDeclaration */:
item.kind = lsp.CompletionItemKind.Class;
break;
case 142 /* FunctionDeclaration */:
item.kind = lsp.CompletionItemKind.Function;
break;
case 141 /* VariableDeclaration */:
case 143 /* ParameterDeclaration */:
item.kind = lsp.CompletionItemKind.Variable;
break;
case 144 /* PropertyDeclaration */:
item.kind = lsp.CompletionItemKind.Property;
break;
case 145 /* TypedefDeclaration */:
item.kind = lsp.CompletionItemKind.Interface;
break;
default:
item.kind = lsp.CompletionItemKind.Text;
break;
}
return item;
}
buildFromSymbolMembers(parentSymbol, query) {
const completions = [];
for (const symbol of parentSymbol.members.values()) {
if (!query || utils_2.fuzzysearch(query, symbol.escapedName)) {
const item = this.buildFromSymbolDecl(symbol);
item.data = {
parentSymbol: parentSymbol.escapedName,
};
if (item) {
completions.push(item);
}
}
}
return completions;
}
provideTriggerHandlers() {
let completions = [];
for (const document of this.store.documents.values()) {
for (const [name, symbol] of document.symbol.members) {
if (symbol.declarations[0].kind !== 142 /* FunctionDeclaration */)
continue;
const funcDecl = symbol.declarations[0];
if (funcDecl.type.kind !== 71 /* BoolKeyword */)
continue;
if (funcDecl.parameters.length !== 2)
continue;
if (funcDecl.parameters[0].type.kind !== 71 /* BoolKeyword */)
continue;
if (funcDecl.parameters[1].type.kind !== 71 /* BoolKeyword */)
continue;
const item = this.buildFromSymbolDecl(symbol);
completions.push(item);
}
}
return completions;
}
provideGameLinks(gameType) {
const links = this.store.s2metadata.getGameLinkItem(gameType);
let completions = [];
for (const item of links) {
completions.push({
label: item.id,
data: {
elementType: 'gamelink',
gameType: gameType,
},
kind: lsp.CompletionItemKind.Value,
});
}
return completions;
}
provideIncludes(query) {
const completions = new Map();
for (const [relativeName, qsMap] of this.store.qualifiedDocuments) {
if (query && !relativeName.startsWith(query.toLowerCase()))
continue;
const qsFiles = Array.from(qsMap.values()).filter(v => v.s2meta);
if (!qsFiles.length)
continue;
const itemPart = qsFiles[0].s2meta.docName.substr(query.length).split('/');
let cpItem = completions.get(itemPart[0]);
if (!cpItem) {
cpItem = lsp.CompletionItem.create(itemPart[0]);
cpItem.documentation = '';
if (itemPart.length > 1) {
cpItem.kind = lsp.CompletionItemKind.Folder;
}
else {
cpItem.kind = lsp.CompletionItemKind.File;
cpItem.detail = qsFiles.filter(v => v.s2meta.file.archive).map(v => `${v.s2meta.file.archive.name}`).join(' | ');
cpItem.documentation = qsFiles.map(v => {
return ((v.s2meta.file.archive ? `${v.s2meta.file.archive.name}/` : '') +
`${v.s2meta.file.relativePath}`);
}).join('\n');
}
completions.set(cpItem.label, cpItem);
}
if (cpItem.kind === lsp.CompletionItemKind.Folder) {
const nDocs = qsFiles.filter(v => v.s2meta.file.archive).map(v => `${v.s2meta.file.archive.name}`);
if (nDocs.length) {
cpItem.documentation = Array.from(new Set(cpItem.documentation.toString().split('\n').concat(nDocs))).join('\n');
}
}
}
return {
items: Array.from(completions.values()),
isIncomplete: false,
};
}
getCompletionsAt(uri, position, context) {
var _a;
let completions = [];
const sourceFile = this.store.documents.get(uri);
if (!sourceFile)
return;
if (isInComment(sourceFile, position))
return;
let currentToken = utils_2.findPrecedingToken(position, sourceFile);
// const adjacentToken = getAdjacentToken(position, sourceFile);
let filterBooleanKeywords = false;
// query
let query = null;
const processedSymbols = new Map();
if (currentToken && currentToken.pos <= position && currentToken.end >= position) {
const offset = position - currentToken.pos;
if (currentToken.kind === 113 /* Identifier */) {
query = currentToken.name.substr(0, offset);
}
else if (utils_1.isKeywordKind(currentToken.kind)) {
query = scanner_1.tokenToString(currentToken.kind).substr(0, offset);
}
}
// trigger handlers
if (currentToken && currentToken.kind === 3 /* StringLiteral */ && currentToken.parent.kind === 121 /* CallExpression */) {
const callExpr = currentToken.parent;
if (callExpr.expression.kind === 113 /* Identifier */ &&
["TriggerCreate", "TriggerFind"].find(x => x === (callExpr.expression).name)) {
return {
items: this.provideTriggerHandlers(),
isIncomplete: false,
};
}
}
// include
if (currentToken && currentToken.kind === 3 /* StringLiteral */ && currentToken.pos <= position && currentToken.end >= position && currentToken.parent.kind === 136 /* IncludeStatement */) {
const inclStmt = currentToken.parent;
const offset = position - currentToken.pos;
query = currentToken.text.substr(1, offset - 1).replace(/(\/*)[^\/]+$/, '$1');
if (currentToken.text.match(/[^"]$/) || currentToken.end !== position) {
return this.provideIncludes(query);
}
}
if (context && context.triggerCharacter === '/')
return;
// presets
if (this.store.s2metadata) {
const elementType = this.store.s2metadata.getElementTypeOfNode(currentToken);
// TODO: support <any> gamelink
if (elementType &&
elementType.type === 'gamelink' &&
currentToken.kind === 3 /* StringLiteral */ &&
elementType.gameType) {
return {
items: this.provideGameLinks(elementType.gameType),
isIncomplete: false,
};
}
if (elementType && elementType.type === 'preset') {
const tPreset = elementType.typeElement.resolve();
let matchingPresetCompletions = 0;
let totalPresetCompletions = 0;
switch (tPreset.baseType) {
default: {
let i = 0;
for (const name of this.store.s2metadata.getConstantNamesOfPreset(tPreset)) {
const symbol = this.store.resolveGlobalSymbol(name);
if (symbol) {
const citem = this.buildFromSymbolDecl(symbol);
if (!citem)
continue;
citem.preselect = true;
citem.kind = lsp.CompletionItemKind.Enum;
citem.label = citem.label;
completions.push(citem);
processedSymbols.set(name, symbol);
totalPresetCompletions++;
if (!query || utils_2.fuzzysearch(query, citem.label)) {
matchingPresetCompletions++;
}
}
}
break;
}
}
if (((context === null || context === void 0 ? void 0 : context.triggerKind) === lsp.CompletionTriggerKind.Invoked &&
(!query || totalPresetCompletions === matchingPresetCompletions) &&
matchingPresetCompletions > 0) ||
((context === null || context === void 0 ? void 0 : context.triggerKind) === lsp.CompletionTriggerKind.Invoked &&
(((_a = query === null || query === void 0 ? void 0 : query.length) !== null && _a !== void 0 ? _a : 0) < 2 || totalPresetCompletions === matchingPresetCompletions) &&
matchingPresetCompletions > 0)) {
return {
items: completions,
isIncomplete: true,
};
}
else if (((context === null || context === void 0 ? void 0 : context.triggerKind) === lsp.CompletionTriggerKind.TriggerForIncompleteCompletions &&
(query === null || query === void 0 ? void 0 : query.length) === 1 &&
matchingPresetCompletions > 0)) {
return {
items: completions,
isIncomplete: false,
};
}
}
}
// exit early for str and num literals
if (currentToken && (currentToken.kind === 3 /* StringLiteral */ ||
currentToken.kind === 2 /* NumericLiteral */)) {
return {
items: completions,
isIncomplete: false,
};
}
// properties
if (currentToken) {
if ((currentToken.kind === 10 /* DotToken */ || currentToken.kind === 113 /* Identifier */) &&
(currentToken.parent.kind === 120 /* PropertyAccessExpression */ && currentToken.parent.expression !== currentToken)) {
const checker = new checker_1.TypeChecker(this.store);
currentToken = currentToken.parent.expression;
const type = checker.getTypeOfNode(currentToken, true);
if (type.flags & 8192 /* Struct */) {
return {
items: this.buildFromSymbolMembers(type.symbol),
isIncomplete: false,
};
}
}
}
// local variables
if (currentToken) {
const currentContext = utils_1.findAncestor(currentToken, (element) => {
return element.kind === 142 /* FunctionDeclaration */;
});
if (currentContext) {
completions = completions.concat(this.buildFromSymbolMembers(currentContext.symbol, query));
}
}
// can append semicolon
let completionFlags = 2 /* CanExpand */;
if (currentToken) {
if (currentToken.kind === 113 /* Identifier */ && position < currentToken.end) {
completionFlags &= ~2 /* CanExpand */;
}
if (currentToken.parent) {
switch (currentToken.parent.kind) {
case 138 /* ExpressionStatement */: {
if (position >= currentToken.end) {
completionFlags |= 4 /* CanAppendSemicolon */;
}
else {
completionFlags |= currentToken.parent.syntaxTokens.findIndex(value => value.kind === 11 /* SemicolonToken */) === -1 ? 4 /* CanAppendSemicolon */ : 0;
}
break;
}
case 128 /* Block */:
case 127 /* SourceFile */:
{
if (position >= currentToken.end) {
completionFlags |= 4 /* CanAppendSemicolon */;
}
break;
}
case 142 /* FunctionDeclaration */:
{
completionFlags &= ~2 /* CanExpand */;
break;
}
}
}
}
let cpCount = 0;
let isIncomplete = false;
const cpLimit = 9000;
outer: for (const document of this.store.documents.values()) {
for (const [name, symbol] of document.symbol.members) {
if ((symbol.flags & 1024 /* Static */) && document.fileName !== uri)
continue;
if (processedSymbols.has(name))
continue;
if (!query || utils_2.fuzzysearch(query, name)) {
processedSymbols.set(name, symbol);
const citem = this.buildFromSymbolDecl(symbol);
citem.data = {
flags: completionFlags,
};
completions.push(citem);
if (++cpCount >= cpLimit) {
if ((context === null || context === void 0 ? void 0 : context.triggerKind) !== lsp.CompletionTriggerKind.TriggerForIncompleteCompletions) {
isIncomplete = true;
}
break outer;
}
}
}
}
// keywords
for (let i = 50 /* FirstKeyword */; i <= 112 /* LastKeyword */; i++) {
const name = scanner_1.tokenToString(i);
if (isIncomplete || !query || utils_2.fuzzysearch(query, name)) {
completions.push({
label: name,
kind: lsp.CompletionItemKind.Keyword
});
}
}
return {
items: completions,
isIncomplete: isIncomplete,
};
}
resolveCompletion(completion) {
switch (completion.kind) {
case lsp.CompletionItemKind.Folder:
case lsp.CompletionItemKind.File:
{
return completion;
}
default:
{
break;
}
}
let symbol;
let parentSymbolName;
const customData = completion.data || {};
if (customData.elementType === 'gamelink') {
const localizedName = this.store.s2metadata.getGameLinkLocalizedName(customData.gameType, completion.label, true);
completion.detail = '';
if (localizedName) {
completion.detail += `"${localizedName}"`;
}
const linkDeclarations = Array.from(this.store.s2metadata.getGameLinkItem(customData.gameType, completion.label));
if (linkDeclarations.length > 0) {
const decl = linkDeclarations[0];
completion.detail += ` [${decl.ctype}]`;
completion.documentation = {
kind: lsp.MarkupKind.Markdown,
value: linkDeclarations.map(x => {
const details = this.store.s2metadata.getGameLinkDetails(x);
if (!details)
return '`unknown`';
return `\`${details.archive.name}\` :: ${details.relativePath}`;
}).join('\\\n'),
};
}
return completion;
}
if (customData.parentSymbol) {
parentSymbolName = customData.parentSymbol;
}
for (const sourceFile of this.store.documents.values()) {
if (parentSymbolName) {
symbol = sourceFile.symbol.members.get(parentSymbolName);
if (!symbol)
continue;
}
else {
symbol = sourceFile.symbol;
}
symbol = symbol.members.get(completion.label);
if (symbol)
break;
}
if (this.config.functionExpand !== 0 /* None */ &&
completion.kind === lsp.CompletionItemKind.Function &&
customData.flags && customData.flags & 2 /* CanExpand */) {
const decl = symbol.declarations[0];
let funcArgs = [];
// TODO: support funcrefs expansion
if (decl.kind === 142 /* FunctionDeclaration */ && this.config.functionExpand !== 1 /* Parenthesis */) {
funcArgs = this.expandFunctionArguments(decl);
}
if (funcArgs) {
completion.insertTextFormat = lsp.InsertTextFormat.Snippet;
funcArgs = funcArgs.map((item, index) => {
return `\${${index + 1}:${item}}`;
});
completion.insertText = completion.label + '(' + funcArgs.join(', ') + ')';
}
else {
completion.insertTextFormat = lsp.InsertTextFormat.PlainText;
completion.insertText = completion.label + '($1)';
}
if (customData.flags && customData.flags & 4 /* CanAppendSemicolon */) {
completion.insertText += ';';
}
completion.insertText += '$0';
}
if (symbol) {
completion.documentation = s2meta_1.getDocumentationOfSymbol(this.store, symbol, false);
let node = symbol.declarations[0];
switch (node.kind) {
case 142 /* FunctionDeclaration */:
node = Object.create(node);
node.body = null;
// pass through
case 141 /* VariableDeclaration */:
case 143 /* ParameterDeclaration */:
case 144 /* PropertyDeclaration */:
case 145 /* TypedefDeclaration */:
completion.detail = this.printer.printNode(node);
break;
}
}
if (completion.documentation) {
completion.documentation = {
kind: lsp.MarkupKind.Markdown,
value: completion.documentation,
};
}
return completion;
}
}
exports.CompletionsProvider = CompletionsProvider;
//# sourceMappingURL=completions.js.map