Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(developer): handle missing files in kmc-model #12596

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions developer/src/kmc-model/src/build-trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ class WordListFromFilename {

*lines() {
const data = callbacks.loadFile(this.name);
if(!data) {
throw new ModelCompilerError(ModelCompilerMessages.Error_WordlistFileNotFound({filename:this.name}));
}
const contents = new TextDecoder(detectEncoding(data)).decode(data);
yield *enumerateLines(contents.split(NEWLINE_SEPARATOR));
}
Expand Down
6 changes: 5 additions & 1 deletion developer/src/kmc-model/src/lexical-model-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,11 @@ export class LexicalModelCompiler implements KeymanCompiler {
*/
public loadFromFilename(filename: string): LexicalModelSource {

let sourceCode = new TextDecoder().decode(callbacks.loadFile(filename));
const data = callbacks.loadFile(filename);
if(!data) {
throw new ModelCompilerError(ModelCompilerMessages.Error_ModelFileNotFound({filename}));
}
let sourceCode = new TextDecoder().decode(data);
// Compile the module to JavaScript code.
// NOTE: transpile module does a very simple TS to JS compilation.
// It DOES NOT check for types!
Expand Down
22 changes: 20 additions & 2 deletions developer/src/kmc-model/src/model-compiler-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const SevHint = CompilerErrorSeverity.Hint | Namespace;
const SevError = CompilerErrorSeverity.Error | Namespace;
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;

const m = (code: number, message: string) : CompilerEvent => ({
...CompilerMessageSpec(code, message),
const m = (code: number, message: string, detail?: string) : CompilerEvent => ({
...CompilerMessageSpec(code, message, detail),
line: ModelCompilerMessageContext.line,
filename: ModelCompilerMessageContext.filename,
});
Expand Down Expand Up @@ -70,6 +70,24 @@ export class ModelCompilerMessages {
static ERROR_UnsupportedScriptOverride = SevError | 0x000A;
static Error_UnsupportedScriptOverride = (o:{option:string}) => m(this.ERROR_UnsupportedScriptOverride,
`Unsupported script override: ${def(o.option)}`);

static ERROR_ModelFileNotFound = SevError | 0x000B;
static Error_ModelFileNotFound = (o:{filename:string}) => m(
this.ERROR_ModelFileNotFound,
`Lexical model source file ${def(o.filename)} was not found`,
`The model source file was not found on the disk. Verify that you have
the correct path to the file.`
);

static ERROR_WordlistFileNotFound = SevError | 0x000C;
static Error_WordlistFileNotFound = (o:{filename:string}) => m(
this.ERROR_WordlistFileNotFound,
`Wordlist file ${def(o.filename)} was not found`,
`The wordlist file was not found on the disk. Verify that you have
the correct path to the file.`
);


};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const source: LexicalModelSource = {
format: 'trie-1.0',
sources: ['wordlist.tsv'],
};
export default source;
54 changes: 53 additions & 1 deletion developer/src/kmc-model/test/test-messages.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,62 @@
import 'mocha';
import { assert } from 'chai';
import { ModelCompilerMessages } from '../src/model-compiler-messages.js';
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
import { LexicalModelCompiler } from '../src/lexical-model-compiler.js';
import { makePathToFixture } from './helpers/index.js';

describe('ModelCompilerMessages', function () {

const callbacks = new TestCompilerCallbacks();

this.beforeEach(function() {
callbacks.clear();
});

this.afterEach(function() {
if(this.currentTest?.isFailed()) {
callbacks.printMessages();
}
});

it('should have a valid ModelCompilerMessages object', function() {
return verifyCompilerMessagesObject(ModelCompilerMessages, CompilerErrorNamespace.ModelCompiler);
});

async function testForMessage(fixture: string[], messageId?: number) {
const compiler = new LexicalModelCompiler();
assert.isTrue(await compiler.init(callbacks, null));

const modelPath = makePathToFixture(...fixture);

// Note: throwing away compile results (just to memory)
await compiler.run(modelPath, null);

if(messageId) {
assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
assert.lengthOf(callbacks.messages, 1, `messages should have 1 entry, instead has: `+JSON.stringify(callbacks.messages,null,2));
} else {
assert.lengthOf(callbacks.messages, 0, `messages should be empty, but instead got: `+JSON.stringify(callbacks.messages,null,2));
}
}

// ERROR_ModelFileNotFound

it('should generate ERROR_ModelFileNotFound if a .model.ts file is missing', async function() {
await testForMessage(
['invalid-models', 'missing-file.model.ts'],
ModelCompilerMessages.ERROR_ModelFileNotFound
);
});

// ERROR_WordlistFileNotFound

it('should generate ERROR_WordlistFileNotFound if a .tsv file is missing', async function() {
await testForMessage(
['invalid-models', 'example.qaa.missing-wordlist', 'example.qaa.missing-wordlist.model.ts'],
ModelCompilerMessages.ERROR_WordlistFileNotFound
);
});

});
Loading