1
0
mirror of https://github.com/pdemian/human2regex.git synced 2025-05-16 12:30:09 -07:00

Added tests and fixed bugs thanks to tests

This commit is contained in:
Patrick Demian 2020-11-05 01:03:31 -05:00
parent 32354d8aaf
commit 7d7d6337e1
8 changed files with 397 additions and 58 deletions

View File

@ -68,15 +68,13 @@ const unicode_script_codes = [
* @internal * @internal
*/ */
export abstract class H2RCST { export abstract class H2RCST {
public tokens: IToken[];
/** /**
* Constructor for H2RCST * Constructor for H2RCST
* *
* @param tokens Tokens used to calculate where an error occured * @param tokens Tokens used to calculate where an error occured
* @internal * @internal
*/ */
constructor(tokens: IToken[]) { constructor(public tokens: IToken[]) {
this.tokens = tokens; this.tokens = tokens;
} }
@ -335,6 +333,8 @@ export class MatchSubStatementCST extends H2RCST {
let ret = ""; let ret = "";
let require_grouping = false;
if (str.length === 1) { if (str.length === 1) {
ret = str[0]; ret = str[0];
} }
@ -344,10 +344,14 @@ export class MatchSubStatementCST extends H2RCST {
} }
else { else {
//use a no-capture group //use a no-capture group
ret = "(?:" + str.join("|") + ")"; ret = str.join("|");
require_grouping = true;
} }
if (this.count) { if (this.count) {
if (require_grouping) {
ret = "(?:" + ret + ")";
}
ret += this.count.toRegex(language); ret += this.count.toRegex(language);
} }
@ -380,7 +384,7 @@ export class UsingStatementCST extends H2RCST {
for (let i = 1; i < this.flags.length; i++) { for (let i = 1; i < this.flags.length; i++) {
if (hasFlag(flag, this.flags[i])) { if (hasFlag(flag, this.flags[i])) {
errors.push(this.error("Duplicate modifier: " + MatchSubStatementType[this.flags[i]] )); errors.push(this.error("Duplicate modifier: " + UsingFlags[this.flags[i]] ));
} }
flag = combineFlags(flag, this.flags[i]); flag = combineFlags(flag, this.flags[i]);
} }

View File

@ -93,7 +93,7 @@ export class Human2RegexLexer {
this.lexer = new Lexer(AllTokens, { ensureOptimizations: true, skipValidations: options.skip_validations }); this.lexer = new Lexer(AllTokens, { ensureOptimizations: true, skipValidations: options.skip_validations });
} }
private lexError(token: IToken) : ILexingError { private lexError(token: IToken): ILexingError {
return { return {
offset: token.startOffset, offset: token.startOffset,
line: token.startLine ?? NaN, line: token.startLine ?? NaN,
@ -109,7 +109,7 @@ export class Human2RegexLexer {
* @param text the text to analyze * @param text the text to analyze
* @returns a lexing result which contains the token stream and error list * @returns a lexing result which contains the token stream and error list
*/ */
public tokenize(text: string) : ILexingResult { public tokenize(text: string): ILexingResult {
const lex_result = this.lexer.tokenize(text); const lex_result = this.lexer.tokenize(text);
if (lex_result.tokens.length === 0) { if (lex_result.tokens.length === 0) {

View File

@ -154,7 +154,7 @@ export class Human2RegexParser extends EmbeddedActionsParser {
}); });
// match sub rules // match sub rules
let mss_rules: IOrAlt<MatchSubStatementValue>[] | null = null; let mss_rules: IOrAlt<{tokens: IToken[], statement: MatchSubStatementValue}>[] | null = null;
const MatchSubStatement = $.RULE("MatchSubStatement", () => { const MatchSubStatement = $.RULE("MatchSubStatement", () => {
let count: CountSubStatementCST | null = null; let count: CountSubStatementCST | null = null;
let invert: boolean = false; let invert: boolean = false;
@ -164,7 +164,7 @@ export class Human2RegexParser extends EmbeddedActionsParser {
let to: string | null = null; let to: string | null = null;
let type: MatchSubStatementType = MatchSubStatementType.Anything; let type: MatchSubStatementType = MatchSubStatementType.Anything;
const tokens: IToken[] = []; let tokens: IToken[] = [];
count = $.OPTION(() => { count = $.OPTION(() => {
const css = $.SUBRULE(CountSubStatement); const css = $.SUBRULE(CountSubStatement);
@ -184,122 +184,124 @@ export class Human2RegexParser extends EmbeddedActionsParser {
SEP: T.Or, SEP: T.Or,
DEF: () => { DEF: () => {
$.OPTION3(() => $.CONSUME(T.A)); $.OPTION3(() => $.CONSUME(T.A));
values.push($.OR(mss_rules || (mss_rules = [ const result = $.OR(mss_rules || (mss_rules = [
// range [a-z] // range [a-z]
{ ALT: () => { { ALT: () => {
$.OPTION4(() => $.CONSUME(T.From)); const token0 = $.OPTION4(() => $.CONSUME(T.From));
from = $.CONSUME2(T.StringLiteral).image; const token1 = $.CONSUME2(T.StringLiteral);
from = token1.image;
$.CONSUME(T.To); $.CONSUME(T.To);
const token = $.CONSUME3(T.StringLiteral); const token2 = $.CONSUME3(T.StringLiteral);
tokens.push(token); to = token2.image;
to = token.image;
type = MatchSubStatementType.Between; type = MatchSubStatementType.Between;
return new MatchSubStatementValue(type, from, to); if (usefulConditional(token0, "Bug in type definition. Option should return <T|undefined>, but it doesn't")) {
return { tokens: [ token0, token2 ], statement: new MatchSubStatementValue(type, from, to) };
}
return { tokens: [ token1, token2 ], statement: new MatchSubStatementValue(type, from, to) };
}}, }},
// range [a-z] // range [a-z]
{ ALT: () => { { ALT: () => {
$.CONSUME(T.Between); const token1 = $.CONSUME(T.Between);
from = $.CONSUME4(T.StringLiteral).image; from = $.CONSUME4(T.StringLiteral).image;
$.CONSUME(T.And); $.CONSUME(T.And);
const token = $.CONSUME5(T.StringLiteral); const token2 = $.CONSUME5(T.StringLiteral);
to = token.image; to = token2.image;
tokens.push(token);
type = MatchSubStatementType.Between; type = MatchSubStatementType.Between;
return new MatchSubStatementValue(type, from, to); return { tokens: [ token1, token2 ], statement: new MatchSubStatementValue(type, from, to) };
}}, }},
// exact string // exact string
{ ALT: () => { { ALT: () => {
const token = $.CONSUME(T.StringLiteral); const token = $.CONSUME(T.StringLiteral);
tokens.push(token);
value = token.image; value = token.image;
type = MatchSubStatementType.SingleString; type = MatchSubStatementType.SingleString;
return new MatchSubStatementValue(type, value); return { tokens: [ token ], statement: new MatchSubStatementValue(type, value) };
}}, }},
//unicode //unicode
{ ALT: () => { { ALT: () => {
$.CONSUME(T.Unicode); const token1 = $.CONSUME(T.Unicode);
const token = $.CONSUME5(T.StringLiteral); const token2 = $.CONSUME6(T.StringLiteral);
tokens.push(token); value = token2.image;
value = token.image;
type = MatchSubStatementType.Unicode; type = MatchSubStatementType.Unicode;
return new MatchSubStatementValue(type, value); return { tokens: [ token1, token2 ], statement: new MatchSubStatementValue(type, value) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Anything)); const token = $.CONSUME(T.Anything);
type = MatchSubStatementType.Anything; type = MatchSubStatementType.Anything;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Boundary)); const token = $.CONSUME(T.Boundary);
type = MatchSubStatementType.Boundary; type = MatchSubStatementType.Boundary;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Word)); const token = $.CONSUME(T.Word);
type = MatchSubStatementType.Word; type = MatchSubStatementType.Word;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Digit)); const token = $.CONSUME(T.Digit);
type = MatchSubStatementType.Digit; type = MatchSubStatementType.Digit;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Character)); const token = $.CONSUME(T.Character);
type = MatchSubStatementType.Character; type = MatchSubStatementType.Character;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Whitespace)); const token = $.CONSUME(T.Whitespace);
type = MatchSubStatementType.Whitespace; type = MatchSubStatementType.Whitespace;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Number)); const token = $.CONSUME(T.Number);
type = MatchSubStatementType.Number; type = MatchSubStatementType.Number;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Tab)); const token = $.CONSUME(T.Tab);
type = MatchSubStatementType.Tab; type = MatchSubStatementType.Tab;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Linefeed)); const token = $.CONSUME(T.Linefeed);
type = MatchSubStatementType.Linefeed; type = MatchSubStatementType.Linefeed;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.Newline)); const token = $.CONSUME(T.Newline);
type = MatchSubStatementType.Newline; type = MatchSubStatementType.Newline;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
{ ALT: () => { { ALT: () => {
tokens.push($.CONSUME(T.CarriageReturn)); const token = $.CONSUME(T.CarriageReturn);
type = MatchSubStatementType.CarriageReturn; type = MatchSubStatementType.CarriageReturn;
return new MatchSubStatementValue(type); return { tokens: [ token ], statement: new MatchSubStatementValue(type) };
}}, }},
]))); ]));
tokens = tokens.concat(result.tokens);
values.push(result.statement);
} }
}); });

