Skip to content

Commit

Permalink
fix: unescapeAll multiple escaped characters after each other
Browse files Browse the repository at this point in the history
fix: unescapeAll multiple escaped characters after each other

fix: unescapeAll multiple escaped characters after each other
  • Loading branch information
stepan662 committed Mar 1, 2024
1 parent 57d2f41 commit 0272c92
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 44 deletions.
17 changes: 12 additions & 5 deletions src/parser/escape.unescape.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { escapeIcuAll } from "./escapeIcuAll";
import { unescapeIcuAll } from "./unescapeIcuAll";
import { parse } from "@formatjs/icu-messageformat-parser";

function escapeAndUnescape(text: string) {
expect(unescapeIcuAll(escapeIcuAll(text))).toEqual(text);
const result = escapeIcuAll(text);
parse(`{value, plural, other {${result}}}`);
expect(unescapeIcuAll(result)).toEqual(text);
}

describe("escape icu variant", () => {
Expand Down Expand Up @@ -38,11 +41,15 @@ describe("escape icu variant", () => {
escapeAndUnescape("test '");
});

it("doesn't take tags escapes into consideration", () => {
escapeAndUnescape("'<'");
});

it("handles tough escape sequence", () => {
escapeAndUnescape("'' ' '{ '' ' '' '");
});

it("handles case with two escapes inside escape sequence", () => {
escapeAndUnescape("{}");
});

it("handles multiple escape chars after one another", () => {
escapeAndUnescape("{{'''{'}}'");
});
});
46 changes: 30 additions & 16 deletions src/parser/escapeIcuAll.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,69 @@
import { parse } from "@formatjs/icu-messageformat-parser";
import { escapeIcuAll } from "./escapeIcuAll";

function escapesAndIsValid(input: string) {
const result = escapeIcuAll(input);
parse(`{value, plural, other {${result}}}`);
return result;
}

