UNPKG

plaxtony

Version:

Static code analysis of SC2 Galaxy Script

1,288 lines 58.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeChecker = exports.TypedefType = exports.ArrayType = exports.ReferenceType = exports.FunctionType = exports.SignatureMeta = exports.StructType = exports.LiteralType = exports.ComplexType = exports.IntrinsicType = exports.UnknownType = exports.AbstractType = exports.getSymbolId = exports.getNodeId = void 0; const lsp = require("vscode-languageserver"); const path = require("path"); const vscode_uri_1 = require("vscode-uri"); const gt = require("./types"); const utils_1 = require("../compiler/utils"); const utils_2 = require("./utils"); const scanner_1 = require("./scanner"); const printer_1 = require("./printer"); const binder_1 = require("./binder"); const utils_3 = require("../service/utils"); let nextSymbolId = 1; let nextNodeId = 1; const printer = new printer_1.Printer(); function getNodeId(node) { if (!node.id) { node.id = nextNodeId; nextNodeId++; } return node.id; } exports.getNodeId = getNodeId; function getSymbolId(symbol) { if (!symbol.id) { symbol.id = nextSymbolId; nextSymbolId++; } return symbol.id; } exports.getSymbolId = getSymbolId; var CheckMode; (function (CheckMode) { CheckMode[CheckMode["Normal"] = 0] = "Normal"; CheckMode[CheckMode["SkipContextSensitive"] = 1] = "SkipContextSensitive"; })(CheckMode || (CheckMode = {})); class AbstractType { isValidBinaryOperation(operation, rightType) { return false; } isValidPrefixOperation(operation) { return false; } isValidPostfixOperation(operation) { return false; } getName() { return this.constructor.name; } } exports.AbstractType = AbstractType; class UnknownType extends AbstractType { constructor() { super(...arguments); this.flags = 1 /* Unknown */; } isAssignableTo(target) { return false; } isComparableTo(target) { return false; } isBoolExpression(negation) { return false; } } exports.UnknownType = UnknownType; class IntrinsicType extends AbstractType { constructor(flags, name) { super(); this.flags = flags; this.name = name; } isAssignableTo(target) { if (this === target) return true; if (target instanceof IntrinsicType) { if (target.flags & 32 /* Fixed */ && (this.flags & 4 /* Integer */ || this.flags & 8 /* Byte */)) return true; if (target.flags & 4 /* Integer */ && (this.flags & 8 /* Byte */)) return true; if (target.flags & 8 /* Byte */ && (this.flags & 4 /* Integer */)) return true; if (this.flags & 64 /* Boolean */ && target.flags & 64 /* Boolean */) return true; } else if (target instanceof ComplexType) { if (this.flags & 2 /* String */ && target.kind === 87 /* HandleKeyword */) return true; } if (this.flags & 4096 /* Null */ && target.flags & 128 /* Nullable */) return true; return false; } isComparableTo(target) { if (this === target) return true; if (target instanceof IntrinsicType) { if ((this.flags & 4 /* Integer */ || this.flags & 8 /* Byte */ || this.flags & 32 /* Fixed */) && (target.flags & 4 /* Integer */ || target.flags & 8 /* Byte */ || target.flags & 32 /* Fixed */)) { return true; } if (this.flags & 64 /* Boolean */ && target.flags & 64 /* Boolean */) return true; } if (this.flags & 4096 /* Null */ && target.flags & 128 /* Nullable */) return true; return false; } isBoolExpression(negation) { return true; } isValidBinaryOperation(operation, rightType) { if (this === rightType || (rightType instanceof LiteralType && rightType.value.kind === 3 /* StringLiteral */)) { switch (operation) { case 20 /* PlusToken */: if (this.flags & 2 /* String */) return true; } } if (this === rightType || (rightType.flags & 44 /* Numeric */) || (rightType instanceof LiteralType && rightType.value.kind === 2 /* NumericLiteral */)) { switch (operation) { case 20 /* PlusToken */: case 21 /* MinusToken */: case 22 /* AsteriskToken */: case 24 /* PercentToken */: case 23 /* SlashToken */: { if (this.flags & 44 /* Numeric */) { return true; } break; } case 29 /* AmpersandToken */: case 30 /* BarToken */: case 31 /* CaretToken */: { // must be same type (either byte and byte, or int and int) if ((this.flags & 12 /* IntLike */) === (rightType.flags & 12 /* IntLike */)) { return true; } break; } case 27 /* LessThanLessThanToken */: case 28 /* GreaterThanGreaterThanToken */: { // no strict type checking, can implictly cast from byte to int if ((this.flags & 12 /* IntLike */) && (rightType.flags & 12 /* IntLike */)) { return true; } break; } // case gt.SyntaxKind.BarBarToken: // case gt.SyntaxKind.AmpersandAmpersandToken: // { // return false; // break; // } } } return false; } isValidPrefixOperation(operation) { switch (operation) { case 20 /* PlusToken */: case 21 /* MinusToken */: if (this.flags & 4 /* Integer */ || this.flags & 8 /* Byte */ || this.flags & 32 /* Fixed */) return true; case 33 /* TildeToken */: if (this.flags & 4 /* Integer */ || this.flags & 8 /* Byte */) return true; case 32 /* ExclamationToken */: if (this.flags & 4 /* Integer */ || this.flags & 8 /* Byte */ || this.flags & 32 /* Fixed */ || this.flags & 64 /* Boolean */ || this.flags & 2 /* String */) return true; } } isValidPostfixOperation(operation) { return false; } getName() { return this.name; } } exports.IntrinsicType = IntrinsicType; class ComplexType extends AbstractType { constructor(kind) { super(); this.flags = 32768 /* Complex */; this.kind = kind; switch (this.kind) { case 84 /* ColorKeyword */: break; default: this.flags |= 128 /* Nullable */; break; } } get extendsHandle() { switch (this.kind) { case 77 /* AbilcmdKeyword */: case 78 /* ActorKeyword */: case 79 /* ActorscopeKeyword */: case 80 /* AifilterKeyword */: case 81 /* BankKeyword */: case 82 /* BitmaskKeyword */: case 83 /* CamerainfoKeyword */: case 85 /* DatetimeKeyword */: case 88 /* GenerichandleKeyword */: case 89 /* EffecthistoryKeyword */: case 90 /* MarkerKeyword */: case 91 /* OrderKeyword */: case 92 /* PlayergroupKeyword */: case 93 /* PointKeyword */: case 94 /* RegionKeyword */: case 96 /* SoundKeyword */: case 97 /* SoundlinkKeyword */: case 98 /* TextKeyword */: case 99 /* TimerKeyword */: case 100 /* TransmissionsourceKeyword */: case 103 /* UnitfilterKeyword */: case 104 /* UnitgroupKeyword */: case 105 /* UnitrefKeyword */: case 108 /* WaveinfoKeyword */: case 109 /* WavetargetKeyword */: return true; default: return false; } } isAssignableTo(target) { if (this === target) return true; if (target instanceof ComplexType) { if (target.kind === 87 /* HandleKeyword */) return this.extendsHandle; if (this.kind === 87 /* HandleKeyword */) return target.extendsHandle; } else { if (this.kind === 87 /* HandleKeyword */ && target.flags & 2 /* String */) return true; } // if (target.flags && gt.TypeFlags.Null && this.flags & gt.TypeFlags.Nullable) return true; return false; } isComparableTo(target) { if (this === target) return true; if (target.flags && 4096 /* Null */ && this.flags & 128 /* Nullable */) return true; return false; } isBoolExpression(negation) { if (negation) { switch (this.kind) { case 101 /* TriggerKeyword */: case 102 /* UnitKeyword */: return false; } } return true; } isValidBinaryOperation(operation, rightType) { if (this !== rightType) return false; switch (operation) { case 20 /* PlusToken */: { switch (this.kind) { case 98 /* TextKeyword */: case 93 /* PointKeyword */: return true; } break; } case 21 /* MinusToken */: { switch (this.kind) { case 93 /* PointKeyword */: return true; } break; } } return false; } isValidPrefixOperation(operation) { switch (operation) { case 32 /* ExclamationToken */: return this.isBoolExpression(true); } return false; } getName() { return scanner_1.tokenToString(this.kind); } } exports.ComplexType = ComplexType; class LiteralType extends AbstractType { constructor(flags, value) { super(); this.flags = flags; this.value = value; } isAssignableTo(target) { if (this === target) return true; if (this.value.kind === 3 /* StringLiteral */ && target.flags & 2 /* String */) { return true; } if (this.value.kind === 2 /* NumericLiteral */ && (target.flags & 8 /* Byte */ || target.flags & 4 /* Integer */ || target.flags & 32 /* Fixed */)) { if (this.value.text.indexOf('.') !== -1 && !(target.flags & 32 /* Fixed */)) { return false; } return true; } if (this.flags & 4096 /* Null */ && target.flags & 128 /* Nullable */) { return true; } return false; } isComparableTo(target) { if (this === target) return true; if (this.value.kind === 2 /* NumericLiteral */ && (target.flags & 8 /* Byte */ || target.flags & 4 /* Integer */ || target.flags & 32 /* Fixed */)) { return true; } if (target instanceof LiteralType && this.value.kind === target.value.kind) return true; return this.isAssignableTo(target); } isBoolExpression(negation) { return true; } isValidBinaryOperation(operation, rightType) { let type; if (this.value.kind === 2 /* NumericLiteral */) { if (this.flags & 32 /* Fixed */) { type = fixedType; } else { type = integerType; } } else if (this.value.kind === 3 /* StringLiteral */) { type = stringType; } else { return false; } return type.isValidBinaryOperation(operation, rightType); } isValidPrefixOperation(operation) { let type; if (this.value.kind === 2 /* NumericLiteral */) { if (this.flags & 32 /* Fixed */) { type = fixedType; } else { type = integerType; } } else if (this.value.kind === 3 /* StringLiteral */) { type = stringType; } else { return false; } return type.isValidPrefixOperation(operation); } getName() { let typeDesc = 'unknown'; if (this.flags & 2 /* String */) { typeDesc = 'string'; } else if (this.flags & 4 /* Integer */) { typeDesc = 'integer'; } else if (this.flags & 8 /* Byte */) { typeDesc = 'byte'; } else if (this.flags & 32 /* Fixed */) { typeDesc = 'fixed'; } else if (this.flags & 64 /* Boolean */) { typeDesc = 'bool'; } return `${this.value.text} [${typeDesc}]`; } } exports.LiteralType = LiteralType; class StructType extends AbstractType { constructor(symbol) { super(); this.flags = 8192 /* Struct */; this.symbol = symbol; } isAssignableTo(target) { if (target instanceof ReferenceType && target.kind === 111 /* StructrefKeyword */ && this.symbol === target.declaredType.symbol) { return true; } return false; } isComparableTo(target) { if (this === target) return true; if (target instanceof StructType && target.symbol === this.symbol) return true; return false; } isBoolExpression(negation) { return false; } getName() { return this.symbol.escapedName; } } exports.StructType = StructType; class SignatureMeta { constructor(returnType, args) { this.returnType = returnType; this.args = args; } match(other) { if (this.returnType !== other.returnType) return false; if (this.args.length !== other.args.length) return false; for (const [key, arg] of this.args.entries()) { if (this.args[key] !== arg) return false; } return true; } toString() { const params = []; for (const p of this.args) { params.push(p.getName()); } return `${this.returnType.getName()} (${params.join(',')})`; } } exports.SignatureMeta = SignatureMeta; class FunctionType extends AbstractType { constructor(symbol, signature) { super(); this.flags = 16384 /* Function */; this.symbol = symbol; this.signature = signature; } isAssignableTo(target) { if (target instanceof ReferenceType && target.kind === 112 /* FuncrefKeyword */) { if (!(target.declaredType.flags & 16384 /* Function */)) return false; return this.isComparableTo(target.declaredType); } return false; } isComparableTo(target) { if (target instanceof ReferenceType && target.kind === 112 /* FuncrefKeyword */) { if (!(target.declaredType.flags & 16384 /* Function */)) return false; return this.isComparableTo(target.declaredType); } if (target instanceof FunctionType) { if (this.symbol === target.symbol) return true; if (this.signature.match(target.signature)) return true; } return false; } isBoolExpression(negation) { return false; } getName() { return this.symbol.escapedName; } } exports.FunctionType = FunctionType; class ReferenceType extends AbstractType { constructor(kind, declaredType) { super(); this.flags = 262144 /* Reference */; this.kind = kind; this.declaredType = declaredType; if (this.kind === 112 /* FuncrefKeyword */) { this.flags |= 128 /* Nullable */; } } isAssignableTo(target) { if (target instanceof ReferenceType && this.kind === target.kind) { return this.declaredType.isAssignableTo(target); } return false; } isComparableTo(target) { if (target === nullType) return true; if (target instanceof ReferenceType && this.kind === target.kind) { return this.isAssignableTo(target); } if (target instanceof FunctionType) { if (this.kind === 112 /* FuncrefKeyword */) { return this.declaredType.signature.match(target.signature); } } return false; } isBoolExpression(negation) { return false; } getName() { return scanner_1.tokenToString(this.kind) + '<' + this.declaredType.getName() + '>'; } } exports.ReferenceType = ReferenceType; class ArrayType extends AbstractType { constructor(elementType) { super(); this.flags = 65536 /* Array */; this.elementType = elementType; } isAssignableTo(target) { if (target instanceof ReferenceType && target.kind === 110 /* ArrayrefKeyword */) { // multi-dimensional array if (this.elementType instanceof ArrayType) { return this.getName() === target.declaredType.getName(); } // intrinsic type / whatever else if (this.elementType === target.declaredType.elementType) return true; } return false; } isComparableTo(target) { return false; } isBoolExpression(negation) { return false; } getName() { return this.elementType.getName() + '[]'; } } exports.ArrayType = ArrayType; class TypedefType extends AbstractType { constructor(referencedType) { super(); this.flags = 2097152 /* Typedef */; this.referencedType = referencedType; } isAssignableTo(target) { return false; } isComparableTo(target) { return false; } isBoolExpression(negation) { return false; } getName() { return this.referencedType.getName(); } } exports.TypedefType = TypedefType; function createSymbol(flags, name) { const symbol = { flags: flags, escapedName: name, }; return symbol; } const unknownType = new UnknownType(); const nullType = new IntrinsicType(4096 /* Null */ | 128 /* Nullable */, "null"); const boolType = new IntrinsicType(64 /* Boolean */, "bool"); const trueType = new IntrinsicType(64 /* Boolean */, "true"); const falseType = new IntrinsicType(64 /* Boolean */, "false"); const stringType = new IntrinsicType(2 /* String */ | 128 /* Nullable */, "string"); const integerType = new IntrinsicType(4 /* Integer */, "integer"); const byteType = new IntrinsicType(8 /* Byte */, "byte"); const fixedType = new IntrinsicType(32 /* Fixed */, "fixed"); const voidType = new IntrinsicType(2048 /* Void */, "void"); const complexTypes = generateComplexTypes(); function generateComplexTypes() { const map = new Map(); for (let i = 77 /* FirstComplexType */; i <= 109 /* LastComplexType */; i++) { const ckind = i; map.set(ckind, new ComplexType(ckind)); } return map; } class TypeChecker { constructor(store) { this.nodeLinks = []; this.diagnostics = new Map(); this.currentSymbolContainer = null; this.currentSymbolReferences = new Map(); this.currentDocuments = new Map(); this.store = store; this.currentDocuments = this.store.documents; } report(location, msg, category = gt.DiagnosticCategory.Error, tags) { const d = utils_2.createDiagnosticForNode(location, category, msg, tags); const c = this.diagnostics.get(d.file.fileName); if (c) c.push(d); } getNodeLinks(node) { const nodeId = getNodeId(node); return this.nodeLinks[nodeId] || (this.nodeLinks[nodeId] = { flags: 0 }); } checkTypeAssignableTo(source, target, node) { // TODO: error when using local var as reference if (source === unknownType || target === unknownType) return; if (!source.isAssignableTo(target)) { this.report(node, 'Type \'' + source.getName() + '\' is not assignable to type \'' + target.getName() + '\''); } } checkTypeComparableTo(source, target, node) { if (source === unknownType || target === unknownType) return; if (!source.isComparableTo(target)) { this.report(node, 'Type \'' + source.getName() + '\' is not comparable to type \'' + target.getName() + '\''); } } checkTypeBoolExpression(source, negation, node) { if (source === unknownType) return; if (!source.isBoolExpression(negation)) { this.report(node, 'Type \'' + source.getName() + '\' can not be used as boolean expression'); } } getTypeFromArrayTypeNode(node) { const links = this.getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = new ArrayType(this.getTypeFromTypeNode(node.elementType)); } return links.resolvedType; } getTypeFromMappedTypeNode(node) { const links = this.getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = new ReferenceType(node.returnType.kind, node.typeArguments.length ? this.getTypeFromTypeNode(node.typeArguments[0]) : unknownType); } return links.resolvedType; } resolveMappedReference(type) { if (type.flags & 262144 /* Reference */) { type = type.declaredType; } return type; } getPropertyOfType(type, name) { if (type && type.flags & 8192 /* Struct */) { if (type.symbol.members.has(name)) { return type.symbol.members.get(name); } } } getDeclaredTypeOfStruct(symbol) { // TODO: persist in map<symbol,type> return new StructType(symbol); } getSignatureOfFunction(fnDecl) { return new SignatureMeta(this.getTypeFromTypeNode(fnDecl.type), fnDecl.parameters.map((param) => { return this.getTypeFromTypeNode(param.type); })); } getTypeOfFunction(symbol) { const fnDecl = symbol.declarations[0]; // TODO: persist in map<symbol,type> return new FunctionType(symbol, this.getSignatureOfFunction(fnDecl)); } getTypeOfTypedef(symbol) { const refType = this.getTypeFromTypeNode(symbol.declarations[0].type); return new TypedefType(refType); } getDeclaredTypeOfSymbol(symbol) { if (symbol.flags & (64 /* Struct */)) { return this.getDeclaredTypeOfStruct(symbol); } else if (symbol.flags & (14 /* Variable */)) { return this.getTypeOfSymbol(symbol); } else if (symbol.flags & (32 /* Function */)) { // should we introduce SignatureType that describes fn declaration and return it instead? return this.getTypeOfFunction(symbol); } else if (symbol.flags & (128 /* Typedef */)) { return this.getTypeFromTypeNode(symbol.declarations[0].type); } return unknownType; } getTypeFromTypeNode(node) { switch (node.kind) { case 76 /* StringKeyword */: return stringType; case 74 /* IntKeyword */: return integerType; case 72 /* ByteKeyword */: return byteType; case 75 /* FixedKeyword */: return fixedType; case 71 /* BoolKeyword */: return boolType; case 106 /* VoidKeyword */: return voidType; case 69 /* NullKeyword */: return nullType; // case gt.SyntaxKind.LiteralType: // return getTypeFromLiteralTypeNode(<LiteralTypeNode>node); case 117 /* ArrayType */: return this.getTypeFromArrayTypeNode(node); case 116 /* MappedType */: return this.getTypeFromMappedTypeNode(node); case 113 /* Identifier */: const symbol = this.getSymbolAtLocation(node); if (symbol) { return this.getDeclaredTypeOfSymbol(symbol); } else { return unknownType; } default: if (utils_1.isComplexTypeKind(node.kind)) { return complexTypes.get(node.kind); } return unknownType; } } getTypeOfSymbol(symbol) { if ((symbol.flags & 14 /* Variable */) || (symbol.flags & 16 /* Property */)) { return this.getTypeOfVariableOrParameterOrProperty(symbol); } else if (symbol.flags & (32 /* Function */)) { return this.getTypeOfFunction(symbol); } else if (symbol.flags & (128 /* Typedef */)) { return this.getTypeOfTypedef(symbol); } return unknownType; } getTypeOfVariableOrParameterOrProperty(symbol) { return this.getTypeFromTypeNode(symbol.declarations[0].type); } getTypeOfNode(node, followRef = false) { // TODO: // if (isPartOfTypeNode(node)) { // return this.getTypeFromTypeNode(<TypeNode>node); // } if (utils_2.isPartOfExpression(node)) { let type = this.getRegularTypeOfExpression(node); if (followRef) { type = this.resolveMappedReference(type); } return type; } return unknownType; } getRegularTypeOfExpression(expr) { return this.getTypeOfExpression(expr); } getTypeOfExpression(node, cache) { return this.checkExpression(node); } clear() { this.diagnostics.clear(); this.currentSymbolReferences.clear(); } checkSourceFile(sourceFile, bindSymbols = false) { this.clear(); this.diagnostics.set(sourceFile.fileName, []); this.currentDocuments = this.store.documents; if (bindSymbols) { binder_1.unbindSourceFile(sourceFile, { resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this) }); this.currentSymbolContainer = binder_1.declareSymbol(sourceFile, { resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this) }, null); } sourceFile.statements.forEach(this.checkSourceElement.bind(this)); if (bindSymbols) { this.checkForUnusedLocalDefinitions(this.currentSymbolContainer); } return Array.from(this.diagnostics.values()).pop(); } checkSourceFileRecursivelyWorker(sourceFile) { binder_1.unbindSourceFile(sourceFile, { resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this) }); this.currentSymbolContainer = binder_1.declareSymbol(sourceFile, { resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this) }, null); this.diagnostics.set(sourceFile.fileName, []); this.currentDocuments.set(sourceFile.fileName, sourceFile); for (const statement of sourceFile.statements) { if (statement.kind === 136 /* IncludeStatement */) { const qsFile = this.checkIncludeStatement(statement); if (qsFile && !this.currentDocuments.has(qsFile.fileName)) { const currentSymbolContainer = this.currentSymbolContainer; this.checkSourceFileRecursivelyWorker(qsFile); this.currentSymbolContainer = currentSymbolContainer; } continue; } this.checkSourceElement(statement); } } checkSourceFileRecursively(sourceFile) { this.clear(); this.currentDocuments = new Map(); if (this.store.s2workspace) { const coreMod = this.store.s2workspace.allArchives.find((archive) => archive.name === 'mods/core.sc2mod'); if (coreMod) { const corePath = path.join(coreMod.directory, 'base.sc2data', 'TriggerLibs'); const nativeScripts = [ path.join(corePath, 'natives_missing.galaxy'), path.join(corePath, 'natives.galaxy'), ]; for (const fpath of nativeScripts) { const qFile = this.store.documents.get(vscode_uri_1.default.file(fpath).toString()); if (qFile) { this.checkSourceFileRecursivelyWorker(qFile); } } } } this.checkSourceFileRecursivelyWorker(sourceFile); this.checkForIdentifierDefinitions(); this.checkForUnusedLocalDefinitions(this.currentSymbolContainer); return { success: Array.from(this.diagnostics.values()).findIndex((value, index) => value.length > 0) === -1, diagnostics: this.diagnostics, sourceFiles: this.currentDocuments, }; } checkForIdentifierDefinitions() { for (const [symbol, symRef] of this.currentSymbolReferences) { if ((symbol.flags & 32 /* Function */)) { if ((symbol.flags & 2048 /* Native */)) continue; if (!symRef.size) continue; if (symbol.valueDeclaration) continue; for (const identifier of symRef) { this.report(identifier, `Referenced function '${identifier.name}' hasn't been defined.`); } } } } checkIsSymbolDeclarationDefined(symbol) { var _a, _b; const symRef = this.currentSymbolReferences.get(symbol); if (symbol.flags & 14 /* Variable */) { if (symRef && symRef.size > 1) return; if (((symbol.flags & 8 /* GlobalVariable */) && !(symbol.flags & 1024 /* Static */)) || ((symbol.flags & 2 /* LocalVariable */) && !(((_a = symbol.parent) === null || _a === void 0 ? void 0 : _a.flags) & 32 /* Function */)) || ((symbol.flags & 4 /* FunctionParameter */) && (((_b = symbol.parent) === null || _b === void 0 ? void 0 : _b.flags) & 32 /* Function */))) { return; } for (const nodeDecl of symbol.declarations) { const nameDecl = nodeDecl; if (nameDecl) { this.report(nameDecl.name, `'${symbol.escapedName}' variable is defined but never used.`, gt.DiagnosticCategory.Hint, [lsp.DiagnosticTag.Unnecessary]); } } } } checkForUnusedLocalDefinitions(rootSym) { if (!rootSym) return; for (const currSym of this.currentSymbolContainer.members.values()) { this.checkIsSymbolDeclarationDefined(currSym); if (currSym.flags & 32 /* Function */ && currSym.valueDeclaration) { for (const childSym of currSym.members.values()) { this.checkIsSymbolDeclarationDefined(childSym); // this.checkForUnusedLocalDefinitions(childSym); } } } } checkSourceElement(node) { let prevSymbolContainer = null; if (this.currentSymbolContainer && utils_2.isDeclarationKind(node.kind)) { prevSymbolContainer = this.currentSymbolContainer; this.currentSymbolContainer = binder_1.declareSymbol(node, { resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this) }, prevSymbolContainer); if (this.currentSymbolContainer.declarations.length > 1) { let previousDeclaration; if (node.kind === 142 /* FunctionDeclaration */) { for (const pd of this.currentSymbolContainer.declarations) { if (pd === node) continue; if (pd.kind === 142 /* FunctionDeclaration */ && (!pd.body || !node.body)) { continue; } previousDeclaration = pd; break; } } else if (node.kind === 143 /* ParameterDeclaration */) { for (const pd of this.currentSymbolContainer.declarations) { if (pd === node) continue; if (pd.parent !== node.parent) continue; previousDeclaration = pd; break; } } else { previousDeclaration = this.currentSymbolContainer.declarations[this.currentSymbolContainer.declarations.length - 2]; } if (previousDeclaration) { const prevSourceFile = utils_2.findAncestorByKind(previousDeclaration, 127 /* SourceFile */); const prevPos = utils_3.getLineAndCharacterOfPosition(prevSourceFile, previousDeclaration.pos); this.report(node.name, `Symbol redeclared, previous declaration in ${prevSourceFile.fileName}:${prevPos.line + 1},${prevPos.character + 1}`); } } } switch (node.kind) { case 136 /* IncludeStatement */: this.checkIncludeStatement(node); break; case 145 /* TypedefDeclaration */: this.checkTypedefDeclaration(node); break; case 128 /* Block */: this.checkBlock(node); break; case 142 /* FunctionDeclaration */: this.checkFunction(node); break; case 141 /* VariableDeclaration */: this.checkVariableDeclaration(node); break; case 144 /* PropertyDeclaration */: this.checkPropertyDeclaration(node); break; case 143 /* ParameterDeclaration */: this.checkParameterDeclaration(node); break; case 140 /* StructDeclaration */: this.checkStructDeclaration(node); break; case 138 /* ExpressionStatement */: this.checkExpressionStatement(node); break; case 129 /* IfStatement */: this.checkIfStatement(node); break; case 132 /* ForStatement */: this.checkForStatement(node); break; case 131 /* WhileStatement */: case 130 /* DoStatement */: this.checkWhileStatement(node); break; case 133 /* BreakStatement */: case 134 /* ContinueStatement */: this.checkBreakOrContinueStatement(node); break; case 137 /* ReturnStatement */: this.checkReturnStatement(node); break; } if (prevSymbolContainer) { this.currentSymbolContainer = prevSymbolContainer; } } checkIncludeStatement(node) { let path = node.path.value.toLowerCase(); let segments = path.split('.'); if (segments.length > 1 && segments[segments.length - 1] !== 'galaxy') { this.report(node.path, `Dot in a script name is not allowed, unless path ends with ".galaxy"`, gt.DiagnosticCategory.Warning); } else { path = path.replace(/\.galaxy$/, ''); } const qsMap = this.store.qualifiedDocuments.get(path); if (!qsMap) { this.report(node.path, `Given filename couldn't be matched`); return; } const qsFile = Array.from(qsMap.values())[0]; const currCourceFile = utils_2.findAncestorByKind(node, 127 /* SourceFile */); if (currCourceFile === qsFile) { this.report(node, `Self-include`, gt.DiagnosticCategory.Warning); return; } return qsFile; } checkTypedefDeclaration(node) { this.checkDeclarationType(node.type); this.checkIdentifier(node.name); } checkDeclarationType(node) { switch (node.kind) { case 116 /* MappedType */: return this.checkMappedType(node); case 117 /* ArrayType */: return this.checkArrayType(node); case 113 /* Identifier */: return this.checkIdentifier(node, false, false)[1]; } } checkFunction(node) { this.checkDeclarationType(node.type); this.checkIdentifier(node.name, false, false); this.checkTypeNoRefs(node.type); const currentSignature = this.getSignatureOfFunction(node); for (const prevDecl of node.symbol.declarations) { if (node === prevDecl) continue; if (prevDecl.kind !== 142 /* FunctionDeclaration */) break; const previousSignature = this.getSignatureOfFunction(prevDecl); if (!currentSignature.match(previousSignature)) { this.report(node, `Function signature doesn't match it's previous declaration '${previousSignature.toString()}'`); break; } } node.parameters.forEach(this.checkSourceElement.bind(this)); if (node.body && node.body.kind === 128 /* Block */) { const rtype = this.getTypeFromTypeNode(node.type); this.checkBlock(node.body); if (!(rtype.flags & 2048 /* Void */) && !node.body.hasReturn) { this.report(node.name, 'Expected return statement'); } } } checkLocalDeclaration(node, symbol) { const sourceFile = utils_2.findAncestorByKind(node, 127 /* SourceFile */); if ((symbol.flags & 6 /* FunctionScopedVariable */) || (symbol.flags & 16 /* Property */)) { const globalSym = this.resolveName(sourceFile, node.name.name, true); if (globalSym) { if ((globalSym.flags & 32 /* Function */) || (globalSym.flags & 128 /* Typedef */)) { const orgSourceFile = utils_2.findAncestorByKind(globalSym.declarations[0], 127 /* SourceFile */); const orgPos = utils_3.getLineAndCharacterOfPosition(orgSourceFile, globalSym.declarations[0].pos); this.report(node.name, [ `Name '${node.name.name}' redefined. Already in use in global scope.`, `See: ${path.basename(vscode_uri_1.default.parse(orgSourceFile.fileName).fsPath)}:${orgPos.line + 1},${orgPos.character + 1}`, ].join(' ')); } } } } checkParameterDeclaration(node) { const declType = this.checkDeclarationType(node.type); const [symbol, symType] = this.checkIdentifier(node.name); // const isNative = (<gt.FunctionDeclaration>node.parent).modifiers.some((value) => value.kind === gt.SyntaxKind.NativeKeyword); // if ((<gt.FunctionDeclaration>node.parent).body || isNative) { // } if (symbol) { this.checkLocalDeclaration(node, symbol); } const type = this.getTypeFromTypeNode(node.type); if (type instanceof StructType || type instanceof FunctionType) { this.report(node.type, 'Can only pass basic types'); } } checkVariableDeclaration(node) { var _a; const declType = this.checkDeclarationType(node.type); const [symbol, symType] = this.checkIdentifier(node.name); if (node.initializer) { const varType = this.getTypeFromTypeNode(node.type); const exprType = this.checkExpression(node.initializer); this.checkTypeAssignableTo(exprType, varType, node.initializer); } const isConstant = (_a = node.modifiers) === null || _a === void 0 ? void 0 : _a.some((value) => value.kind === 53 /* ConstKeyword */); if (isConstant && declType instanceof TypedefType) { this.report(node.type, `Constant variables cannot reference Typedefs`); } if (symbol) { this.checkLocalDeclaration(node, symbol); } if (symbol.flags & 8 /* GlobalVariable */) { this.checkTypeNoRefs(node.type); } } checkTypeNoRefs(node) { if (node.kind === 116 /* MappedType */) { switch (node.returnType.kind) { case 111 /* StructrefKeyword */: case 110 /* ArrayrefKeyword */: { this.report(node, `Can not use arrayref/structref as a global, a field, or a return value (only as a local or a parameter).`); break; } } } } checkPropertyDeclaration(node) { const declType = this.checkDeclarationType(node.type); const [symbol, symType] = this.checkIdentifier(node.name); if (symbol) { this.checkLocalDeclaration(node, symbol); } this.checkTypeNoRefs(node.type); } checkStructDeclaration(node) { node.members.forEach(this.checkSourceElement.bind(this)); } checkIfStatement(node) { const exprType = this.checkExpression(node.expression); this.checkTypeBoolExpression(exprType, false, node.expression); this.checkSourceElement(node.thenStatement); if (node.elseStatement) { this.checkSourceElement(node.elseStatement); node.hasReturn = node.thenStatement.hasReturn && node.elseStatement.hasReturn; } } checkForStatement(node) { if (node.initializer) { this.checkExpression(node.initializer); } if (node.condition) { const exprType = this.checkExpression(node.condition); this.checkTypeBoolExpression(exprType, false, node.condition); } if (node.incrementor) { this.checkExpression(node.incrementor); } this.checkSourceElement(node.statement); } checkWhileStatement(node) { if (node.expression) { const exprType = this.checkExpression(node.expression); this.checkTypeBoolExpression(exprType, false, node.expression); } this.checkSourceElement(node.statement); } checkBreakOrContinueStatement(node) { const loop = utils_2.findAncestor(node, (parent) => { switch (parent.kind) { case 132 /* ForStatement */: case 131 /* WhileStatement */: case 130 /* DoStatement */: return true; } return false; }); if (!loop) { this.report(node, `${scanner_1.tokenToString(node.syntaxTokens[0].kind)} statement used outside of loop boundaries`); } } checkReturnStatement(node) { const fn = utils_2.findAncestorByKind(node, 142 /* FunctionDeclaration */); const rtype = this.getTypeFromTypeNode(fn.type); if (rtype.flags & 2048 /* Void */ && node.expression) { this.report(node, 'Unexpected value returned for void function'); } else if (!(rtype.flags & 2048 /* Void */) && !node.expression) { this.report(node, 'Expected a return value'); } if (node.expression) { const exprType = this.checkExpression(node.expression); this.checkTypeAssignableTo(exprType, rtype, node.expression); } } checkArrayType(node) { this.checkExpression(node.size); this.checkDeclarationType(node.elementType); } checkMappedType(node) { if (!utils_2.isReferenceKeywordKind(node.returnType.kind)) { this.report(node.returnType, 'Invalid keyword for reference type provided - use funcref, arrayref or structref'); } if (node.typeArguments.length !== 1) { this.report(node, 'Expected exactly 1 argument'); } node.typeArguments.forEach(this.checkDeclarationType.bind(this)); if (node.typeArguments.length > 0) { const type = this.getTypeFromMappedTypeNode(node); let invalid = false; switch (type.kind) { case 111 /* StructrefKeyword */: invalid = !(type.declaredType.flags & 8192 /* Struct */); break; case 112 /* FuncrefKeyword */: invalid = !(type.declaredType.flags & 16384 /* Function */); break; case 110 /* ArrayrefKeyword */: invalid = !(type.declaredType.flags & 65536 /* Array */); break; } if (invalid) { this.report(node, 'Type \'' + type.declaredType.getName() + '\' is not a valid reference for \'' + scanner_1.tokenToString(node.returnType.kind) + '\''); } } } checkBlock(node) { let returnFound = false; let returnFoundExplict = false; node.statements.forEach((child) => { this.checkSourceElement(child); switch (child.kind) { case 137 /* ReturnStatement */: returnFoundExplict = returnFound = true; break; case 129 /* IfStatement */: // if (returnFoundExplict === true) break; returnFound = child.hasReturn; break; } }); node.hasReturn = returnFound; } checkExpressionStatement(node) { this.checkExpression(node.expression); } checkExpression(node, checkMode) { return this.checkExpressionWorker(node, checkMode); } checkExpressionWorker(node, checkMode) { switch (node.kind) { case 113 /* Identifier */: return this.checkIdentifier(node)[1]; case 69 /* NullKeyword */: return nullType; case 3 /* StringLiteral */: case 2 /* NumericLiteral */: case 67 /* TrueKeyword */: case 68 /* FalseKeyword */: return this.checkLiteralExpression(node); case 120 /* PropertyAccessExpression */: return this.checkPropertyAccessExpression(node); case 119 /* ElementAccessExpression */: return this.checkIndexedAccess(node); case 121 /* CallExpression */: return this.checkCallExpression(node); case 126 /* ParenthesizedExpression */: return this.checkParenthesizedExpression(node, checkMode); case 122 /* PrefixUnaryExpression */: return this.checkPrefixUnaryExpression(node); case 123 /* PostfixUnaryExpression */: return this.checkPostfixUnaryExpression(node); case 124 /* BinaryExpression */: return this.checkBinaryExpression(node, checkMode); } return unknownType; } checkLiteralExpression(node) { switch (node.kind) { case 3 /* StringLiteral */: return new LiteralType(256 /* StringLiteral */ | 2 /* String */ | 128 /* Nullable */, node); case 2 /* NumericLiteral */: if (node.text.indexOf('.') !== -1) { return new LiteralType(512 /* NumericLiteral */ | 32 /* Fixed */, node); } else { return new LiteralType(512 /* NumericLiteral */ | 4 /* Integer */, node); } case 67 /* TrueKeyword */: return trueType; case 68 /* FalseKeyword */: return falseType; } } checkBinaryExpression(node, checkMode) { const leftType = this.checkExpression(node.left); const rightType = this.checkExpression(node.right); if (utils_2.isAssignmentOperator(node.operatorToken.kind)) { this.checkTypeAssignableTo(rightType, leftType, node.right); } else if (utils_2.isComparisonOperator(node.operatorToken.kind)) { this.checkTypeComparableTo(rightType, leftType, node.right); return boolType; } else if (node.operatorToken.kind === 35 /* BarBarToken */ || node.operatorToken.kind === 34 /* AmpersandAmpersandToken */) { this.checkTypeAssignableTo(leftType, boolType, node.left); this.checkTypeAssignableTo(rightType, boolType, node.right); return boolType; } else { const valid = leftType.isValidBinaryOperation(node.operatorToken.kind, rightType); if (!valid) { this.report(node, [ `Binary expression '${scanner_1.tokenToString(node.operatorToken.kind)}' not supported between '${leftType.getName()}' and '${rightType.getName()}'.`, ].join(' ')); } switc