mirror of
https://github.com/brucificus/vb6-antlr4-typescript.git
synced 2025-12-16 20:07:03 +03:00
7
tests/ScanAndValidateParseTrees.spec.ts
Normal file
7
tests/ScanAndValidateParseTrees.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { File } from './support/infrastructure/File';
|
||||
import scanAndValidateParseTrees from './support/scanAndValidateParseTrees';
|
||||
|
||||
const INPUT_DIRECTORY: File = new File(__dirname, "../proleap-vb6/src/test/resources");
|
||||
const DIRECTORIES_EXCLUDED: File[] = [ new File(INPUT_DIRECTORY.getAbsolutePath(), "io/proleap/vb6/asg") ];
|
||||
|
||||
scanAndValidateParseTrees(INPUT_DIRECTORY, (directory) => Boolean(DIRECTORIES_EXCLUDED.find(d => d.equals(directory))));
|
||||
8
tests/support/ThrowingErrorListener.ts
Normal file
8
tests/support/ThrowingErrorListener.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { VbParserError } from './VbParserError';
|
||||
import { ANTLRErrorListener, Recognizer, RecognitionException } from "antlr4ts";
|
||||
|
||||
export class ThrowingErrorListener<TSymbol> implements ANTLRErrorListener<TSymbol> {
|
||||
syntaxError<T extends TSymbol>(recognizer: Recognizer<T, any>, offendingSymbol: T | undefined, line: number, charPositionInLine: number, msg: string, e: RecognitionException | undefined): void {
|
||||
throw new VbParserError("syntax error in line " + line + ":" + charPositionInLine + " " + msg);
|
||||
}
|
||||
}
|
||||
82
tests/support/VbParseTestRunner.ts
Normal file
82
tests/support/VbParseTestRunner.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* VB6 parse runner for Jest tests.
|
||||
*/
|
||||
|
||||
import { createLogger, ILogger } from './infrastructure/Logger';
|
||||
import { File } from './infrastructure/File';
|
||||
import { readFileSync } from 'fs';
|
||||
const readFileToString = (path: string): string => readFileSync(path, {encoding: 'utf8'});
|
||||
import { StartRuleContext, VisualBasic6Parser, VisualBasic6Lexer } from '../../index';
|
||||
import { ANTLRInputStream, CommonTokenStream } from "antlr4ts";
|
||||
import { cleanFileTree } from './cleanFileTree';
|
||||
import { IVbParserParams, VbParserParams } from './VbParserParams';
|
||||
import { ThrowingErrorListener } from './ThrowingErrorListener';
|
||||
import { isClazzModule, isStandardModule, isForm } from './vbFileTypes'
|
||||
|
||||
export interface IVbParseTestRunner {
|
||||
parseFile(inputFile: File): void;
|
||||
}
|
||||
|
||||
const LOG: ILogger = createLogger(__filename);
|
||||
|
||||
const TREE_SUFFIX: string = ".tree";
|
||||
|
||||
export class VbParseTestRunner
|
||||
implements IVbParseTestRunner {
|
||||
|
||||
protected createDefaultParams(): IVbParserParams {
|
||||
return new VbParserParams();
|
||||
}
|
||||
|
||||
protected doCompareParseTree(treeFile: File, startRule: StartRuleContext, parser: VisualBasic6Parser): void {
|
||||
const treeFileData: string = readFileToString(treeFile.getAbsolutePath());
|
||||
|
||||
if (treeFileData && treeFileData.length) {
|
||||
LOG.info(`Comparing parse tree with file ${treeFile.getName()}.`);
|
||||
|
||||
it(treeFile.getName(), () => {
|
||||
const inputFileTree: string = startRule.toStringTree(parser);
|
||||
const cleanedInputFileTree: string = cleanFileTree(inputFileTree);
|
||||
const cleanedTreeFileData: string = cleanFileTree(treeFileData);
|
||||
|
||||
expect(cleanedInputFileTree).toBe(cleanedTreeFileData);
|
||||
});
|
||||
} else {
|
||||
LOG.info(`Ignoring empty parse tree file ${treeFile.getName()}.`);
|
||||
}
|
||||
}
|
||||
|
||||
protected doParse(inputFile: File, params: IVbParserParams): void {
|
||||
LOG.info(`Parsing file ${inputFile.getName()}.`);
|
||||
|
||||
const lexer: VisualBasic6Lexer = new VisualBasic6Lexer(new ANTLRInputStream(readFileToString(inputFile.getAbsolutePath())));
|
||||
|
||||
if (!params.getIgnoreSyntaxErrors()) {
|
||||
lexer.removeErrorListeners();
|
||||
lexer.addErrorListener(new ThrowingErrorListener());
|
||||
}
|
||||
|
||||
const tokens: CommonTokenStream = new CommonTokenStream(lexer);
|
||||
const parser: VisualBasic6Parser = new VisualBasic6Parser(tokens);
|
||||
|
||||
if (!params.getIgnoreSyntaxErrors()) {
|
||||
parser.removeErrorListeners();
|
||||
parser.addErrorListener(new ThrowingErrorListener());
|
||||
}
|
||||
|
||||
const startRule: StartRuleContext = parser.startRule();
|
||||
const treeFile: File = new File(inputFile.getAbsolutePath() + TREE_SUFFIX);
|
||||
|
||||
if (treeFile.exists()) {
|
||||
this.doCompareParseTree(treeFile, startRule, parser);
|
||||
}
|
||||
}
|
||||
|
||||
parseFile(inputFile: File): void {
|
||||
if (!isClazzModule(inputFile) && !isStandardModule(inputFile) && !isForm(inputFile)) {
|
||||
LOG.info(`Ignoring file ${inputFile.getName()}.`);
|
||||
} else {
|
||||
this.doParse(inputFile, this.createDefaultParams());
|
||||
}
|
||||
}
|
||||
}
|
||||
5
tests/support/VbParserError.ts
Normal file
5
tests/support/VbParserError.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class VbParserError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
9
tests/support/VbParserParams.ts
Normal file
9
tests/support/VbParserParams.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface IVbParserParams {
|
||||
getIgnoreSyntaxErrors(): boolean;
|
||||
}
|
||||
|
||||
export class VbParserParams implements IVbParserParams {
|
||||
getIgnoreSyntaxErrors(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
10
tests/support/cleanFileTree.ts
Normal file
10
tests/support/cleanFileTree.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* To be removed, as soon as the grammar does not require NEWLINEs and WS
|
||||
* anymore
|
||||
*/
|
||||
export function cleanFileTree(input: string): string {
|
||||
const inputNoEscapedNewline: string = input.replace(/\\r/g, "").replace(/\\n/g, "").replace(/\\t/g, "");;
|
||||
const inputNoNewline: string = inputNoEscapedNewline.replace(/\r?\n|\r/g, "");
|
||||
const inputReducedWhitespace: string = inputNoNewline.replace(/[\s]+/g, " ").replace(/[\s]+\)/, ")");
|
||||
return inputReducedWhitespace;
|
||||
}
|
||||
50
tests/support/infrastructure/File.ts
Normal file
50
tests/support/infrastructure/File.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export class File {
|
||||
private _path: string;
|
||||
|
||||
constructor(value: string, ...additionalPathPieces: string[]) {
|
||||
if (additionalPathPieces && additionalPathPieces.length) {
|
||||
this._path = path.resolve(process.cwd(), value, ...additionalPathPieces);
|
||||
} else {
|
||||
this._path = path.resolve(process.cwd(), value);
|
||||
}
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return path.basename(this._path);
|
||||
}
|
||||
|
||||
getAbsolutePath(): string {
|
||||
return path.resolve(this._path);
|
||||
}
|
||||
|
||||
exists(): boolean {
|
||||
return fs.existsSync(this._path);
|
||||
}
|
||||
|
||||
removeExtension(): string {
|
||||
return path.basename(this._path, path.extname(this._path));
|
||||
}
|
||||
|
||||
equals(other: File): boolean {
|
||||
return this.getAbsolutePath() === other.getAbsolutePath();
|
||||
}
|
||||
|
||||
isDirectory(): boolean {
|
||||
return fs.statSync(this._path).isDirectory();
|
||||
}
|
||||
|
||||
isFile(): boolean {
|
||||
return fs.statSync(this._path).isFile();
|
||||
}
|
||||
|
||||
listFiles(): File[] {
|
||||
return fs.readdirSync(this._path).map(e => new File(this._path, e))
|
||||
}
|
||||
|
||||
getParent(): string {
|
||||
return path.dirname(this._path);
|
||||
}
|
||||
}
|
||||
27
tests/support/infrastructure/Logger.ts
Normal file
27
tests/support/infrastructure/Logger.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export interface ILogger {
|
||||
info(message: string): void;
|
||||
}
|
||||
|
||||
export class ConsoleLogger implements ILogger {
|
||||
private _name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
info(message: string): void {
|
||||
console.log(`${this._name}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class NullLogger implements ILogger {
|
||||
constructor(name: string) {
|
||||
}
|
||||
|
||||
info(message: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function createLogger(name: string): ILogger {
|
||||
return new NullLogger(name);
|
||||
}
|
||||
35
tests/support/scanAndValidateParseTrees.ts
Normal file
35
tests/support/scanAndValidateParseTrees.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { File } from './infrastructure/File';
|
||||
import { isClazzModule, isStandardModule, isForm } from './vbFileTypes';
|
||||
import { IVbParseTestRunner, VbParseTestRunner } from './VbParseTestRunner';
|
||||
|
||||
function validateParseTrees(vb6InputFile: File): void {
|
||||
const runner: IVbParseTestRunner = new VbParseTestRunner();
|
||||
runner.parseFile(vb6InputFile);
|
||||
}
|
||||
|
||||
export default function scanAndValidateParseTrees(inputDirectory: File, exclusionFilter: (file: File) => boolean) {
|
||||
if (inputDirectory.isDirectory() && !exclusionFilter(inputDirectory)) {
|
||||
describe(inputDirectory.getName(), () => {
|
||||
// for each of the files in the directory
|
||||
inputDirectory.listFiles().forEach(inputDirectoryFile => {
|
||||
// if the file is a VB6 relevant file
|
||||
if (
|
||||
inputDirectoryFile.isFile()
|
||||
&& (isClazzModule(inputDirectoryFile)
|
||||
|| isStandardModule(inputDirectoryFile)
|
||||
|| isForm(inputDirectoryFile))) {
|
||||
validateParseTrees(inputDirectoryFile);
|
||||
}
|
||||
// else, if the file is a relevant directory
|
||||
else if (inputDirectoryFile.isDirectory()) {
|
||||
const subInputDirectory: File = inputDirectoryFile;
|
||||
const subInputDirectoryName: string = subInputDirectory.getName();
|
||||
|
||||
if ("."!==subInputDirectoryName && ".."!==subInputDirectoryName) {
|
||||
scanAndValidateParseTrees(subInputDirectory, exclusionFilter);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
17
tests/support/vbFileTypes.ts
Normal file
17
tests/support/vbFileTypes.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { File } from './infrastructure/File';
|
||||
import { extname as getFileExtension } from 'path';
|
||||
|
||||
export function isClazzModule(inputFile: File): boolean {
|
||||
const extension: string = getFileExtension(inputFile.getName()).toLowerCase();
|
||||
return ".cls" === extension;
|
||||
}
|
||||
|
||||
export function isStandardModule(inputFile: File): boolean {
|
||||
const extension: string = getFileExtension(inputFile.getName()).toLowerCase();
|
||||
return ".bas" === extension;
|
||||
}
|
||||
|
||||
export function isForm(inputFile: File): boolean {
|
||||
const extension: string = getFileExtension(inputFile.getName()).toLowerCase();
|
||||
return ".frm" === extension;
|
||||
}
|
||||
Reference in New Issue
Block a user