describe("escape icu variant", () => {
it("handles parameter", () => {
expect(escapeIcuAll("this {is} variant")).toEqual("this '{'is'}' variant");
expect(escapesAndIsValid("this {is} variant")).toEqual(
"this '{'is'}' variant"
);
});

it("handles already escaped parameter", () => {
expect(escapeIcuAll("this '{is}' variant")).toEqual(
"this '''{'is'}'' variant"
expect(escapesAndIsValid("this '{is}' variant")).toEqual(
"this '''{'is'}''' variant"
);
});

it("handles text with apostrophe", () => {
expect(escapeIcuAll("apostrophe ' is here")).toEqual(
expect(escapesAndIsValid("apostrophe ' is here")).toEqual(
"apostrophe ' is here"
);
});

it("escapes hash", () => {
expect(escapeIcuAll("hash # is here")).toEqual("hash '#' is here");
expect(escapesAndIsValid("hash # is here")).toEqual("hash '#' is here");
});

it("handles double quotes", () => {
expect(escapeIcuAll("this is '' not {param} escaped")).toEqual(
expect(escapesAndIsValid("this is '' not {param} escaped")).toEqual(
"this is '''' not '{'param'}' escaped"
);
});

it("handles triple quotes", () => {
expect(escapeIcuAll("this is ''' actually #' escaped")).toEqual(
"this is ''''' actually '#'' escaped"
expect(escapesAndIsValid("unescaped ''' unescaped #' unescaped")).toEqual(
"unescaped ''''' unescaped '#''' unescaped"
);
});

it("takes hash as escape character", () => {
expect(escapeIcuAll("should be '# }' escaped")).toEqual(
"should be '''#' '}'' escaped"
expect(escapesAndIsValid("should be '# }' escaped")).toEqual(
"should be '''#' '}''' escaped"
);
});

it("escapes dangling escape at the end", () => {
expect(escapeIcuAll("test '")).toEqual("test ''");
});

it("doesn't take tags escapes into consideration", () => {
expect(escapeIcuAll("'<'")).toEqual("'<''");
expect(escapesAndIsValid("test '")).toEqual("test ''");
});

it("handles tough escape sequence", () => {
expect(escapeIcuAll("' ' '{ '' ' '' '")).toEqual(
expect(escapesAndIsValid("' ' '{ '' ' '' '")).toEqual(
"' ' '''{' '''' ' '''' ''"
);
});

it("handles case with two escapes inside escape sequence", () => {
expect(escapesAndIsValid("{}")).toEqual("'{}'");
});

// tags won't work with icu parser
it("doesn't take tags escapes into consideration", () => {
expect(escapeIcuAll("'<'")).toEqual("'<''");
});
});
25 changes: 19 additions & 6 deletions src/parser/escapeIcuAll.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const StateText = 0,
StateEscapedMaybe = 1;
StateEscapedMaybe = 1,
StateEscaping = 2;

type State = typeof StateText | typeof StateEscapedMaybe;
type State = typeof StateText | typeof StateEscapedMaybe | typeof StateEscaping;

const ESCAPABLE = new Set(["{", "}", "#"]);
const ESCAPE_CHAR = "'";
Expand All @@ -18,7 +19,7 @@ export const escapeIcuAll = (input: string) => {
} else if (ESCAPABLE.has(char)) {
result.push(ESCAPE_CHAR);
result.push(char);
result.push(ESCAPE_CHAR);
state = StateEscaping;
} else {
result.push(char);
}
Expand All @@ -30,21 +31,33 @@ export const escapeIcuAll = (input: string) => {
// add another layer of escape on top
result.push(ESCAPE_CHAR);
result.push(char);
result.push(ESCAPE_CHAR);
state = StateEscaping;
} else if (char === ESCAPE_CHAR) {
// two escape chars - escape both
result.push(ESCAPE_CHAR);
result.push(char);
result.push(ESCAPE_CHAR);
state = StateText;
} else {
result.push(char);
state = StateText;
}
state = StateText;
break;
case StateEscaping:
if (ESCAPABLE.has(char)) {
result.push(char);
} else if (char === ESCAPE_CHAR) {
result.push(ESCAPE_CHAR);
result.push(ESCAPE_CHAR);
} else {
result.push(ESCAPE_CHAR);
result.push(char);
state = StateText;
}
}
}

if (state === StateEscapedMaybe) {
if ([StateEscapedMaybe, StateEscaping].includes(state)) {
result.push(ESCAPE_CHAR);
}
return result.join("");
Expand Down
42 changes: 25 additions & 17 deletions src/parser/escapeIcuVariant.test.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,83 @@
import { escapeIcuVariant } from "./escapeIcuVariant";
import { parse } from "@formatjs/icu-messageformat-parser";

function escapesAndIsValid(input: string) {
const result = escapeIcuVariant(input);
parse(`{value, plural, other {${result}}}`);
return result;
}

describe("escape icu variant", () => {
it("handles parameter", () => {
expect(escapeIcuVariant("this {is} variant")).toEqual(
expect(escapesAndIsValid("this {is} variant")).toEqual(
"this '{is}' variant"
);
});

it("handles already escaped parameter", () => {
expect(escapeIcuVariant("this '{is}' variant")).toEqual(
expect(escapesAndIsValid("this '{is}' variant")).toEqual(
"this '{is}' variant"
);
});

it("handles text with apostrophe", () => {
expect(escapeIcuVariant("apostrophe ' is here")).toEqual(
expect(escapesAndIsValid("apostrophe ' is here")).toEqual(
"apostrophe ' is here"
);
});

it("doesn't escape hash", () => {
expect(escapeIcuVariant("hash # is here")).toEqual("hash # is here");
expect(escapesAndIsValid("hash # is here")).toEqual("hash # is here");
});

it("escapes shortest necessary distance", () => {
expect(escapeIcuVariant("hash {param} is here {param2} and here")).toEqual(
expect(escapesAndIsValid("hash {param} is here {param2} and here")).toEqual(
"hash '{param} is here {param2}' and here"
);
});

it("handles double quotes", () => {
expect(escapeIcuVariant("this is '' not {param} escaped")).toEqual(
expect(escapesAndIsValid("this is '' not {param} escaped")).toEqual(
"this is '' not '{param}' escaped"
);
});

it("handles triple quotes", () => {
expect(escapeIcuVariant("this is ''' actually #' escaped")).toEqual(
expect(escapesAndIsValid("this is ''' actually #' escaped")).toEqual(
"this is ''' actually #' escaped"
);
});

it("takes hash as escape character", () => {
expect(escapeIcuVariant("should be '# }' escaped")).toEqual(
expect(escapesAndIsValid("should be '# }' escaped")).toEqual(
"should be '# }' escaped"
);
});

it("escapes dangling escape at the end", () => {
expect(escapeIcuVariant("test '")).toEqual("test ''");
});

it("doesn't take tags escapes into consideration", () => {
expect(escapeIcuVariant("'<'")).toEqual("'<''");
expect(escapesAndIsValid("test '")).toEqual("test ''");
});

it("handles apostrophes in escape sequence correctly", () => {
expect(escapeIcuVariant("'{ '' }'")).toEqual("'{ '' }'");
expect(escapesAndIsValid("'{ '' }'")).toEqual("'{ '' }'");
});

it("escapes stuff correctly when apostrophes as parameter", () => {
expect(escapeIcuVariant("{ '' }")).toEqual("'{ '' }'");
expect(escapesAndIsValid("{ '' }")).toEqual("'{ '' }'");
});

it("escapes stuff correctly when apostrophe as parameter", () => {
expect(escapeIcuVariant("{ ' }")).toEqual("'{' ' '}'");
expect(escapesAndIsValid("{ ' }")).toEqual("'{' ' '}'");
});

it("handles apostrophes and dangling escape sequence at the end", () => {
expect(escapeIcuVariant("# položka seznamu '{ '' }")).toEqual(
expect(escapesAndIsValid("# položka seznamu '{ '' }")).toEqual(
"# položka seznamu '{ '' }'"
);
});

// is not valid for icu parser, because that one considers tags escapable
it("doesn't take tags escapes into consideration", () => {
expect(escapeIcuVariant("'<'")).toEqual("'<''");
});
});
4 changes: 4 additions & 0 deletions src/parser/unescapeIcuAll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ describe("unescapes icu variant", () => {
it("handles tough escape sequence", () => {
expect(unescapeIcuAll("' ' '{ '' ' '' '")).toEqual("' ' { ' ' '");
});

it("handles case with two escapes inside escape sequence", () => {
expect(unescapeIcuAll("'{''}'")).toEqual("{'}");
});
});

0 comments on commit 0272c92

Please sign in to comment.