Skip to content

Commit

Permalink
add verify script command. that attempts to analyze the file respecti…
Browse files Browse the repository at this point in the history
…ng order of declarations
  • Loading branch information
Talv committed Sep 17, 2018
1 parent ee3570d commit 3293e3f
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 22 deletions.
8 changes: 4 additions & 4 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Parser } from './parser';
import * as gt from './types';
import { SyntaxKind, SourceFile, Node, Symbol, SymbolTable, NamedDeclaration } from './types';
import { forEachChild, isNamedDeclarationKind, isDeclarationKind, isContainerKind, getSourceFileOfNode } from './utils';
import { Store } from '../service/store';
import { IStoreSymbols } from '../service/store';
// import { SignatureMeta, TypeChecker } from './checker';

export function getDeclarationName(node: Node): string {
Expand Down Expand Up @@ -55,7 +55,7 @@ export function getDeclarationName(node: Node): string {
// return result;
// }

export function declareSymbol(node: gt.Declaration, store: Store, parentSymbol?: Symbol): Symbol {
export function declareSymbol(node: gt.Declaration, store: IStoreSymbols, parentSymbol?: Symbol): Symbol {
let scopedSymbolTable: Symbol;
let nodeSymbol: Symbol;
let name: string;
Expand Down Expand Up @@ -143,7 +143,7 @@ export function declareSymbol(node: gt.Declaration, store: Store, parentSymbol?:
return nodeSymbol;
}

export function bindSourceFile(sourceFile: SourceFile, store: Store) {
export function bindSourceFile(sourceFile: SourceFile, store: IStoreSymbols) {
let currentScope: gt.Declaration;
let currentContainer: gt.NamedDeclaration;

Expand Down Expand Up @@ -182,7 +182,7 @@ export function bindSourceFile(sourceFile: SourceFile, store: Store) {
}
}

export function unbindSourceFile(sourceFile: SourceFile, store: Store) {
export function unbindSourceFile(sourceFile: SourceFile, store: IStoreSymbols) {
function unbindSymbol(parentSymbol: Symbol) {
for (const symbol of parentSymbol.members.values()) {
symbol.declarations = symbol.declarations.filter((decl) => {
Expand Down
91 changes: 83 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as util from 'util';
import * as path from 'path';
import URI from 'vscode-uri';
import * as gt from './types';
import { isComplexTypeKind } from '../compiler/utils';
import { isDeclarationKind, forEachChild, isPartOfExpression, isRightSideOfPropertyAccess, findAncestor, createDiagnosticForNode, isAssignmentOperator, isComparisonOperator, isReferenceKeywordKind, findAncestorByKind } from './utils';
import { Store } from '../service/store';
import { tokenToString } from './scanner';
import { Printer } from './printer';
import { declareSymbol } from './binder';
import { declareSymbol, unbindSourceFile } from './binder';
import { getLineAndCharacterOfPosition } from '../service/utils';

let nextSymbolId = 1;
Expand Down Expand Up @@ -614,15 +616,19 @@ function generateComplexTypes() {
export class TypeChecker {
private store: Store;
private nodeLinks: gt.NodeLinks[] = [];
private diagnostics: gt.Diagnostic[] = [];
private diagnostics = new Map<string, gt.Diagnostic[]>();
private currentSymbolContainer: gt.Symbol = null;
private currentDocuments = new Map<string, gt.SourceFile>();

constructor(store: Store) {
this.store = store;
this.currentDocuments = this.store.documents;
}

private report(location: gt.Node, msg: string, category: gt.DiagnosticCategory = gt.DiagnosticCategory.Error): void {
this.diagnostics.push(createDiagnosticForNode(location, category, msg));
const d = createDiagnosticForNode(location, category, msg);
const c = this.diagnostics.get(d.file.fileName);
if (c) c.push(d);
}

private getNodeLinks(node: gt.Node): gt.NodeLinks {
Expand Down Expand Up @@ -809,21 +815,67 @@ export class TypeChecker {
}

public checkSourceFile(sourceFile: gt.SourceFile, bindSymbols = false) {
this.diagnostics = [];
this.diagnostics.clear();
this.diagnostics.set(sourceFile.fileName, []);
this.currentDocuments = this.store.documents;
if (bindSymbols) {
this.currentSymbolContainer = declareSymbol(sourceFile, this.store, null);
this.currentSymbolContainer = declareSymbol(sourceFile, {resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this)}, null);
}
sourceFile.statements.forEach(this.checkSourceElement.bind(this));
return Array.from(this.diagnostics.values()).pop();
}

protected checkSourceFileRecursivelyWorker(sourceFile: gt.SourceFile) {
unbindSourceFile(sourceFile, {resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this)});
this.currentSymbolContainer = 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 === gt.SyntaxKind.IncludeStatement) {
const docUri = this.checkIncludeStatement(<gt.IncludeStatement>statement);
if (docUri && !this.currentDocuments.has(docUri)) {
const inclFile = this.store.documents.get(docUri);
if (inclFile) {
const currentSymbolContainer = this.currentSymbolContainer;
this.checkSourceFileRecursivelyWorker(inclFile);
this.currentSymbolContainer = currentSymbolContainer;
}
}
continue;
}
this.checkSourceElement(statement);
}
return this.diagnostics;
}

public checkSourceFileRecursively(sourceFile: gt.SourceFile) {
this.diagnostics.clear();
this.currentDocuments = new Map<string, gt.SourceFile>();

if (this.store.s2workspace) {
const coreMod = this.store.s2workspace.allArchives.find((archive) => archive.name === 'mods/core.sc2mod');
if (coreMod) {
const fsp = path.join(coreMod.directory, 'base.sc2data', 'TriggerLibs', 'natives_missing.galaxy');
const smNatives = this.store.documents.get(URI.file(fsp).toString());
if (smNatives) {
this.checkSourceFileRecursivelyWorker(smNatives);
}
}
}

this.checkSourceFileRecursivelyWorker(sourceFile);

return {
success: Array.from(this.diagnostics.values()).findIndex((value, index) => value.length > 0) === -1,
diagnostics: this.diagnostics,
};
}

private checkSourceElement(node: gt.Node) {
let prevSymbolContainer = null;
if (this.currentSymbolContainer && isDeclarationKind(node.kind)) {
prevSymbolContainer = this.currentSymbolContainer;
this.currentSymbolContainer = declareSymbol(node, this.store, prevSymbolContainer);
this.currentSymbolContainer = declareSymbol(node, {resolveGlobalSymbol: this.resolveGlobalSymbol.bind(this)}, prevSymbolContainer);
if (this.currentSymbolContainer.declarations.length > 1) {
let previousDeclaration: gt.Declaration;
if (node.kind === gt.SyntaxKind.FunctionDeclaration) {
Expand Down Expand Up @@ -859,6 +911,9 @@ export class TypeChecker {
}

switch (node.kind) {
case gt.SyntaxKind.IncludeStatement:
this.checkIncludeStatement(<gt.IncludeStatement>node);
break;
case gt.SyntaxKind.Block:
this.checkBlock(<gt.Block>node);
break;
Expand Down Expand Up @@ -911,6 +966,22 @@ export class TypeChecker {
}
}

private checkIncludeStatement(node: gt.IncludeStatement) {
const path = node.path.value.replace(/\.galaxy$/i, '').toLowerCase();
for (const docUri of this.store.documents.keys()) {
const meta = this.store.getDocumentMeta(docUri);
if (!meta.relativeName) continue;
if (meta.relativeName.toLowerCase() != path) continue;
const sourceFile = <gt.SourceFile>findAncestorByKind(node, gt.SyntaxKind.SourceFile);
if (this.store.documents.get(docUri) === sourceFile) {
this.report(node.path, `Self-include`, gt.DiagnosticCategory.Warning);
return;
}
return docUri;
}
this.report(node.path, `Given filename couldn't be matched`);
}

private checkFunction(node: gt.FunctionDeclaration) {
this.checkSourceElement(node.type);

Expand Down Expand Up @@ -1283,7 +1354,11 @@ export class TypeChecker {
}
}

for (const document of this.store.documents.values()) {
return this.resolveGlobalSymbol(name);
}

private resolveGlobalSymbol(name: string) {
for (const document of this.currentDocuments.values()) {
const symbol = document.symbol.members.get(name);
if (symbol) {
return symbol;
Expand Down
38 changes: 30 additions & 8 deletions src/service/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type DiagnosticsCallback = (a: string) => void;
export class DiagnosticsProvider extends AbstractProvider {
private reporter?: DiagnosticsCallback;

private translateDiagnostics(sourceFile: gt.SourceFile, origDiagnostics: gt.Diagnostic[]): lsp.Diagnostic[] {
private translateDiagnostics(sourceFile: gt.SourceFile, origDiagnostics: gt.Diagnostic[], source: string): lsp.Diagnostic[] {
let lspDiagnostics: lsp.Diagnostic[] = [];

for (let dg of origDiagnostics) {
Expand All @@ -22,6 +22,7 @@ export class DiagnosticsProvider extends AbstractProvider {
end: getLineAndCharacterOfPosition(sourceFile, dg.start + dg.length)
},
message: dg.messageText,
source,
});
}

Expand All @@ -39,17 +40,38 @@ export class DiagnosticsProvider extends AbstractProvider {
}

public provideDiagnostics(uri: string): lsp.Diagnostic[] {
let diagnostics: Diagnostic[] = [];
const sourceFile = this.store.documents.get(uri);

const parseDiag = sourceFile.parseDiagnostics;
diagnostics = diagnostics.concat(parseDiag);
let parseDiag = sourceFile.parseDiagnostics;
let checkerDiag = sourceFile.additionalSyntacticDiagnostics;

const checkerDiag = sourceFile.additionalSyntacticDiagnostics;
diagnostics = diagnostics.concat(checkerDiag);
this.console.log(`${uri} - ${parseDiag.length} - ${checkerDiag.length}`);

this.console.info(`${uri} - ${parseDiag.length} - ${checkerDiag.length}`);
if (parseDiag.length > 100) parseDiag = parseDiag.slice(0, 100);
if (checkerDiag.length > 100) checkerDiag = checkerDiag.slice(0, 100);

return this.translateDiagnostics(sourceFile, diagnostics);
return [].concat(
this.translateDiagnostics(sourceFile, parseDiag, 'parser'),
this.translateDiagnostics(sourceFile, checkerDiag, 'typecheck')
);
}

public checkFileRecursively(documentUri: string) {
const checker = new TypeChecker(this.store);
const sourceFile = this.store.documents.get(documentUri);
const result = checker.checkSourceFileRecursively(sourceFile);

const ld: lsp.PublishDiagnosticsParams[] = [];
for (const [itUri, itDg] of result.diagnostics) {
ld.push({
uri: itUri,
diagnostics: this.translateDiagnostics(this.store.documents.get(itUri), itDg, 'verify'),
})
}

return {
success: result.success,
diagnostics: ld,
};
}
}
12 changes: 12 additions & 0 deletions src/service/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ export class Server {
this.connection.onRenameRequest(this.onRenameRequest.bind(this));
this.connection.onPrepareRename(this.onPrepareRename.bind(this));

this.connection.onRequest('document/checkRecursively', this.onDiagnoseDocumentRecursively.bind(this));

return this.connection;
}

Expand Down Expand Up @@ -612,6 +614,16 @@ export class Server {
await this.flushDocument(params.textDocument.uri);
return this.renameProvider.prefetchLocations();
}

@wrapRequest()
private async onDiagnoseDocumentRecursively(params: lsp.TextDocumentIdentifier) {
await this.flushDocument(params.uri);
const dg = this.diagnosticsProvider.checkFileRecursively(params.uri);
for (const item of dg.diagnostics) {
this.connection.sendDiagnostics(item);
}
return dg.success;
}
}

export function createServer(): lsp.IConnection {
Expand Down
6 changes: 5 additions & 1 deletion src/service/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ export interface SourceFileMeta {
archive?: SC2Archive;
}

export class Store {
export interface IStoreSymbols {
resolveGlobalSymbol(name: string): gt.Symbol | undefined;
}

export class Store implements IStoreSymbols {
private parser = new Parser();
public rootPath?: string;
public documents = new Map<string, SourceFile>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
int ggg;

include "sub_a.galaxy"

int bbb;

void InitMap()
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
void test_sub_a_stuff1()
{
ggg = 1;
}
1 change: 1 addition & 0 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function mockupStoreFromDirectory(directory: string) {
return new Promise<Store>((resolve, reject) => {
try {
const store = new Store();
store.rootPath = directory;
const ws = new WorkspaceWatcher(directory);
ws.onDidOpen((ev) => {
store.updateDocument(ev.document);
Expand Down
13 changes: 12 additions & 1 deletion tests/typechecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import * as path from 'path';
import { assert } from 'chai';
import * as tc from '../src/compiler/checker';
import { TypeChecker } from '../src/compiler/checker';
import { mockupStoreDocument, mockupStore, mockupSourceFile, mockupTextDocument } from './helpers';
import { mockupStoreDocument, mockupStore, mockupSourceFile, mockupTextDocument, mockupStoreFromDirectory } from './helpers';
import { getPositionOfLineAndCharacter, findPrecedingToken, getTokenAtPosition } from '../src/service/utils';
import * as lsp from 'vscode-languageserver';
import * as gt from './../src/compiler/types';
import { unbindSourceFile } from '../src/compiler/binder';
import URI from 'vscode-uri';

function getSymbolAt(checker: TypeChecker, sourceFile: gt.SourceFile, line: number, character: number): gt.Symbol | undefined {
const token = getTokenAtPosition(getPositionOfLineAndCharacter(sourceFile, line, character), sourceFile);
Expand Down Expand Up @@ -330,4 +331,14 @@ describe('Checker', () => {
}
});
});

describe('Diagnostics Recursive', () => {
it('simple', async () => {
const store = await mockupStoreFromDirectory(path.resolve('tests/fixtures/type_checker/diagnostics_recursive/simple'));
const checker = new TypeChecker(store);
const sourceFile = store.documents.get(URI.file(path.join(store.rootPath, 'MapScript.galaxy')).toString());
const diagnostics = checker.checkSourceFileRecursively(sourceFile);
assert.isTrue(diagnostics.success);
});
});
});

0 comments on commit 3293e3f

Please sign in to comment.