mirror of
https://github.com/TriliumNext/Notes.git
synced 2026-01-06 04:50:03 -06:00
chore(tests): move tests next to actual file
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import lex from "../../src/services/search/services/lex.js";
|
||||
|
||||
describe("Lexer fulltext", () => {
|
||||
it("simple lexing", () => {
|
||||
expect(lex("hello world").fulltextTokens.map((t) => t.token)).toEqual(["hello", "world"]);
|
||||
|
||||
expect(lex("hello, world").fulltextTokens.map((t) => t.token)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
it("use quotes to keep words together", () => {
|
||||
expect(lex("'hello world' my friend").fulltextTokens.map((t) => t.token)).toEqual(["hello world", "my", "friend"]);
|
||||
|
||||
expect(lex('"hello world" my friend').fulltextTokens.map((t) => t.token)).toEqual(["hello world", "my", "friend"]);
|
||||
|
||||
expect(lex("`hello world` my friend").fulltextTokens.map((t) => t.token)).toEqual(["hello world", "my", "friend"]);
|
||||
});
|
||||
|
||||
it("you can use different quotes and other special characters inside quotes", () => {
|
||||
expect(lex("'i can use \" or ` or #~=*' without problem").fulltextTokens.map((t) => t.token)).toEqual(['i can use " or ` or #~=*', "without", "problem"]);
|
||||
});
|
||||
|
||||
it("I can use backslash to escape quotes", () => {
|
||||
expect(lex('hello \\"world\\"').fulltextTokens.map((t) => t.token)).toEqual(["hello", '"world"']);
|
||||
|
||||
expect(lex("hello \\'world\\'").fulltextTokens.map((t) => t.token)).toEqual(["hello", "'world'"]);
|
||||
|
||||
expect(lex("hello \\`world\\`").fulltextTokens.map((t) => t.token)).toEqual(["hello", "`world`"]);
|
||||
|
||||
expect(lex('"hello \\"world\\"').fulltextTokens.map((t) => t.token)).toEqual(['hello "world"']);
|
||||
|
||||
expect(lex("'hello \\'world\\''").fulltextTokens.map((t) => t.token)).toEqual(["hello 'world'"]);
|
||||
|
||||
expect(lex("`hello \\`world\\``").fulltextTokens.map((t) => t.token)).toEqual(["hello `world`"]);
|
||||
|
||||
expect(lex("\\#token").fulltextTokens.map((t) => t.token)).toEqual(["#token"]);
|
||||
});
|
||||
|
||||
it("quote inside a word does not have a special meaning", () => {
|
||||
const lexResult = lex("d'Artagnan is dead #hero = d'Artagnan");
|
||||
|
||||
expect(lexResult.fulltextTokens.map((t) => t.token)).toEqual(["d'artagnan", "is", "dead"]);
|
||||
|
||||
expect(lexResult.expressionTokens.map((t) => t.token)).toEqual(["#hero", "=", "d'artagnan"]);
|
||||
});
|
||||
|
||||
it("if quote is not ended then it's just one long token", () => {
|
||||
expect(lex("'unfinished quote").fulltextTokens.map((t) => t.token)).toEqual(["unfinished quote"]);
|
||||
});
|
||||
|
||||
it("parenthesis and symbols in fulltext section are just normal characters", () => {
|
||||
expect(lex("what's u=p <b(r*t)h>").fulltextTokens.map((t) => t.token)).toEqual(["what's", "u=p", "<b(r*t)h>"]);
|
||||
});
|
||||
|
||||
it("operator characters in expressions are separate tokens", () => {
|
||||
expect(lex("# abc+=-def**-+d").expressionTokens.map((t) => t.token)).toEqual(["#", "abc", "+=-", "def", "**-+", "d"]);
|
||||
});
|
||||
|
||||
it("escaping special characters", () => {
|
||||
expect(lex("hello \\#\\~\\'").fulltextTokens.map((t) => t.token)).toEqual(["hello", "#~'"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Lexer expression", () => {
|
||||
it("simple attribute existence", () => {
|
||||
expect(lex("#label ~relation").expressionTokens.map((t) => t.token)).toEqual(["#label", "~relation"]);
|
||||
});
|
||||
|
||||
it("simple label operators", () => {
|
||||
expect(lex("#label*=*text").expressionTokens.map((t) => t.token)).toEqual(["#label", "*=*", "text"]);
|
||||
});
|
||||
|
||||
it("simple label operator with in quotes", () => {
|
||||
expect(lex("#label*=*'text'").expressionTokens).toEqual([
|
||||
{ token: "#label", inQuotes: false, startIndex: 0, endIndex: 5 },
|
||||
{ token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8 },
|
||||
{ token: "text", inQuotes: true, startIndex: 10, endIndex: 13 }
|
||||
]);
|
||||
});
|
||||
|
||||
it("simple label operator with param without quotes", () => {
|
||||
expect(lex("#label*=*text").expressionTokens).toEqual([
|
||||
{ token: "#label", inQuotes: false, startIndex: 0, endIndex: 5 },
|
||||
{ token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8 },
|
||||
{ token: "text", inQuotes: false, startIndex: 9, endIndex: 12 }
|
||||
]);
|
||||
});
|
||||
|
||||
it("simple label operator with empty string param", () => {
|
||||
expect(lex("#label = ''").expressionTokens).toEqual([
|
||||
{ token: "#label", inQuotes: false, startIndex: 0, endIndex: 5 },
|
||||
{ token: "=", inQuotes: false, startIndex: 7, endIndex: 7 },
|
||||
// weird case for empty strings which ends up with endIndex < startIndex :-(
|
||||
{ token: "", inQuotes: true, startIndex: 10, endIndex: 9 }
|
||||
]);
|
||||
});
|
||||
|
||||
it("note. prefix also separates fulltext from expression", () => {
|
||||
expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map((t) => t.token)).toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]);
|
||||
});
|
||||
|
||||
it("note. prefix in quotes will note start expression", () => {
|
||||
expect(lex(`hello fulltext "note.txt"`).expressionTokens.map((t) => t.token)).toEqual([]);
|
||||
|
||||
expect(lex(`hello fulltext "note.txt"`).fulltextTokens.map((t) => t.token)).toEqual(["hello", "fulltext", "note.txt"]);
|
||||
});
|
||||
|
||||
it("complex expressions with and, or and parenthesis", () => {
|
||||
expect(lex(`# (#label=text OR #second=text) AND ~relation`).expressionTokens.map((t) => t.token)).toEqual([
|
||||
"#",
|
||||
"(",
|
||||
"#label",
|
||||
"=",
|
||||
"text",
|
||||
"or",
|
||||
"#second",
|
||||
"=",
|
||||
"text",
|
||||
")",
|
||||
"and",
|
||||
"~relation"
|
||||
]);
|
||||
});
|
||||
|
||||
it("dot separated properties", () => {
|
||||
expect(lex(`# ~author.title = 'Hugh Howey' AND note.'book title' = 'Silo'`).expressionTokens.map((t) => t.token)).toEqual([
|
||||
"#",
|
||||
"~author",
|
||||
".",
|
||||
"title",
|
||||
"=",
|
||||
"hugh howey",
|
||||
"and",
|
||||
"note",
|
||||
".",
|
||||
"book title",
|
||||
"=",
|
||||
"silo"
|
||||
]);
|
||||
});
|
||||
|
||||
it("negation of label and relation", () => {
|
||||
expect(lex(`#!capital ~!neighbor`).expressionTokens.map((t) => t.token)).toEqual(["#!capital", "~!neighbor"]);
|
||||
});
|
||||
|
||||
it("negation of sub-expression", () => {
|
||||
expect(lex(`# not(#capital) and note.noteId != "root"`).expressionTokens.map((t) => t.token)).toEqual(["#", "not", "(", "#capital", ")", "and", "note", ".", "noteid", "!=", "root"]);
|
||||
});
|
||||
|
||||
it("order by multiple labels", () => {
|
||||
expect(lex(`# orderby #a,#b`).expressionTokens.map((t) => t.token)).toEqual(["#", "orderby", "#a", ",", "#b"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Lexer invalid queries and edge cases", () => {
|
||||
it("concatenated attributes", () => {
|
||||
expect(lex("#label~relation").expressionTokens.map((t) => t.token)).toEqual(["#label", "~relation"]);
|
||||
});
|
||||
|
||||
it("trailing escape \\", () => {
|
||||
expect(lex("abc \\").fulltextTokens.map((t) => t.token)).toEqual(["abc", "\\"]);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import handleParens from "../../src/services/search/services/handle_parens.js";
|
||||
import type { TokenStructure } from "../../src/services/search/services/types.js";
|
||||
|
||||
describe("Parens handler", () => {
|
||||
it("handles parens", () => {
|
||||
const input = ["(", "hello", ")", "and", "(", "(", "pick", "one", ")", "and", "another", ")"].map((token) => ({ token }));
|
||||
|
||||
const actual: TokenStructure = [[{ token: "hello" }], { token: "and" }, [[{ token: "pick" }, { token: "one" }], { token: "and" }, { token: "another" }]];
|
||||
|
||||
expect(handleParens(input)).toEqual(actual);
|
||||
});
|
||||
});
|
||||
@@ -1,356 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import AndExp from "../../src/services/search/expressions/and.js";
|
||||
import AttributeExistsExp from "../../src/services/search/expressions/attribute_exists.js";
|
||||
import type Expression from "../../src/services/search/expressions/expression.js";
|
||||
import LabelComparisonExp from "../../src/services/search/expressions/label_comparison.js";
|
||||
import NotExp from "../../src/services/search/expressions/not.js";
|
||||
import NoteContentFulltextExp from "../../src/services/search/expressions/note_content_fulltext.js";
|
||||
import NoteFlatTextExp from "../../src/services/search/expressions/note_flat_text.js";
|
||||
import OrExp from "../../src/services/search/expressions/or.js";
|
||||
import OrderByAndLimitExp from "../../src/services/search/expressions/order_by_and_limit.js";
|
||||
import PropertyComparisonExp from "../../src/services/search/expressions/property_comparison.js";
|
||||
import SearchContext from "../../src/services/search/search_context.js";
|
||||
import { default as parseInternal, type ParseOpts } from "../../src/services/search/services/parse.js";
|
||||
|
||||
describe("Parser", () => {
|
||||
it("fulltext parser without content", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
expectExpression(rootExp.subExpressions[0], PropertyComparisonExp);
|
||||
const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
|
||||
const flatTextExp = expectExpression(orExp.subExpressions[0], NoteFlatTextExp);
|
||||
expect(flatTextExp.tokens).toEqual(["hello", "hi"]);
|
||||
});
|
||||
|
||||
it("fulltext parser with content", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
|
||||
|
||||
const firstSub = expectExpression(orExp.subExpressions[0], NoteFlatTextExp);
|
||||
expect(firstSub.tokens).toEqual(["hello", "hi"]);
|
||||
|
||||
const secondSub = expectExpression(orExp.subExpressions[1], NoteContentFulltextExp);
|
||||
expect(secondSub.tokens).toEqual(["hello", "hi"]);
|
||||
});
|
||||
|
||||
it("simple label comparison", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#mylabel", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp);
|
||||
expect(labelComparisonExp.attributeType).toEqual("label");
|
||||
expect(labelComparisonExp.attributeName).toEqual("mylabel");
|
||||
expect(labelComparisonExp.comparator).toBeTruthy();
|
||||
});
|
||||
|
||||
it("simple attribute negation", () => {
|
||||
let rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#!mylabel"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
let notExp = expectExpression(rootExp.subExpressions[2], NotExp);
|
||||
let attributeExistsExp = expectExpression(notExp.subExpression, AttributeExistsExp);
|
||||
expect(attributeExistsExp.attributeType).toEqual("label");
|
||||
expect(attributeExistsExp.attributeName).toEqual("mylabel");
|
||||
|
||||
rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["~!myrelation"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
notExp = expectExpression(rootExp.subExpressions[2], NotExp);
|
||||
attributeExistsExp = expectExpression(notExp.subExpression, AttributeExistsExp);
|
||||
expect(attributeExistsExp.attributeType).toEqual("relation");
|
||||
expect(attributeExistsExp.attributeName).toEqual("myrelation");
|
||||
});
|
||||
|
||||
it("simple label AND", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const andExp = expectExpression(rootExp.subExpressions[2], AndExp);
|
||||
const [firstSub, secondSub] = expectSubexpressions(andExp, LabelComparisonExp, LabelComparisonExp);
|
||||
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
expect(secondSub.attributeName).toEqual("second");
|
||||
});
|
||||
|
||||
it("simple label AND without explicit AND", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const andExp = expectExpression(rootExp.subExpressions[2], AndExp);
|
||||
const [firstSub, secondSub] = expectSubexpressions(andExp, LabelComparisonExp, LabelComparisonExp);
|
||||
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
expect(secondSub.attributeName).toEqual("second");
|
||||
});
|
||||
|
||||
it("simple label OR", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
|
||||
const [firstSub, secondSub] = expectSubexpressions(orExp, LabelComparisonExp, LabelComparisonExp);
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
expect(secondSub.attributeName).toEqual("second");
|
||||
});
|
||||
|
||||
it("fulltext and simple label", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens(["hello"]),
|
||||
expressionTokens: tokens(["#mylabel", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
const [firstSub, _, thirdSub, fourth] = expectSubexpressions(rootExp, PropertyComparisonExp, undefined, OrExp, LabelComparisonExp);
|
||||
|
||||
expect(firstSub.propertyName).toEqual("isArchived");
|
||||
|
||||
const noteFlatTextExp = expectExpression(thirdSub.subExpressions[0], NoteFlatTextExp);
|
||||
expect(noteFlatTextExp.tokens).toEqual(["hello"]);
|
||||
|
||||
expect(fourth.attributeName).toEqual("mylabel");
|
||||
});
|
||||
|
||||
it("label sub-expression", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
|
||||
const [firstSub, secondSub] = expectSubexpressions(orExp, LabelComparisonExp, AndExp);
|
||||
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
|
||||
const [firstSubSub, secondSubSub] = expectSubexpressions(secondSub, LabelComparisonExp, LabelComparisonExp);
|
||||
expect(firstSubSub.attributeName).toEqual("second");
|
||||
expect(secondSubSub.attributeName).toEqual("third");
|
||||
});
|
||||
|
||||
it("label sub-expression without explicit operator", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const andExp = expectExpression(rootExp.subExpressions[2], AndExp);
|
||||
const [firstSub, secondSub, thirdSub] = expectSubexpressions(andExp, AttributeExistsExp, OrExp, AttributeExistsExp);
|
||||
|
||||
expect(firstSub.attributeName).toEqual("first");
|
||||
|
||||
const [firstSubSub, secondSubSub] = expectSubexpressions(secondSub, AttributeExistsExp, AttributeExistsExp);
|
||||
expect(firstSubSub.attributeName).toEqual("second");
|
||||
expect(secondSubSub.attributeName).toEqual("third");
|
||||
|
||||
expect(thirdSub.attributeName).toEqual("fourth");
|
||||
});
|
||||
|
||||
it("parses limit without order by", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext({ limit: 2 })
|
||||
}, OrderByAndLimitExp);
|
||||
|
||||
expect(rootExp.limit).toBe(2);
|
||||
expect(rootExp.subExpression).toBeInstanceOf(AndExp);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Invalid expressions", () => {
|
||||
it("incomplete comparison", () => {
|
||||
const searchContext = new SearchContext();
|
||||
|
||||
parseInternal({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "="]),
|
||||
searchContext
|
||||
});
|
||||
|
||||
expect(searchContext.error).toEqual('Misplaced or incomplete expression "="');
|
||||
});
|
||||
|
||||
it("comparison between labels is impossible", () => {
|
||||
let searchContext = new SearchContext();
|
||||
searchContext.originalQuery = "#first = #second";
|
||||
|
||||
parseInternal({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "#second"]),
|
||||
searchContext
|
||||
});
|
||||
|
||||
expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`);
|
||||
|
||||
searchContext = new SearchContext();
|
||||
searchContext.originalQuery = "#first = note.relations.second";
|
||||
|
||||
parseInternal({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]),
|
||||
searchContext
|
||||
});
|
||||
|
||||
expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`);
|
||||
|
||||
const rootExp = parse({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: [
|
||||
{ token: "#first", inQuotes: false },
|
||||
{ token: "=", inQuotes: false },
|
||||
{ token: "#second", inQuotes: true }
|
||||
],
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp);
|
||||
expect(labelComparisonExp.attributeType).toEqual("label");
|
||||
expect(labelComparisonExp.attributeName).toEqual("first");
|
||||
expect(labelComparisonExp.comparator).toBeTruthy();
|
||||
});
|
||||
|
||||
it("searching by relation without note property", () => {
|
||||
const searchContext = new SearchContext();
|
||||
|
||||
parseInternal({
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["~first", "=", "text", "-", "abc"]),
|
||||
searchContext
|
||||
});
|
||||
|
||||
expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""');
|
||||
});
|
||||
});
|
||||
|
||||
type ClassType<T extends Expression> = new (...args: any[]) => T;
|
||||
|
||||
function tokens(toks: (string | string[])[], cur = 0): Array<any> {
|
||||
return toks.map((arg) => {
|
||||
if (Array.isArray(arg)) {
|
||||
return tokens(arg, cur);
|
||||
} else {
|
||||
cur += arg.length;
|
||||
|
||||
return {
|
||||
token: arg,
|
||||
inQuotes: false,
|
||||
startIndex: cur - arg.length,
|
||||
endIndex: cur - 1
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function assertIsArchived(_exp: Expression) {
|
||||
const exp = expectExpression(_exp, PropertyComparisonExp);
|
||||
expect(exp.propertyName).toEqual("isArchived");
|
||||
expect(exp.operator).toEqual("=");
|
||||
expect(exp.comparedValue).toEqual("false");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the corresponding {@link Expression} from plain text, while also expecting the resulting expression to be of the given type.
|
||||
*
|
||||
* @param opts the options for parsing.
|
||||
* @param type the expected type of the expression.
|
||||
* @returns the expression typecasted to the expected type.
|
||||
*/
|
||||
function parse<T extends Expression>(opts: ParseOpts, type: ClassType<T>) {
|
||||
return expectExpression(parseInternal(opts), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the given {@link Expression} to be of the given type.
|
||||
*
|
||||
* @param exp an instance of an {@link Expression}.
|
||||
* @param type a type class such as {@link AndExp}, {@link OrExp}, etc.
|
||||
* @returns the same expression typecasted to the expected type.
|
||||
*/
|
||||
function expectExpression<T extends Expression>(exp: Expression, type: ClassType<T>) {
|
||||
expect(exp).toBeInstanceOf(type);
|
||||
return exp as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* For an {@link AndExp}, it goes through all its subexpressions (up to fourth) and checks their type and returns them as a typecasted array.
|
||||
* Each subexpression can have their own type.
|
||||
*
|
||||
* @param exp the expression containing one or more subexpressions.
|
||||
* @param firstType the type of the first subexpression.
|
||||
* @param secondType the type of the second subexpression.
|
||||
* @param thirdType the type of the third subexpression.
|
||||
* @param fourthType the type of the fourth subexpression.
|
||||
* @returns an array of all the subexpressions (in order) typecasted to their expected type.
|
||||
*/
|
||||
function expectSubexpressions<FirstT extends Expression,
|
||||
SecondT extends Expression,
|
||||
ThirdT extends Expression,
|
||||
FourthT extends Expression>(
|
||||
exp: AndExp,
|
||||
firstType: ClassType<FirstT>,
|
||||
secondType?: ClassType<SecondT>,
|
||||
thirdType?: ClassType<ThirdT>,
|
||||
fourthType?: ClassType<FourthT>): [ FirstT, SecondT, ThirdT, FourthT ]
|
||||
{
|
||||
expectExpression(exp.subExpressions[0], firstType);
|
||||
if (secondType) {
|
||||
expectExpression(exp.subExpressions[1], secondType);
|
||||
}
|
||||
if (thirdType) {
|
||||
expectExpression(exp.subExpressions[2], thirdType);
|
||||
}
|
||||
if (fourthType) {
|
||||
expectExpression(exp.subExpressions[3], fourthType);
|
||||
}
|
||||
return [
|
||||
exp.subExpressions[0] as FirstT,
|
||||
exp.subExpressions[1] as SecondT,
|
||||
exp.subExpressions[2] as ThirdT,
|
||||
exp.subExpressions[3] as FourthT
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user