plaxtony
Version:
Static code analysis of SC2 Galaxy Script
1,288 lines • 58.9 kB
JavaScript
"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