clarity-pattern-parser
Version:
Parsing Library for Typescript and Javascript.
736 lines (642 loc) • 31.5 kB
text/typescript
import { Grammar } from "../../grammar/Grammar";
import { Pattern } from "../../patterns/Pattern";
import { typescriptGrammar } from "./typescript";
let patterns: Record<string, Pattern>;
beforeAll(() => {
patterns = Grammar.parseString(typescriptGrammar);
});
function parse(patternName: string, input: string) {
const pattern = patterns[patternName];
if (!pattern) {
throw new Error(`Pattern "${patternName}" not found`);
}
return pattern.exec(input);
}
function expectParse(patternName: string, input: string) {
const result = parse(patternName, input);
expect(result.ast).not.toBeNull();
expect(result.ast?.value).toBe(input);
}
function getAst(patternName: string, input: string) {
const result = parse(patternName, input);
expect(result.ast).not.toBeNull();
expect(result.ast?.value).toBe(input);
return result.ast!;
}
function expectFail(patternName: string, input: string) {
const result = parse(patternName, input);
expect(result.ast).toBeNull();
}
// =====================================================
// SECTION 1: Literals
// =====================================================
describe("Number Literals", () => {
test("integer", () => expectParse("number-literal", "42"));
test("float", () => expectParse("number-literal", "3.14"));
test("leading dot", () => expectParse("number-literal", ".5"));
test("scientific", () => expectParse("number-literal", "1.2e10"));
test("scientific negative", () => expectParse("number-literal", "1e-3"));
test("hex", () => expectParse("number-literal", "0xFF"));
test("octal", () => expectParse("number-literal", "0o77"));
test("binary", () => expectParse("number-literal", "0b1010"));
test("bigint", () => expectParse("number-literal", "100n"));
test("separator", () => expectParse("number-literal", "1_000_000"));
});
describe("String Literals", () => {
test("double-quoted", () => expectParse("string-literal", '"hello"'));
test("single-quoted", () => expectParse("string-literal", "'world'"));
test("escape sequences", () => expectParse("string-literal", '"line\\n"'));
test("empty", () => expectParse("string-literal", '""'));
});
describe("Template Literals", () => {
test("simple", () => expectParse("template-literal", "`hello`"));
test("with interpolation", () => expectParse("template-literal", "`hello ${name}`"));
test("multiple spans", () => expectParse("template-literal", "`${a} and ${b}`"));
test("nested expression", () => expectParse("template-literal", "`${a + b}`"));
});
describe("Regex Literals", () => {
test("simple", () => expectParse("regex-literal", "/abc/"));
test("with flags", () => expectParse("regex-literal", "/abc/gi"));
test("with character class", () => expectParse("regex-literal", "/[a-z]+/"));
test("with escape", () => expectParse("regex-literal", "/\\d+/"));
});
describe("Keyword Literals", () => {
test("true", () => expectParse("true-literal", "true"));
test("false", () => expectParse("false-literal", "false"));
test("null", () => expectParse("null-literal", "null"));
test("undefined", () => expectParse("undefined-literal", "undefined"));
test("this", () => expectParse("this-literal", "this"));
});
// =====================================================
// SECTION 2: Identifiers
// =====================================================
describe("Identifiers", () => {
test("simple", () => expectParse("identifier", "foo"));
test("with underscore", () => expectParse("identifier", "_private"));
test("with dollar", () => expectParse("identifier", "$element"));
test("camelCase", () => expectParse("identifier", "myVariable"));
test("rejects reserved words", () => expectFail("identifier", "class"));
test("rejects if", () => expectFail("identifier", "if"));
test("allows contextual keywords", () => expectParse("identifier", "type"));
test("allows 'as'", () => expectParse("identifier", "as"));
test("allows 'from'", () => expectParse("identifier", "from"));
});
// =====================================================
// SECTION 3: Type Expressions
// =====================================================
describe("Primitive Types", () => {
test("string", () => expectParse("type-expr", "string"));
test("number", () => expectParse("type-expr", "number"));
test("boolean", () => expectParse("type-expr", "boolean"));
test("void", () => expectParse("type-expr", "void"));
test("never", () => expectParse("type-expr", "never"));
test("any", () => expectParse("type-expr", "any"));
test("unknown", () => expectParse("type-expr", "unknown"));
test("null", () => expectParse("type-expr", "null"));
test("undefined", () => expectParse("type-expr", "undefined"));
test("symbol", () => expectParse("type-expr", "symbol"));
test("bigint", () => expectParse("type-expr", "bigint"));
test("object", () => expectParse("type-expr", "object"));
});
describe("Type References", () => {
test("simple", () => expectParse("type-expr", "Foo"));
test("qualified", () => expectParse("type-expr", "A.B.C"));
test("generic", () => expectParse("type-expr", "Array<string>"));
test("multiple generics", () => expectParse("type-expr", "Map<string, number>"));
test("nested generics", () => expectParse("type-expr", "Promise<Array<string>>"));
});
describe("Literal Types", () => {
test("string literal type", () => expectParse("type-expr", '"hello"'));
test("number literal type", () => expectParse("type-expr", "42"));
test("true type", () => expectParse("type-expr", "true"));
test("false type", () => expectParse("type-expr", "false"));
});
describe("Union & Intersection Types", () => {
test("union", () => expectParse("type-expr", "string | number"));
test("triple union", () => expectParse("type-expr", "string | number | boolean"));
test("intersection", () => expectParse("type-expr", "A & B"));
test("intersection binds tighter", () => {
const ast = getAst("type-expr", "A & B | C");
// A & B should bind tighter than |
expect(ast.name).toBe("union-type");
});
});
describe("Array Types", () => {
test("simple array", () => expectParse("type-expr", "string[]"));
test("nested array", () => expectParse("type-expr", "number[][]"));
});
describe("Tuple Types", () => {
test("simple tuple", () => expectParse("type-expr", "[string, number]"));
test("optional element", () => expectParse("type-expr", "[string, number?]"));
test("rest element", () => expectParse("type-expr", "[string, ...number[]]"));
test("labeled", () => expectParse("type-expr", "[x: string, y: number]"));
test("empty tuple", () => expectParse("type-expr", "[]"));
});
describe("Object Type Literals", () => {
test("simple", () => expectParse("type-expr", "{ x: number; y: string }"));
test("optional property", () => expectParse("type-expr", "{ x?: number }"));
test("readonly property", () => expectParse("type-expr", "{ readonly x: number }"));
test("index signature", () => expectParse("type-expr", "{ [key: string]: number }"));
test("empty", () => expectParse("type-expr", "{}"));
});
describe("Function Types", () => {
test("simple", () => expectParse("type-expr", "() => void"));
test("with params", () => expectParse("type-expr", "(x: string) => number"));
test("generic function type", () => expectParse("type-expr", "<T>(x: T) => T"));
test("return type is union", () => expectParse("type-expr", "() => string | undefined"));
});
describe("Conditional Types", () => {
test("simple", () => expectParse("type-expr", "T extends string ? true : false"));
test("with union checked type", () => expectParse("type-expr", "A | B extends C ? D : E"));
});
describe("Keyof & Typeof Types", () => {
test("keyof", () => expectParse("type-expr", "keyof T"));
test("typeof", () => expectParse("type-expr", "typeof x"));
test("typeof qualified", () => expectParse("type-expr", "typeof x.y.z"));
test("keyof typeof", () => expectParse("type-expr", "keyof typeof x"));
});
describe("Mapped Types", () => {
test("simple mapped", () => expectParse("type-expr", "{ [K in keyof T]: T[K] }"));
test("with optional modifier", () => expectParse("type-expr", "{ [K in keyof T]?: T[K] }"));
test("with readonly modifier", () => expectParse("type-expr", "{ readonly [K in keyof T]: T[K] }"));
test("with as clause", () => expectParse("type-expr", "{ [K in keyof T as string]: T[K] }"));
});
describe("Indexed Access Types", () => {
test("simple", () => expectParse("type-expr", "T[K]"));
test("nested", () => expectParse("type-expr", 'T["key"]'));
});
describe("Infer Types", () => {
test("simple infer", () => expectParse("type-expr", "infer T"));
test("constrained infer", () => expectParse("type-expr", "infer T extends string"));
});
describe("Parenthesized Types", () => {
test("simple", () => expectParse("type-expr", "(string)"));
test("union in parens", () => expectParse("type-expr", "(string | number)"));
test("function in parens", () => expectParse("type-expr", "(() => void)"));
});
describe("Type Parameters", () => {
test("simple", () => expectParse("type-params", "<T>"));
test("multiple", () => expectParse("type-params", "<T, U>"));
test("with constraint", () => expectParse("type-params", "<T extends string>"));
test("with default", () => expectParse("type-params", "<T = string>"));
test("complex", () => expectParse("type-params", "<T extends object, U = T>"));
});
// =====================================================
// SECTION 4: Value Expressions
// =====================================================
describe("Primary Expressions", () => {
test("identifier", () => expectParse("expr", "foo"));
test("number", () => expectParse("expr", "42"));
test("string", () => expectParse("expr", '"hello"'));
test("true", () => expectParse("expr", "true"));
test("false", () => expectParse("expr", "false"));
test("null", () => expectParse("expr", "null"));
test("undefined", () => expectParse("expr", "undefined"));
test("this", () => expectParse("expr", "this"));
test("template literal", () => expectParse("expr", "`hello ${x}`"));
test("regex", () => expectParse("expr", "/test/g"));
test("parenthesized", () => expectParse("expr", "(x)"));
});
describe("Array Expressions", () => {
test("empty", () => expectParse("expr", "[]"));
test("simple", () => expectParse("expr", "[1, 2, 3]"));
test("trailing comma", () => expectParse("expr", "[1, 2,]"));
test("spread", () => expectParse("expr", "[...arr]"));
test("mixed", () => expectParse("expr", "[1, ...arr, 3]"));
});
describe("Object Expressions", () => {
test("empty", () => expectParse("expr", "{}"));
test("properties", () => expectParse("expr", "{ a: 1, b: 2 }"));
test("shorthand", () => expectParse("expr", "{ x, y }"));
test("computed", () => expectParse("expr", "{ [key]: value }"));
test("spread", () => expectParse("expr", "{ ...other }"));
test("method", () => expectParse("expr", "{ foo() { } }"));
test("getter", () => expectParse("expr", "{ get x() { return 1 } }"));
test("setter", () => expectParse("expr", "{ set x(v) { } }"));
test("trailing comma", () => expectParse("expr", "{ a: 1, }"));
});
describe("Member Access", () => {
test("dot access", () => expectParse("expr", "a.b"));
test("chained", () => expectParse("expr", "a.b.c"));
test("optional chain", () => expectParse("expr", "a?.b"));
test("computed", () => expectParse("expr", "a[0]"));
test("optional computed", () => expectParse("expr", "a?.[0]"));
});
describe("Call Expressions", () => {
test("simple call", () => expectParse("expr", "foo()"));
test("with args", () => expectParse("expr", "foo(a, b)"));
test("chained", () => expectParse("expr", "foo()()"));
test("method call", () => expectParse("expr", "obj.method()"));
test("optional call", () => expectParse("expr", "foo?.()"));
test("generic call", () => expectParse("expr", "foo<string>(x)"));
});
describe("Arithmetic Operators", () => {
test("addition", () => expectParse("expr", "a + b"));
test("subtraction", () => expectParse("expr", "a - b"));
test("multiplication", () => expectParse("expr", "a * b"));
test("division", () => expectParse("expr", "a / b"));
test("modulo", () => expectParse("expr", "a % b"));
test("exponentiation", () => expectParse("expr", "a ** b"));
});
describe("Operator Precedence", () => {
test("mul before add", () => {
const ast = getAst("expr", "a + b * c");
// Should parse as a + (b * c)
expect(ast.name).toBe("add-sub");
});
test("parentheses override", () => {
const ast = getAst("expr", "(a + b) * c");
expect(ast.name).toBe("mul-div-mod");
});
test("complex precedence", () => expectParse("expr", "a + b * c - d / e"));
});
describe("Comparison Operators", () => {
test("less than", () => expectParse("expr", "a < b"));
test("greater than", () => expectParse("expr", "a > b"));
test("less equal", () => expectParse("expr", "a <= b"));
test("greater equal", () => expectParse("expr", "a >= b"));
test("strict equal", () => expectParse("expr", "a === b"));
test("strict not equal", () => expectParse("expr", "a !== b"));
test("instanceof", () => expectParse("expr", "a instanceof B"));
test("in", () => expectParse("expr", "a in b"));
});
describe("Logical Operators", () => {
test("and", () => expectParse("expr", "a && b"));
test("or", () => expectParse("expr", "a || b"));
test("nullish coalescing", () => expectParse("expr", "a ?? b"));
test("not", () => expectParse("expr", "!a"));
});
describe("Bitwise Operators", () => {
test("and", () => expectParse("expr", "a & b"));
test("or", () => expectParse("expr", "a | b"));
test("xor", () => expectParse("expr", "a ^ b"));
test("not", () => expectParse("expr", "~a"));
test("left shift", () => expectParse("expr", "a << b"));
test("right shift", () => expectParse("expr", "a >> b"));
test("unsigned right shift", () => expectParse("expr", "a >>> b"));
});
describe("Assignment Operators", () => {
test("assign", () => expectParse("expr", "a = b"));
test("add assign", () => expectParse("expr", "a += b"));
test("sub assign", () => expectParse("expr", "a -= b"));
test("mul assign", () => expectParse("expr", "a *= b"));
test("div assign", () => expectParse("expr", "a /= b"));
test("mod assign", () => expectParse("expr", "a %= b"));
test("and assign", () => expectParse("expr", "a &&= b"));
test("or assign", () => expectParse("expr", "a ||= b"));
test("nullish assign", () => expectParse("expr", "a ??= b"));
});
describe("Unary Operators", () => {
test("prefix increment", () => expectParse("expr", "++a"));
test("prefix decrement", () => expectParse("expr", "--a"));
test("postfix increment", () => expectParse("expr", "a++"));
test("postfix decrement", () => expectParse("expr", "a--"));
test("unary plus", () => expectParse("expr", "+a"));
test("unary minus", () => expectParse("expr", "-a"));
test("typeof", () => expectParse("expr", "typeof x"));
test("void", () => expectParse("expr", "void 0"));
test("delete", () => expectParse("expr", "delete obj.prop"));
test("await", () => expectParse("expr", "await promise"));
});
describe("Ternary Operator", () => {
test("simple", () => expectParse("expr", "a ? b : c"));
test("nested (right-associative)", () => expectParse("expr", "a ? b : c ? d : e"));
test("with complex operands", () => expectParse("expr", "x > 0 ? x : -x"));
});
describe("Arrow Functions", () => {
test("single param no parens", () => expectParse("expr", "x => x"));
test("single param with parens", () => expectParse("expr", "(x) => x"));
test("multiple params", () => expectParse("expr", "(x, y) => x + y"));
test("no params", () => expectParse("expr", "() => 42"));
test("block body", () => expectParse("expr", "() => { return 1 }"));
test("typed params", () => expectParse("expr", "(x: number) => x"));
test("return type", () => expectParse("expr", "(x: number): number => x"));
test("async", () => expectParse("expr", "async () => await fetch()"));
test("generic", () => expectParse("expr", "<T>(x: T) => x"));
});
describe("Function Expressions", () => {
test("anonymous", () => expectParse("expr", "function() { }"));
test("named", () => expectParse("expr", "function foo() { }"));
test("async", () => expectParse("expr", "async function() { }"));
test("generator", () => expectParse("expr", "function*() { }"));
test("with params", () => expectParse("expr", "function(a, b) { return a + b }"));
test("typed", () => expectParse("expr", "function(x: number): number { return x }"));
});
describe("New Expressions", () => {
test("simple", () => expectParse("expr", "new Foo()"));
test("with args", () => expectParse("expr", "new Foo(1, 2)"));
test("member new", () => expectParse("expr", "new a.b.Foo()"));
});
describe("Type Assertions", () => {
test("as expression", () => expectParse("expr", "x as string"));
test("as const", () => expectParse("expr", "x as const"));
test("satisfies", () => expectParse("expr", "x satisfies Type"));
test("non-null assertion", () => expectParse("expr", "x!"));
});
describe("Dynamic Import", () => {
test("simple", () => expectParse("expr", 'import("module")'));
});
// =====================================================
// SECTION 5: Statements
// =====================================================
describe("Variable Declarations", () => {
test("let", () => expectParse("statement", "let x = 1"));
test("const", () => expectParse("statement", "const y = 2"));
test("var", () => expectParse("statement", "var z = 3"));
test("with type", () => expectParse("statement", "let x: number = 1"));
test("multiple", () => expectParse("statement", "let a = 1, b = 2"));
test("destructure array", () => expectParse("statement", "const [a, b] = arr"));
test("destructure object", () => expectParse("statement", "const { x, y } = obj"));
test("with semicolon", () => expectParse("statement", "let x = 1;"));
test("using", () => expectParse("statement", "using handle = getHandle()"));
});
describe("If Statements", () => {
test("simple if", () => expectParse("statement", "if (x) foo()"));
test("if-else", () => expectParse("statement", "if (x) foo(); else bar()"));
test("if with block", () => expectParse("statement", "if (x) { foo() }"));
test("if-else if-else", () =>
expectParse("statement", "if (a) x(); else if (b) y(); else z()"));
});
describe("For Statements", () => {
test("classic for", () => expectParse("statement", "for (let i = 0; i < 10; i++) { }"));
test("for-in", () => expectParse("statement", "for (const key in obj) { }"));
test("for-of", () => expectParse("statement", "for (const item of arr) { }"));
test("for-await-of", () => expectParse("statement", "for await (const item of stream) { }"));
test("for with empty clauses", () => expectParse("statement", "for (;;) { }"));
});
describe("While Statements", () => {
test("while", () => expectParse("statement", "while (true) { }"));
test("do-while", () => expectParse("statement", "do { } while (true)"));
});
describe("Switch Statements", () => {
test("simple switch", () =>
expectParse("statement", "switch (x) { case 1: break; default: break }"));
test("multiple cases", () =>
expectParse("statement", "switch (x) { case 1: a(); case 2: b(); default: c() }"));
});
describe("Try-Catch-Finally", () => {
test("try-catch", () => expectParse("statement", "try { } catch (e) { }"));
test("try-finally", () => expectParse("statement", "try { } finally { }"));
test("try-catch-finally", () =>
expectParse("statement", "try { } catch (e) { } finally { }"));
test("catch without binding", () => expectParse("statement", "try { } catch { }"));
test("typed catch", () =>
expectParse("statement", "try { } catch (e: unknown) { }"));
});
describe("Jump Statements", () => {
test("return", () => expectParse("statement", "return"));
test("return value", () => expectParse("statement", "return x"));
test("throw", () => expectParse("statement", "throw new Error()"));
test("break", () => expectParse("statement", "break"));
test("break label", () => expectParse("statement", "break outer"));
test("continue", () => expectParse("statement", "continue"));
test("continue label", () => expectParse("statement", "continue loop"));
});
describe("Other Statements", () => {
test("block", () => expectParse("statement", "{ let x = 1 }"));
test("empty", () => expectParse("statement", ";"));
test("debugger", () => expectParse("statement", "debugger"));
test("labeled", () => expectParse("statement", "loop: while (true) { }"));
test("expression statement", () => expectParse("statement", "foo()"));
});
// =====================================================
// SECTION 6: Declarations
// =====================================================
describe("Function Declarations", () => {
test("simple", () => expectParse("statement", "function foo() { }"));
test("with params", () =>
expectParse("statement", "function add(a: number, b: number): number { return a + b }"));
test("async", () => expectParse("statement", "async function fetch() { }"));
test("generator", () => expectParse("statement", "function* gen() { }"));
test("generic", () =>
expectParse("statement", "function identity<T>(x: T): T { return x }"));
test("rest param", () =>
expectParse("statement", "function foo(...args: string[]) { }"));
test("default param", () =>
expectParse("statement", "function foo(x = 10) { }"));
test("optional param", () =>
expectParse("statement", "function foo(x?: number) { }"));
});
describe("Class Declarations", () => {
test("empty class", () => expectParse("statement", "class Foo { }"));
test("with property", () =>
expectParse("statement", "class Foo { x: number }"));
test("with method", () =>
expectParse("statement", "class Foo { bar() { } }"));
test("with constructor", () =>
expectParse("statement", "class Foo { constructor(x: number) { } }"));
test("extends", () =>
expectParse("statement", "class Bar extends Foo { }"));
test("implements", () =>
expectParse("statement", "class Bar implements IFoo { }"));
test("generic class", () =>
expectParse("statement", "class Box<T> { value: T }"));
test("access modifiers", () =>
expectParse("statement", "class Foo { public x: number; private y: string; protected z: boolean }"));
test("static members", () =>
expectParse("statement", "class Foo { static count: number; static create() { } }"));
test("abstract class", () =>
expectParse("statement", "abstract class Shape { abstract area(): number }"));
test("getter/setter", () =>
expectParse("statement", "class Foo { get x() { return 1 } set x(v) { } }"));
test("readonly property", () =>
expectParse("statement", "class Foo { readonly x: number }"));
test("parameter property", () =>
expectParse("statement", "class Foo { constructor(public readonly name: string) { } }"));
test("static block", () =>
expectParse("statement", "class Foo { static { init() } }"));
test("optional property", () =>
expectParse("statement", "class Foo { x?: number }"));
test("definite assignment", () =>
expectParse("statement", "class Foo { x!: number }"));
});
describe("Interface Declarations", () => {
test("empty", () => expectParse("statement", "interface Foo { }"));
test("with properties", () =>
expectParse("statement", "interface Foo { x: number; y: string }"));
test("optional properties", () =>
expectParse("statement", "interface Foo { x?: number }"));
test("readonly", () =>
expectParse("statement", "interface Foo { readonly x: number }"));
test("methods", () =>
expectParse("statement", "interface Foo { bar(x: number): string }"));
test("extends", () =>
expectParse("statement", "interface Bar extends Foo { }"));
test("extends multiple", () =>
expectParse("statement", "interface Bar extends Foo, Baz { }"));
test("generic", () =>
expectParse("statement", "interface Box<T> { value: T }"));
test("index signature", () =>
expectParse("statement", "interface Dict { [key: string]: number }"));
test("call signature", () =>
expectParse("statement", "interface Callable { (x: string): number }"));
});
describe("Type Aliases", () => {
test("simple", () => expectParse("statement", "type Name = string"));
test("union", () => expectParse("statement", "type StringOrNumber = string | number"));
test("generic", () => expectParse("statement", "type Box<T> = { value: T }"));
test("conditional", () =>
expectParse("statement", "type IsString<T> = T extends string ? true : false"));
test("mapped", () =>
expectParse("statement", "type Readonly<T> = { readonly [K in keyof T]: T[K] }"));
test("template literal", () =>
expectParse("statement", "type Greeting = `hello ${string}`"));
});
describe("Enum Declarations", () => {
test("simple", () => expectParse("statement", "enum Color { Red, Green, Blue }"));
test("with values", () =>
expectParse("statement", 'enum Direction { Up = "UP", Down = "DOWN" }'));
test("const enum", () =>
expectParse("statement", "const enum Flags { A = 1, B = 2, C = 4 }"));
test("computed value", () =>
expectParse("statement", "enum Foo { A = 1 + 2 }"));
});
describe("Namespace Declarations", () => {
test("namespace", () =>
expectParse("statement", "namespace MyLib { }"));
test("module", () =>
expectParse("statement", 'module "my-module" { }'));
test("nested namespace", () =>
expectParse("statement", "namespace Outer { namespace Inner { } }"));
});
describe("Declare Statements", () => {
test("declare var", () => expectParse("statement", "declare var x: number"));
test("declare function", () =>
expectParse("statement", "declare function foo(): void { }"));
test("declare class", () =>
expectParse("statement", "declare class Foo { }"));
test("declare namespace", () =>
expectParse("statement", "declare namespace MyLib { }"));
test("declare enum", () =>
expectParse("statement", "declare enum Color { Red, Green, Blue }"));
});
describe("Decorators", () => {
test("class decorator", () =>
expectParse("statement", "@Component class Foo { }"));
test("decorator with args", () =>
expectParse("statement", '@Component({ selector: "app" }) class Foo { }'));
test("multiple decorators", () =>
expectParse("statement", "@A @B class Foo { }"));
test("member decorator", () =>
expectParse("statement", "class Foo { @Log method() { } }"));
});
// =====================================================
// SECTION 7: Module System
// =====================================================
describe("Import Declarations", () => {
test("default import", () =>
expectParse("statement", 'import foo from "module"'));
test("named import", () =>
expectParse("statement", 'import { foo } from "module"'));
test("multiple named", () =>
expectParse("statement", 'import { foo, bar } from "module"'));
test("aliased import", () =>
expectParse("statement", 'import { foo as bar } from "module"'));
test("namespace import", () =>
expectParse("statement", 'import * as mod from "module"'));
test("default and named", () =>
expectParse("statement", 'import React, { useState } from "react"'));
test("side-effect import", () =>
expectParse("statement", 'import "polyfill"'));
test("type import", () =>
expectParse("statement", 'import type { Foo } from "module"'));
test("with semicolon", () =>
expectParse("statement", 'import { x } from "y";'));
});
describe("Export Declarations", () => {
test("export variable", () =>
expectParse("statement", "export const x = 1"));
test("export function", () =>
expectParse("statement", "export function foo() { }"));
test("export class", () =>
expectParse("statement", "export class Foo { }"));
test("export interface", () =>
expectParse("statement", "export interface Foo { }"));
test("export type", () =>
expectParse("statement", "export type Foo = string"));
test("export enum", () =>
expectParse("statement", "export enum Color { Red }"));
test("export default expression", () =>
expectParse("statement", "export default 42"));
test("export default function", () =>
expectParse("statement", "export default function foo() { }"));
test("export default class", () =>
expectParse("statement", "export default class Foo { }"));
test("named exports", () =>
expectParse("statement", "export { foo, bar }"));
test("re-export", () =>
expectParse("statement", 'export { foo } from "module"'));
test("export all", () =>
expectParse("statement", 'export * from "module"'));
test("export all as", () =>
expectParse("statement", 'export * as utils from "module"'));
test("export type", () =>
expectParse("statement", 'export type { Foo } from "module"'));
});
// =====================================================
// SECTION 8: Program (Multi-statement)
// =====================================================
describe("Program", () => {
test("empty program", () => {
// Empty program: pattern matches but value might be empty
const result = parse("program", "");
// Empty string may not produce an AST node since ws matches nothing
// and statement-list? is optional - this is OK
expect(result.ast === null || result.ast.value === "").toBeTruthy();
});
test("single statement", () => expectParse("program", "const x = 1"));
test("multiple statements", () =>
expectParse("program", `let x = 1
let y = 2`));
test("realistic program", () =>
expectParse("program", `import { Component } from "react"
interface Props {
name: string;
age?: number
}
class App extends Component {
render() { return null }
}`));
test("function with generics", () =>
expectParse("program", `function identity<T>(arg: T): T {
return arg
}
const result = identity<string>("hello")`));
test("async/await", () =>
expectParse("program", `async function fetchData() {
const response = await fetch("url")
return response
}`));
test("destructuring and spread", () =>
expectParse("program", `const { a, b, ...rest } = obj
const [first, ...remaining] = arr`));
test("enum and type alias", () =>
expectParse("program", `enum Status {
Active,
Inactive
}
type UserStatus = Status.Active | Status.Inactive`));
test("complex type operations", () =>
expectParse("program", `type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T`));
});
// =====================================================
// SECTION 9: Edge Cases & Complex Patterns
// =====================================================
describe("Edge Cases", () => {
test("chained member access and calls", () =>
expectParse("expr", "a.b().c[0].d"));
test("nested ternary", () =>
expectParse("expr", "a ? b ? c : d : e"));
test("arrow in assignment", () =>
expectParse("expr", "x = () => y"));
test("complex expression", () =>
expectParse("expr", "a + b * c > d && e || f"));
test("type assertion chain", () =>
expectParse("expr", "x as any as string"));
test("optional chaining mixed", () =>
expectParse("expr", "a?.b.c?.d"));
test("tagged template", () =>
expectParse("expr", "html`<div>${x}</div>`"));
test("new with member access", () =>
expectParse("expr", "new a.B()"));
});