plaxtony
Version:
Static code analysis of SC2 Galaxy Script
305 lines • 16.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
require("mocha");
const fs = require("fs");
const path = require("path");
const chai_1 = require("chai");
const tc = require("../src/compiler/checker");
const checker_1 = require("../src/compiler/checker");
const helpers_1 = require("./helpers");
const utils_1 = require("../src/service/utils");
const binder_1 = require("../src/compiler/binder");
const vscode_uri_1 = require("vscode-uri");
function getSymbolAt(checker, sourceFile, line, character) {
const token = utils_1.getTokenAtPosition(utils_1.getPositionOfLineAndCharacter(sourceFile, line, character), sourceFile);
return checker.getSymbolAtLocation(token);
}
function getNodeTypeAt(checker, sourceFile, line, character) {
const token = utils_1.findPrecedingToken(utils_1.getPositionOfLineAndCharacter(sourceFile, line, character), sourceFile);
return checker.getTypeOfNode(token);
}
describe('Checker', () => {
describe('Resolve', () => {
const store = helpers_1.mockupStore();
const checker = new checker_1.TypeChecker(store);
context('typedef', () => {
let type;
let sourceFile;
before(() => {
const document = helpers_1.mockupTextDocument('type_checker', 'typedef.galaxy');
store.updateDocument(document);
sourceFile = store.documents.get(document.uri);
});
it('scalar', () => {
type = getNodeTypeAt(checker, sourceFile, 11, 5);
chai_1.assert.isOk(type.flags & 2097152 /* Typedef */);
type = getNodeTypeAt(checker, sourceFile, 11, 12);
chai_1.assert.isOk(type.flags & 32768 /* Complex */);
});
it('struct', () => {
type = getNodeTypeAt(checker, sourceFile, 12, 6);
chai_1.assert.isOk(type.flags & 2097152 /* Typedef */);
type = getNodeTypeAt(checker, sourceFile, 12, 13);
chai_1.assert.isOk(type.flags & 8192 /* Struct */);
});
it('struct deep', () => {
type = getNodeTypeAt(checker, sourceFile, 13, 6);
chai_1.assert.isOk(type.flags & 2097152 /* Typedef */);
type = getNodeTypeAt(checker, sourceFile, 13, 18);
chai_1.assert.isOk(type.flags & 8192 /* Struct */);
chai_1.assert.equal(type.symbol.escapedName, 'obj_t');
});
it('struct deep property', () => {
type = getNodeTypeAt(checker, sourceFile, 15, 15);
chai_1.assert.isOk(type.flags & 4 /* Integer */);
});
it('funcref', () => {
type = getNodeTypeAt(checker, sourceFile, 29, 6);
chai_1.assert.isOk(type instanceof tc.ReferenceType);
chai_1.assert.isOk(type.kind & 112 /* FuncrefKeyword */);
chai_1.assert.isOk(type.declaredType.symbol.escapedName, 'fprototype');
});
it('code validation', () => {
const diag = checker.checkSourceFile(sourceFile);
chai_1.assert.equal(diag.length, 0);
});
});
context('arrayref', () => {
let type;
let sourceFile;
before(() => {
const document = helpers_1.mockupTextDocument('type_checker', 'arrayref.galaxy');
store.updateDocument(document);
sourceFile = store.documents.get(document.uri);
});
it('ref []', () => {
type = getNodeTypeAt(checker, sourceFile, 7, 5);
chai_1.assert.isOk(type instanceof tc.ReferenceType);
chai_1.assert.isOk(type.kind & 110 /* ArrayrefKeyword */);
chai_1.assert.isOk(type.declaredType.flags & 65536 /* Array */);
chai_1.assert.isOk(type.declaredType.elementType.flags & 2 /* String */);
});
it('ref [][]', () => {
type = getNodeTypeAt(checker, sourceFile, 8, 5);
chai_1.assert.isOk(type instanceof tc.ReferenceType);
chai_1.assert.isOk(type.kind & 110 /* ArrayrefKeyword */);
chai_1.assert.isOk(type.declaredType.flags & 65536 /* Array */);
chai_1.assert.isOk(type.declaredType.elementType.flags & 65536 /* Array */);
});
it('typedef decl of [][]', () => {
type = getNodeTypeAt(checker, sourceFile, 13, 2);
chai_1.assert.isOk(type.flags & 2097152 /* Typedef */);
chai_1.assert.isOk(type.referencedType.flags & 65536 /* Array */);
chai_1.assert.isOk(type.referencedType.elementType.flags & 65536 /* Array */);
});
it('typedef var of [][]', () => {
type = getNodeTypeAt(checker, sourceFile, 13, 11);
chai_1.assert.isOk(type.flags & 65536 /* Array */);
});
});
it('struct property', () => {
let type;
const document = helpers_1.mockupTextDocument('type_checker', 'struct.galaxy');
store.updateDocument(document);
const sourceFile = store.documents.get(document.uri);
type = getNodeTypeAt(checker, sourceFile, 19, 21);
chai_1.assert.isAbove(type.flags & 2 /* String */, 0, '.');
type = getNodeTypeAt(checker, sourceFile, 20, 28);
chai_1.assert.isAbove(type.flags & 4 /* Integer */, 0, '..');
type = getNodeTypeAt(checker, sourceFile, 22, 27);
chai_1.assert.isAbove(type.flags & 2 /* String */, 0, '[].');
type = getNodeTypeAt(checker, sourceFile, 23, 37);
chai_1.assert.isAbove(type.flags & 32768 /* Complex */, 0, '[].[].');
chai_1.assert.equal(type.kind, 102 /* UnitKeyword */);
});
it('structref property', () => {
let type;
const document = helpers_1.mockupTextDocument('type_checker', 'ref.galaxy');
store.updateDocument(document);
const sourceFile = store.documents.get(document.uri);
type = getNodeTypeAt(checker, sourceFile, 9, 12);
chai_1.assert.isAbove(type.flags & 4 /* Integer */, 0);
});
it('funcref array', () => {
let type;
const document = helpers_1.mockupTextDocument('type_checker', 'funcref_arr.galaxy');
store.updateDocument(document);
const sourceFile = store.documents.get(document.uri);
type = getNodeTypeAt(checker, sourceFile, 2, 31);
chai_1.assert.isAbove(type.flags & 65536 /* Array */, 0);
chai_1.assert.isAbove(type.elementType.flags & 262144 /* Reference */, 0);
});
});
describe('Static', () => {
const documentStatic1 = helpers_1.mockupTextDocument('type_checker', 'static_conflict1.galaxy');
const documentStatic2 = helpers_1.mockupTextDocument('type_checker', 'static_conflict2.galaxy');
const store = helpers_1.mockupStore(documentStatic1, documentStatic2);
const sourceFileStatic1 = store.documents.get(documentStatic1.uri);
const sourceFileStatic2 = store.documents.get(documentStatic2.uri);
const checker = new checker_1.TypeChecker(store);
it('name non-conflict', () => {
let dg;
dg = checker.checkSourceFile(sourceFileStatic1, true);
chai_1.assert.equal(dg.length, 0, dg.join('\n'));
dg = checker.checkSourceFile(sourceFileStatic2, true);
chai_1.assert.equal(dg.length, 0, dg.join('\n'));
});
});
describe('Resolve symbol', () => {
const documentStruct = helpers_1.mockupTextDocument('type_checker', 'struct.galaxy');
const documentRef = helpers_1.mockupTextDocument('type_checker', 'ref.galaxy');
const store = helpers_1.mockupStore(documentStruct, documentRef);
const sourceFileStruct = store.documents.get(documentStruct.uri);
const sourceFileRef = store.documents.get(documentRef.uri);
const checker = new checker_1.TypeChecker(store);
it('variable', () => {
let symbol;
symbol = getSymbolAt(checker, sourceFileStruct, 14, 0);
chai_1.assert.isDefined(symbol);
});
it('[]variable', () => {
let symbol;
symbol = getSymbolAt(checker, sourceFileStruct, 15, 0);
chai_1.assert.isDefined(symbol);
});
it('structref', () => {
let symbol;
symbol = getSymbolAt(checker, sourceFileRef, 9, 11);
chai_1.assert.isDefined(symbol);
symbol = getSymbolAt(checker, sourceFileRef, 10, 11);
chai_1.assert.isDefined(symbol);
});
});
describe('Type', () => {
function validateDocument(src) {
const doc = helpers_1.mockupTextDocument('type_checker', 'diagnostics', src);
const store = helpers_1.mockupStore(doc);
const checker = new checker_1.TypeChecker(store);
const sourceFile = store.documents.get(doc.uri);
return checker.checkSourceFile(sourceFile);
}
it('string', () => {
const diagnostics = validateDocument('string.galaxy');
chai_1.assert.equal(diagnostics.length, 0);
});
it('complex', () => {
const diagnostics = validateDocument('complex.galaxy');
chai_1.assert.equal(diagnostics.length, 0);
});
it('loop', () => {
const diagnostics = validateDocument('loop.galaxy');
chai_1.assert.equal(diagnostics.length, 2);
chai_1.assert.equal(diagnostics[0].messageText, 'break statement used outside of loop boundaries');
chai_1.assert.equal(diagnostics[1].messageText, 'continue statement used outside of loop boundaries');
});
it('func_call', () => {
const diagnostics = validateDocument('func_call.galaxy');
chai_1.assert.equal(diagnostics.length, 3);
chai_1.assert.equal(diagnostics[0].messageText, 'Type \'string\' is not assignable to type \'integer\'');
chai_1.assert.equal(diagnostics[1].messageText, 'Type \'integer\' is not assignable to type \'string\'');
chai_1.assert.equal(diagnostics[2].messageText, 'Type \'null\' is not assignable to type \'integer\'');
});
it('struct', () => {
const diagnostics = validateDocument('struct.galaxy');
chai_1.assert.equal(diagnostics.length, 5);
chai_1.assert.equal(diagnostics[0].messageText, 'Can only pass basic types');
chai_1.assert.equal(diagnostics[1].messageText, 'Type \'struct1_t\' is not assignable to type \'struct1_t\'');
chai_1.assert.equal(diagnostics[2].messageText, 'Type \'struct2_t\' is not assignable to type \'struct1_t\'');
chai_1.assert.equal(diagnostics[3].messageText, 'Type \'struct1_t\' is not assignable to type \'struct1_t\'');
chai_1.assert.equal(diagnostics[4].messageText, 'Type \'struct2_t\' is not assignable to type \'structref<struct1_t>\'');
});
it('funcref', () => {
const diagnostics = validateDocument('funcref.galaxy');
chai_1.assert.equal(diagnostics.length, 3);
chai_1.assert.equal(diagnostics[0].messageText, 'Type \'fn_prototype_c\' is not assignable to type \'funcref<fn_prototype_t>\'');
chai_1.assert.equal(diagnostics[1].messageText, 'Expected 1 arguments, got 2');
chai_1.assert.equal(diagnostics[2].messageText, 'Type \'void\' is not assignable to type \'integer\'');
});
it('array', () => {
const diagnostics = validateDocument('array.galaxy');
chai_1.assert.isAtLeast(diagnostics.length, 1);
chai_1.assert.equal(diagnostics[0].messageText, 'Index access on non-array type');
});
it('typedef', () => {
const diagnostics = validateDocument('../typedef.galaxy');
chai_1.assert.equal(diagnostics.length, 0);
});
it('arrayref', () => {
const diagnostics = validateDocument('../arrayref.galaxy');
chai_1.assert.equal(diagnostics.length, 0);
});
});
describe('Diagnostics', () => {
function checkFile(filename) {
const document = helpers_1.mockupTextDocument('type_checker', filename);
const store = helpers_1.mockupStore(document);
const sourceFile = store.documents.get(document.uri);
const checker = new checker_1.TypeChecker(store);
binder_1.unbindSourceFile(sourceFile, store);
const diagnostics = checker.checkSourceFile(sourceFile, true);
const expectedDiags = new Map();
for (const [cLine, cInfo] of sourceFile.commentsLineMap) {
const m = sourceFile.text.substring(cInfo.pos, cInfo.end).trim().match(/^\/\/ \^ERR\:?\s?(.*)$/);
if (m) {
const dg = diagnostics.find((v) => v.line === (cLine - 1));
chai_1.assert.isDefined(dg, `(expected) ${cLine}: ${m[1] || 'ERR'}`);
expectedDiags.set(cLine - 1, dg);
}
}
if (diagnostics.length > 0 && expectedDiags.size > 0) {
for (const dg of diagnostics) {
chai_1.assert.isTrue(expectedDiags.has(dg.line), `(unexpected) ${dg.line + 1}: ${dg.messageText}`);
}
}
return diagnostics;
}
describe('Error', () => {
for (let filename of fs.readdirSync(path.resolve('tests/fixtures/type_checker/error'))) {
it(filename, () => {
chai_1.assert.isAtLeast(checkFile(path.join('error', filename)).length, 1);
});
}
});
describe('Pass', () => {
for (let filename of fs.readdirSync(path.resolve('tests/fixtures/type_checker/pass'))) {
it(filename, () => {
const dg = checkFile(path.join('pass', filename));
chai_1.assert.equal(dg.length, 0, 'Unexpected diagnostics');
});
}
});
});
describe('Diagnostics Recursive', () => {
const drFixturesDir = 'tests/fixtures/type_checker/diagnostics_recursive';
for (let nsName of fs.readdirSync(path.resolve(drFixturesDir))) {
for (let nsCurrentFilename of fs.readdirSync(path.resolve(drFixturesDir, nsName))) {
const matchedTestFile = nsCurrentFilename.match(/^(([\w]+)_(pass|fail))\.galaxy$/);
if (!matchedTestFile)
continue;
it(`${nsName}/${matchedTestFile[1]}`, async () => {
const store = await helpers_1.mockupStoreFromDirectory(path.resolve(drFixturesDir, nsName));
const checker = new checker_1.TypeChecker(store);
const sourceFile = store.documents.get(vscode_uri_1.default.file(path.resolve(drFixturesDir, nsName, nsCurrentFilename)).toString());
const result = checker.checkSourceFileRecursively(sourceFile);
switch (matchedTestFile[3]) {
case 'pass':
{
chai_1.assert.isTrue(result.success, Array.from(result.diagnostics.values()).flat().map(item => item.toString()).join('\n'));
break;
}
case 'fail':
{
chai_1.assert.isFalse(result.success);
break;
}
default:
{
throw new Error();
}
}
});
}
}
});
});
//# sourceMappingURL=typechecker.js.map