feat(developer): refactor a little bit to give a postValidate() phase

- postValidate() is called after all other compilation happens
- also make SectionCompiler actually abstract
- test fixes to support this

For: #9446
This commit is contained in:
Steven R. Loomis 2023-11-17 18:08:07 +00:00
parent feb2296dbc
commit eebb88699d
4 changed files with 47 additions and 17 deletions

View file

@ -207,6 +207,13 @@ export class LdmlKeyboardCompiler {
kmx.kmxplus[section.id] = sect as any;
}
// give all sections a chance to postValidate
for(let section of sections) {
if(!section.postValidate(kmx.kmxplus[section.id])) {
passed = false;
}
}
return passed ? kmx : null;
}
}

View file

@ -1,8 +1,9 @@
import { LDMLKeyboard, KMXPlus, CompilerCallbacks } from "@keymanapp/common-types";
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
/* istanbul ignore next */
export class SectionCompiler {
/** newable interface to SectionCompiler c'tor */
export type SectionCompilerNew = new (source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) => SectionCompiler;
export abstract class SectionCompiler {
protected readonly keyboard3: LDMLKeyboard.LKKeyboard;
protected readonly callbacks: CompilerCallbacks;
@ -11,19 +12,34 @@ export class SectionCompiler {
this.callbacks = callbacks;
}
/* c8 ignore next 11 */
public get id(): SectionIdent {
throw Error(`Internal Error: id() not implemented`);
}
public compile(sections: KMXPlus.DependencySections): KMXPlus.Section {
throw Error(`Internal Error: compile() not implemented`);
}
public abstract get id(): SectionIdent;
/**
* This is called before compile.
* @returns false if this compiler failed to validate.
*/
public validate(): boolean {
return true;
}
/**
* Perform the compilation for this section, returning the correct Section subclass
* object.
*
* @param sections any declared dependency sections per dependencies()
*/
public abstract compile(sections: KMXPlus.DependencySections): KMXPlus.Section;
/**
* This is called after all other compile phases have completed, and provides an
* opportunity for late error reporting, for example for invalid strings.
* @param section the compiled section, if any.
* @returns false if validate fails
*/
public postValidate(section?: KMXPlus.Section): boolean {
return true;
}
/**
* Get the dependencies for this compiler.
* @returns set of dependent sections

View file

@ -19,7 +19,7 @@ import { MarkerTracker, MarkerUse } from "./marker-tracker.js";
type TransformCompilerType = 'simple' | 'backspace';
export class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran> extends SectionCompiler {
export abstract class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran> extends SectionCompiler {
static validateMarkers(keyboard: LDMLKeyboard.LKKeyboard, mt : MarkerTracker): boolean {
keyboard?.transforms?.forEach(transforms =>

View file

@ -4,7 +4,7 @@
import 'mocha';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { SectionCompiler } from '../../src/compiler/section-compiler.js';
import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js';
import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, compilerEventFormat, LDMLKeyboard, UnicodeSetParser, CompilerCallbacks } from '@keymanapp/common-types';
import { LdmlKeyboardCompiler } from '../../src/main.js'; // make sure main.js compiles
import { assert } from 'chai';
@ -52,7 +52,7 @@ afterEach(function() {
});
export async function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: TestCompilerCallbacks, dependencies?: typeof SectionCompiler[]): Promise<Section> {
export async function loadSectionFixture(compilerClass: SectionCompilerNew, filename: string, callbacks: TestCompilerCallbacks, dependencies?: SectionCompilerNew[], postValidateFail?: boolean): Promise<Section> {
callbacks.messages = [];
const inputFilename = makePathToFixture(filename);
const data = callbacks.loadFile(inputFilename);
@ -83,13 +83,16 @@ export async function loadSectionFixture(compilerClass: typeof SectionCompiler,
compiler.dependencies.forEach(dep => assert.ok(sections[dep],
`Required dependency '${dep}' for '${compiler.id}' was not supplied: Check the 'dependencies' argument to loadSectionFixture or testCompilationCases`));
return compiler.compile(sections);
const section = await compiler.compile(sections);
const postValidate = compiler.postValidate(section);
assert.equal(postValidate, !postValidateFail, `expected postValidate() to return ${!postValidateFail}`);
return section;
}
/**
* Recursively load dependencies. Normally they are loaded in SECTION_COMPILERS order
*/
async function loadDepsFor(sections: DependencySections, parentCompiler: SectionCompiler, source: LDMLKeyboardXMLSourceFile, callbacks: TestCompilerCallbacks, dependencies?: typeof SectionCompiler[]) {
async function loadDepsFor(sections: DependencySections, parentCompiler: SectionCompiler, source: LDMLKeyboardXMLSourceFile, callbacks: TestCompilerCallbacks, dependencies?: SectionCompilerNew[]) {
const parentId = parentCompiler.id;
if (!dependencies) {
// default dependencies
@ -184,7 +187,11 @@ export interface CompilationCase {
/**
* Optional dependent sections to load. Will be strs+list+elem if falsy.
*/
dependencies?: (typeof SectionCompiler)[];
dependencies?: (SectionCompilerNew)[];
/**
* Optional, if true, postValidate() must return false. (must be != postValidate())
*/
postValidateFail?: boolean;
}
/**
@ -193,7 +200,7 @@ export interface CompilationCase {
* @param compiler argument to loadSectionFixture()
* @param callbacks argument to loadSectionFixture()
*/
export function testCompilationCases(compiler: typeof SectionCompiler, cases : CompilationCase[], dependencies?: (typeof SectionCompiler)[]) {
export function testCompilationCases(compiler: SectionCompilerNew, cases : CompilationCase[], dependencies?: (SectionCompilerNew)[]) {
// we need our own callbacks rather than using the global so messages don't get mixed
const callbacks = new TestCompilerCallbacks();
for (let testcase of cases) {