UNPKG

plaxtony

Version:

Static code analysis of SC2 Galaxy Script

558 lines 24.7 kB
"use strict"; 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