feat(developer): errs on \uXXXX escapes 🙀

- note, some older test cases had to be fixed also.

For: #10389
This commit is contained in:
Steven R. Loomis 2024-02-13 12:26:46 -06:00
parent b99a2cf2a5
commit 8787073c40
10 changed files with 100 additions and 11 deletions

View file

@ -36,7 +36,7 @@ export class UnescapeError extends Error {
* @param hex one codepoint in hex, such as '0127'
* @returns the unescaped codepoint
*/
function unescapeOne(hex: string): string {
export function unescapeOne(hex: string): string {
const codepoint = Number.parseInt(hex, 16);
return String.fromCodePoint(codepoint);
}

View file

@ -173,6 +173,10 @@ export class CompilerMessages {
static Error_UnparseableTransformFrom = (o: { from: string }) =>
m(this.ERROR_UnparseableTransformFrom, `Invalid transfom from "${o.from}"`);
static ERROR_UnparseableTransformFrom = SevError | 0x0029;
static Error_InvalidQuadEscape = (o: { cp: number }) =>
m(this.ERROR_InvalidQuadEscape, `Invalid escape "\\u${util.hexQuad(o?.cp || 0)}", use "\\u{${o?.cp?.toString(16)}}" instead.`);
static ERROR_InvalidQuadEscape = SevError | 0x0030;
}

View file

@ -144,8 +144,11 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
// don't unescape literals here, because we are going to pass them through into the regex
cookedFrom = util.unescapeStringToRegex(cookedFrom);
// cook the ranges also
cookedFrom = this.checkEscapes(cookedFrom); // check for \uXXXX escapes before normalizing
if (cookedFrom === null) return null; // error
cookedFrom = this.checkRanges(cookedFrom); // check before normalizing
if (cookedFrom === null) return null; // error
if (!sections?.meta?.normalizationDisabled) {
// nfd here.
@ -156,7 +159,7 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
new RegExp(cookedFrom, 'ug');
} catch (e) {
this.callbacks.reportMessage(CompilerMessages.Error_UnparseableTransformFrom({ from: transform.from }));
return null;
return null; // error
}
// cookedFrom is cooked above, since there's some special treatment
@ -185,6 +188,12 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
private compileReorder(sections: DependencySections, reorder: LKReorder): TranReorder {
let result = new TranReorder();
if (reorder.from && this.checkEscapes(reorder.from) === null) {
return null; // error'ed
}
if (reorder.before && this.checkEscapes(reorder.before) === null) {
return null; // error'ed
}
result.elements = sections.elem.allocElementString(sections, reorder.from, reorder.order, reorder.tertiary, reorder.tertiaryBase, reorder.preBase);
result.before = sections.elem.allocElementString(sections, reorder.before);
if (!result.elements || !result.before) {
@ -216,9 +225,27 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
return defaults;
}
/** true if ok */
private checkRanges(cookedFrom: string): string {
/** returns ===null on error */
private checkEscapes(cookedFrom: string): string | null {
if (!cookedFrom) return cookedFrom;
// should not follow marker prefix, nor marker prefix with range
const anyQuad = /(?<!\\uffff\\u0008(?:\[[0-9a-fA-F\\u-]*)?)\\u([0-9a-fA-F]{4})/g;
for (const [, sub] of cookedFrom.matchAll(anyQuad)) {
const s = util.unescapeOne(sub);
if (s !== '\uffff' && s !== '\u0008') { // markers
this.callbacks.reportMessage(CompilerMessages.Error_InvalidQuadEscape({ cp: s.codePointAt(0) }));
return null; // exit on the first error
}
}
return cookedFrom;
}
/** returns ===null on error */
private checkRanges(cookedFrom: string): string | null {
if (!cookedFrom) return cookedFrom;
// extract all of the potential ranges - but don't match any-markers!
const anyRange = /(?<!\\uffff\\u0008)\[([^\]]+)\]/g;
const ranges = cookedFrom.matchAll(anyRange);
@ -226,7 +253,7 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
if (!ranges) return cookedFrom;
// extract inner members of a range (inside the [])
const rangeRegex = /(\\u[0-9a-fA-F]{4}|.)-(\\u[0-9a-fA-F]{4}|.)|./g;
const rangeRegex = /(\\u\{[0-9a-fA-F]\}{1,6}|.)-(\\u\{[0-9a-fA-F]\}{1,6}|.)|./g;
const rangeExplicit = new util.NFDAnalyzer();
const rangeImplicit = new util.NFDAnalyzer();
@ -235,8 +262,6 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
function processExplicit(s: string) {
if (s.startsWith('\\u{')) {
s = util.unescapeString(s);
} else if(s.startsWith('\\u')) {
s = util.unescapeOneQuadString(s);
}
rangeExplicit.add(s);
return s;

View file

@ -47,7 +47,7 @@
<transformGroup>
<!-- Northern Thai example from spec -->
<reorder before="\u{1A6B}" from="\u{1A60}[\u1A75-\u1A79]\u{1A45}" order="10 55 10" />
<reorder before="\u{1A6B}" from="\u{1A60}[\u{1A75}-\u{1A79}]\u{1A45}" order="10 55 10" />
</transformGroup>
</transforms>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<info name="fail-bad-reorder"/>
<keys />
<transforms type="simple">
<transformGroup>
<reorder before="\u1A6B" from="\u{1A60}[\u{1A75}-\u{1A79}]\u{1A45}" order="10 55 10" />
</transformGroup>
</transforms>
</keyboard3>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<info name="fail-bad-reorder"/>
<keys />
<transforms type="simple">
<transformGroup>
<reorder from="\u1A60[\u1A75-\u1A79]\u1A45" order="10 55 10" />
</transformGroup>
</transforms>
</keyboard3>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard3 SYSTEM "../../../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="mt" conformsTo="techpreview">
<info name="fail-bad-tran-2"/>
<keys />
<transforms type="simple">
<transformGroup>
<transform from="\u0127" to="x"/> <!-- fail: use \u{1234} instead -->
</transformGroup>
</transforms>
</keyboard3>

View file

@ -71,7 +71,7 @@ describe('compiler-tests', function() {
const source = k.load(filename);
assert.notOk(source, `Trying to loadTestData(${filename})`);
});
it('should fail on illegal chars', async function() {
it('should fail on illegal chars - sections/strs/invalid-illegal.xml', async function() {
const inputFilename = makePathToFixture('sections/strs/invalid-illegal.xml');
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
[

View file

@ -265,11 +265,23 @@ describe('tran', function () {
],
},
{
subpath: 'sections/tran/fail-bad-reorder.xml',
subpath: 'sections/tran/fail-bad-reorder-1.xml',
errors: [
KmnOtherCompilerMessages.Error_UnicodeSetSyntaxError()
],
},
{
subpath: 'sections/tran/fail-bad-reorder-2.xml',
errors: [
CompilerMessages.Error_InvalidQuadEscape({ cp: 0x1a6b }),
],
},
{
subpath: 'sections/tran/fail-bad-reorder-3.xml',
errors: [
CompilerMessages.Error_InvalidQuadEscape({ cp: 0x1a60 }),
],
},
// error due to bad regex
{
subpath: `sections/tran/fail-bad-tran-1.xml`,
@ -277,6 +289,12 @@ describe('tran', function () {
CompilerMessages.Error_UnparseableTransformFrom({ from: 'AB(now if only I would terminate this group..' }),
],
},
{
subpath: `sections/tran/fail-bad-tran-2.xml`,
errors: [
CompilerMessages.Error_InvalidQuadEscape({ cp: 295 }),
],
},
], tranDependencies);
});