View File

@ -155,7 +155,7 @@ export function removeQuotes(input: string): string {
* @internal * @internal
*/ */
export function regexEscape(input: string): string { export function regexEscape(input: string): string {
return input.replace("\\", "\\\\").replace(/([=:\-\.\[\]\^\|\(\)\*\+\?\{\}\$\/])/g, "\\$1"); return input.replace(/([:\\\-\.\[\]\^\|\(\)\*\+\?\{\}\$\/])/g, "\\$1");
} }
/** /**

91
tests/generator.spec.ts Normal file
View File

@ -0,0 +1,91 @@
/*! Copyright (c) 2020 Patrick Demian; Licensed under MIT */
import { Human2RegexParser, Human2RegexParserOptions } from "../src/parser";
import { Human2RegexLexer, Human2RegexLexerOptions } from "../src/lexer";
import { RegexDialect } from "../src/generator";
describe("Generator functionality", function() {
const lexer = new Human2RegexLexer(new Human2RegexLexerOptions(true));
// eslint-disable-next-line init-declarations
const parser = new Human2RegexParser(new Human2RegexParserOptions(true));
it("generates an empty regex", function() {
parser.input = lexer.tokenize("").tokens;
const reg0 = parser.parse();
expect(reg0.validate(RegexDialect.JS).length).toBe(0);
expect(reg0.toRegex(RegexDialect.JS)).toBe("//");
parser.input = lexer.tokenize("\n/*hello world*/\n").tokens;
const reg1 = parser.parse();
expect(reg1.validate(RegexDialect.JS).length).toBe(0);
expect(reg1.toRegex(RegexDialect.JS)).toBe("//");
});
it("generates a basic regex", function() {
parser.input = lexer.tokenize('match "hello" or "world"').tokens;
const reg0 = parser.parse();
expect(reg0.validate(RegexDialect.JS).length).toBe(0);
expect(reg0.toRegex(RegexDialect.JS)).toBe("/hello|world/");
parser.input = lexer.tokenize('match "http" then optionally "s"').tokens;
const reg1 = parser.parse();
expect(reg1.validate(RegexDialect.JS).length).toBe(0);
expect(reg1.toRegex(RegexDialect.JS)).toBe("/https?/");
});
it("validates invalid regexes", function() {
parser.input = lexer.tokenize('match unicode "Latin"').tokens;
const reg0 = parser.parse();
expect(reg0.validate(RegexDialect.DotNet).length).toBeGreaterThan(0);
parser.input = lexer.tokenize("using global and global").tokens;
const reg1 = parser.parse();
expect(reg1.validate(RegexDialect.DotNet).length).toBeGreaterThan(0);
parser.input = lexer.tokenize('match "a" to "asdf"').tokens;
const reg2 = parser.parse();
expect(reg2.validate(RegexDialect.DotNet).length).toBeGreaterThan(0);
});
it("runs complex scripts", function() {
const str = `
using global and exact matching
create an optional group called protocol
match "http"
optionally match "s"
match "://"
create an optional group called subdomain
repeat
match a word
match "."
create a group called domain
match 1+ words or "_" or "-"
match "."
match a word
# port, but we don't care about it, so ignore it
optionally match ":" then 0+ digits
create an optional group called path
repeat
match "/"
match 0+ words or "_" or "-"
create an optional group
# we don't want to capture the '?', so don't name the group until afterwards
match "?"
create a group called query
repeat
match 1+ words or "_" or "-"
match "="
match 1+ words or "_" or "-"
create an optional group
# fragment, again, we don't care, so ignore everything afterwards
match "#"
match 0+ any thing
`;
parser.input = lexer.tokenize(str).tokens;
const reg = parser.parse();
expect(reg.validate(RegexDialect.JS).length).toBe(0);
expect(reg.toRegex(RegexDialect.JS)).toBe("/^(?<protocol>https?\\:\\/\\/)?(?<subdomain>(\\w+\\.)*)?(?<domain>(?:\\w+|_|\\-)+\\.\\w+)\\:?\\d*(?<path>(\\/(?:\\w+|_|\\-)*)*)?(\\?(?<query>((?:\\w+|_|\\-)+\=(?:\\w+|_|\\-)+)*))?(#.*)?$/g");
});
});

View File

@ -1,6 +1,69 @@
describe("calculate", function() { /*! Copyright (c) 2020 Patrick Demian; Licensed under MIT */
it("add", function() {
const result = 5 + 2; import { Human2RegexLexer, Human2RegexLexerOptions, IndentType } from "../src/lexer";
expect(result).toBe(7); import { Indent } from "../src/tokens";
});
describe("Lexer capabilities", function() {
const lexer = new Human2RegexLexer(new Human2RegexLexerOptions(true));
it("validates", function() {
expect(() => lexer.setOptions(new Human2RegexLexerOptions(false, IndentType.Both))).not.toThrow();
});
it("parses nothing", function() {
expect(() => lexer.tokenize("")).not.toThrow();
expect(lexer.tokenize("").errors).toHaveLength(0);
expect(lexer.tokenize("").tokens).toHaveLength(0);
expect(() => lexer.tokenize("\n/* hello world */\n")).not.toThrow();
expect(lexer.tokenize("\n/* hello world */\n").errors).toHaveLength(0);
expect(lexer.tokenize("\n/* hello world */\n").tokens).toHaveLength(0);
});
it("parses something", function() {
// tabs
expect(() => lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n')).not.toThrow();
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n').errors).toHaveLength(0);
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n').tokens).toHaveLength(17);
// spaces
expect(() => lexer.tokenize('optionally create a group called test\n optionally match "-" or "$/()" then "^[]"\n')).not.toThrow();
expect(lexer.tokenize('optionally create a group called test\n optionally match "-" or "$/()" then "^[]"\n').errors).toHaveLength(0);
expect(lexer.tokenize('optionally create a group called test\n optionally match "-" or "$/()" then "^[]"\n').tokens).toHaveLength(17);
// no EOF newline
expect(() => lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"')).not.toThrow();
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"').errors).toHaveLength(0);
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"').tokens).toHaveLength(17);
// Outdent
expect(() => lexer.tokenize('optionally create a group\n\trepeat\n\t\tmatch "-"\n\toptionally match "-" or "$/()" then "^[]"\n')).not.toThrow();
expect(lexer.tokenize('optionally create a group\n\trepeat\n\t\tmatch "-"\n\toptionally match "-" or "$/()" then "^[]"\n').errors).toHaveLength(0);
expect(lexer.tokenize('optionally create a group\n\trepeat\n\t\tmatch "-"\n\toptionally match "-" or "$/()" then "^[]"\n').tokens).toHaveLength(22);
});
it("fails to parse bad text", function() {
// double indent
expect(() => lexer.tokenize('optionally create a group called test\n\t\toptionally match "-" or "$/()" then "^[]"')).not.toThrow();
expect(lexer.tokenize('optionally create a group called test\n\t\toptionally match "-" or "$/()" then "^[]"').errors.length).toBeGreaterThan(0);
// missing " at end
expect(() => lexer.tokenize('optionally create a group\n\toptionally match "- or "$/()" then "^[]')).not.toThrow();
expect(lexer.tokenize('optionally create a group\n\toptionally match "- or "$/()" then "^[]').errors.length).toBeGreaterThan(0);
});
it("handles switching between tabs and spaces", function() {
lexer.setOptions(new Human2RegexLexerOptions(true, IndentType.Tabs));
// tabs
expect(() => lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n')).not.toThrow();
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n').errors).toHaveLength(0);
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n').tokens).toHaveLength(17);
expect(lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n').tokens.map((x) => x.tokenType)).toContain(Indent);
// spaces should be ignored
expect(() => lexer.tokenize('optionally create a group called test\n optionally match "-" or "$/()" then "^[]"\n')).not.toThrow();
expect(lexer.tokenize('optionally create a group called test\n optionally match "-" or "$/()" then "^[]"\n').errors).toHaveLength(0);
expect(lexer.tokenize('optionally create a group called test\n optionally match "-" or "$/()" then "^[]"\n').tokens.map((x) => x.tokenType)).not.toContain(Indent);
});
}); });

71
tests/parser.spec.ts Normal file
View File

@ -0,0 +1,71 @@
/*! Copyright (c) 2020 Patrick Demian; Licensed under MIT */
import { Human2RegexLexer, Human2RegexLexerOptions } from "../src/lexer";
import { Human2RegexParser, Human2RegexParserOptions } from "../src/parser";
import { IToken } from "chevrotain";
import { RegularExpressionCST } from "../src/generator";
describe("Parser capabilities", function() {
const lexer = new Human2RegexLexer(new Human2RegexLexerOptions(true));
// eslint-disable-next-line init-declarations
let parser!: Human2RegexParser;
it("validates", function() {
expect(() => parser = new Human2RegexParser(new Human2RegexParserOptions(false))).not.toThrow();
});
it("parses nothing", function() {
let tokens: IToken[] = [];
tokens = lexer.tokenize("").tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
expect(parser.parse()).toBeInstanceOf(RegularExpressionCST);
expect(parser.errors.length).toEqual(0);
tokens = lexer.tokenize("\n/* hello world */\n").tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
expect(parser.parse()).toBeInstanceOf(RegularExpressionCST);
expect(parser.errors.length).toEqual(0);
});
it("parses something", function() {
let tokens: IToken[] = [];
tokens = lexer.tokenize('optionally create a group called test\n\toptionally match "-" or "$/()" then "^[]"\n').tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
parser.input = tokens;
expect(parser.parse()).toBeInstanceOf(RegularExpressionCST);
expect(parser.errors.length).toEqual(0);
tokens = lexer.tokenize('optionally create a group called test\n\trepeat 3..five\n\t\toptionally match "-" or "$/()" then "^[]"').tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
parser.input = tokens;
expect(parser.parse()).toBeInstanceOf(RegularExpressionCST);
expect(parser.errors.length).toEqual(0);
});
it("fails to parse bad text", function() {
let tokens: IToken[] = [];
tokens = lexer.tokenize('optionally create a group called\n\toptionally match "-" or "$/()" then "^[]"\n').tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
expect(parser.errors.length).toBeGreaterThan(0);
tokens = lexer.tokenize('optionally create a called test\n\toptionally match "-" or "$/()" then "^[]"\n').tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
expect(parser.errors.length).toBeGreaterThan(0);
tokens = lexer.tokenize('optionally create a group\n\toptionally match or "$/()" then "^[]"\n').tokens;
parser.input = tokens;
expect(() => parser.parse()).not.toThrow();
expect(parser.errors.length).toBeGreaterThan(0);
});
});

108
tests/utilities.spec.ts Normal file
View File

@ -0,0 +1,108 @@
/*! Copyright (c) 2020 Patrick Demian; Licensed under MIT */
import "../src/utilities";
import { isSingleRegexCharacter, findLastIndex, removeQuotes, regexEscape, hasFlag, combineFlags, makeFlag, first, last, CommonError } from "../src/utilities";
import { UsingFlags, ISemanticError } from "../src/generator";
import { IRecognitionException, ILexingError, createTokenInstance } from "chevrotain";
import { Indent } from "../src/tokens";
describe("Utility functions", function() {
it("should handle flags", function() {
expect(makeFlag(0)).toBe(1);
expect(makeFlag(7)).toBe(1*2*2*2*2*2*2*2);
expect(combineFlags(UsingFlags.Exact, UsingFlags.Global)).toBe(UsingFlags.Exact + UsingFlags.Global);
expect(combineFlags(UsingFlags.Multiline, combineFlags(UsingFlags.Exact, UsingFlags.Global))).toBe(UsingFlags.Exact + UsingFlags.Global + UsingFlags.Multiline);
expect(hasFlag(UsingFlags.Exact, UsingFlags.Exact)).toBe(true);
expect(hasFlag(UsingFlags.Exact, UsingFlags.Global)).toBe(false);
expect(hasFlag(UsingFlags.Global + UsingFlags.Exact, UsingFlags.Exact)).toBe(true);
expect(hasFlag(UsingFlags.Global + UsingFlags.Exact, UsingFlags.Multiline)).toBe(false);
expect(hasFlag(combineFlags(UsingFlags.Global, UsingFlags.Exact), UsingFlags.Exact)).toBe(true);
expect(hasFlag(combineFlags(UsingFlags.Global, UsingFlags.Exact), UsingFlags.Global)).toBe(true);
expect(hasFlag(combineFlags(UsingFlags.Global, UsingFlags.Exact), UsingFlags.Multiline)).toBe(false);
});
it("should return correct array elements", function() {
expect(first([ 1, 2, 3 ])).toBe(1);
expect(last([ 1, 2, 3 ])).toBe(3);
});
it("should recognize single regex regular characters", function() {
expect(isSingleRegexCharacter("")).toBe(false);
expect(isSingleRegexCharacter("a")).toBe(true);
expect(isSingleRegexCharacter("ab")).toBe(false);
});
it("should recognize single regex escape characters", function() {
expect(isSingleRegexCharacter("\\n")).toBe(true);
expect(isSingleRegexCharacter("\\r\\n")).toBe(false);
expect(isSingleRegexCharacter("\\na")).toBe(false);
expect(isSingleRegexCharacter("\\?")).toBe(true);
});
it("should recognize single unicode characters", function() {
expect(isSingleRegexCharacter("\\u1")).toBe(false);
expect(isSingleRegexCharacter("\\u1234")).toBe(true);
expect(isSingleRegexCharacter("\\u1234\\u1234")).toBe(false);
expect(isSingleRegexCharacter("\\U12345678")).toBe(false);
expect(isSingleRegexCharacter("\\U1")).toBe(false);
expect(isSingleRegexCharacter("௹")).toBe(true);
expect(isSingleRegexCharacter("💩")).toBe(false);
});
it("should remove quotes correctly", function() {
expect(removeQuotes('""')).toEqual("");
expect(removeQuotes('"hello world"')).toEqual("hello world");
expect(removeQuotes('"hello"world"')).toEqual('hello"world');
});
it("should escape regex correctly", function() {
expect(regexEscape("")).toEqual("");
expect(regexEscape("\\$")).toEqual("\\\\\\$");
expect(regexEscape("^(.*)?\\?$")).toEqual("\\^\\(\\.\\*\\)\\?\\\\\\?\\$");
expect(regexEscape("\\p{Latin}")).toEqual("\\\\p\\{Latin\\}");
});
it("should find the last index of an element", function() {
expect(findLastIndex([], 3)).toBe(-1);
expect(findLastIndex([ 3, 1, 2, 3, 3 ], 3)).toBe(4);
expect(findLastIndex([ 3, 1, 2, 3, 3 ], 1)).toBe(1);
expect(findLastIndex([ 3, 1, 2, 3, 3 ], 9)).toBe(-1);
});
it("should generate CommonErrors correctly", function() {
const lex_error: ILexingError = {
offset: 123,
line: 123,
column: 123,
length: 123,
message: "error"
};
const par_error: IRecognitionException = {
name: "Recognition Exception",
message: "Mismatch at 1,1",
token: createTokenInstance(Indent, "", 123, 124, 123, 123, 123, 124),
resyncedTokens: [],
context: { ruleStack: [], ruleOccurrenceStack: [] }
};
const sem_error: ISemanticError = {
startLine: 123,
startColumn: 123,
length: 123,
message: "error"
};
expect(CommonError.fromLexError(lex_error)).toBeInstanceOf(CommonError);
expect(CommonError.fromParseError(par_error)).toBeInstanceOf(CommonError);
expect(CommonError.fromSemanticError(sem_error)).toBeInstanceOf(CommonError);
expect(() => CommonError.fromSemanticError(sem_error).toString()).not.toThrow();
expect(CommonError.fromSemanticError(sem_error).toString()).not.toBeNull();
});
});