mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
030857b509
Remove the miette `Report` wrapper and update oxc-miette to v4's narrowed diagnostic and source protocol. AI-assisted by OpenAI Codex.
923 lines
26 KiB
TypeScript
923 lines
26 KiB
TypeScript
import { Worker } from "node:worker_threads";
|
||
import { describe, expect, it, test } from "vitest";
|
||
|
||
import { parse, parseSync } from "../src-js/index.js";
|
||
import { parseSync as parseRawSync } from "./parser.ts";
|
||
import type {
|
||
ExpressionStatement,
|
||
ParserOptions,
|
||
Program,
|
||
TSTypeAliasDeclaration,
|
||
VariableDeclaration,
|
||
} from "../src-js/index.js";
|
||
|
||
describe("parse", () => {
|
||
const code = "/* comment */ foo";
|
||
|
||
it("uses the `lang` option", () => {
|
||
const ret = parseSync("test.vue", code, { lang: "ts" });
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.errors.length).toBe(0);
|
||
});
|
||
|
||
it("matches output", async () => {
|
||
const ret = await parse("test.js", code);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.comments.length).toBe(1);
|
||
|
||
const comment = ret.comments[0];
|
||
expect(comment).toEqual({
|
||
type: "Block",
|
||
start: 0,
|
||
end: 13,
|
||
value: " comment ",
|
||
});
|
||
expect(code.substring(comment.start, comment.end)).toBe("/*" + comment.value + "*/");
|
||
});
|
||
|
||
it("checks semantic", async () => {
|
||
const code = "let x; let x;";
|
||
let ret = await parse("test.js", code);
|
||
expect(ret.errors.length).toBe(0);
|
||
|
||
ret = await parse("test.js", code, {
|
||
showSemanticErrors: true,
|
||
});
|
||
expect(ret.errors.length).toBe(1);
|
||
});
|
||
|
||
describe("sets lang and sourceType", () => {
|
||
const langs: ParserOptions["lang"][] = ["js", "ts", "jsx", "tsx"];
|
||
|
||
test.each(langs)("%s", (lang) => {
|
||
const code = "await(1)";
|
||
const ret = parseSync("test.cjs", code, { lang, sourceType: "script" });
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.sourceType).toBe("script");
|
||
// Parsed as `await(1)`
|
||
expect((ret.program.body[0] as ExpressionStatement).expression.type).toBe("CallExpression");
|
||
});
|
||
|
||
test.each(langs)("%s", (lang) => {
|
||
const code = "await(1)";
|
||
const ret = parseSync("test.cjs", code, { lang, sourceType: "module" });
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.sourceType).toBe("module");
|
||
// Parsed as `await 1`
|
||
expect((ret.program.body[0] as ExpressionStatement).expression.type).toBe("AwaitExpression");
|
||
});
|
||
|
||
test.each(langs)("%s", (lang) => {
|
||
const code = "return 123;";
|
||
const ret = parseSync("test.cjs", code, { lang, sourceType: "commonjs" });
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.sourceType).toBe("commonjs");
|
||
expect(ret.program.body[0].type).toBe("ReturnStatement");
|
||
});
|
||
|
||
test("sets lang as dts", () => {
|
||
const code = "declare const foo";
|
||
const ret = parseSync("test", code, { lang: "dts" });
|
||
expect(ret.errors.length).toBe(0);
|
||
expect((ret.program.body[0] as VariableDeclaration).declare).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe("TS properties", () => {
|
||
const code = "let x;";
|
||
|
||
const withTsFields: Program = {
|
||
type: "Program",
|
||
start: 0,
|
||
end: 6,
|
||
body: [
|
||
{
|
||
type: "VariableDeclaration",
|
||
start: 0,
|
||
end: 6,
|
||
declarations: [
|
||
{
|
||
type: "VariableDeclarator",
|
||
start: 4,
|
||
end: 5,
|
||
id: {
|
||
type: "Identifier",
|
||
start: 4,
|
||
end: 5,
|
||
name: "x",
|
||
decorators: [],
|
||
typeAnnotation: null,
|
||
optional: false,
|
||
},
|
||
init: null,
|
||
definite: false,
|
||
},
|
||
],
|
||
kind: "let",
|
||
declare: false,
|
||
},
|
||
],
|
||
sourceType: "script",
|
||
hashbang: null,
|
||
};
|
||
|
||
const withoutTsFields: Program = {
|
||
type: "Program",
|
||
start: 0,
|
||
end: 6,
|
||
body: [
|
||
{
|
||
type: "VariableDeclaration",
|
||
start: 0,
|
||
end: 6,
|
||
declarations: [
|
||
{
|
||
type: "VariableDeclarator",
|
||
start: 4,
|
||
end: 5,
|
||
id: {
|
||
type: "Identifier",
|
||
start: 4,
|
||
end: 5,
|
||
name: "x",
|
||
},
|
||
init: null,
|
||
},
|
||
],
|
||
kind: "let",
|
||
},
|
||
],
|
||
sourceType: "script",
|
||
hashbang: null,
|
||
};
|
||
|
||
describe("included when", () => {
|
||
it("filename has `.ts` extension", () => {
|
||
const { program } = parseSync("test.ts", code);
|
||
expect(program).toEqual(withTsFields);
|
||
});
|
||
|
||
it('`lang` option is "ts"', () => {
|
||
const { program } = parseSync("test.js", code, { lang: "ts" });
|
||
expect(program).toEqual(withTsFields);
|
||
});
|
||
|
||
it('`astType` option is "ts"', () => {
|
||
const { program } = parseSync("test.js", code, { lang: "js", astType: "ts" });
|
||
expect(program).toEqual(withTsFields);
|
||
});
|
||
});
|
||
|
||
describe("not included when", () => {
|
||
it("filename has `.js` extension", () => {
|
||
const { program } = parseSync("test.js", code);
|
||
expect(program).toEqual(withoutTsFields);
|
||
});
|
||
|
||
it('`lang` option is "js"', () => {
|
||
const { program } = parseSync("test.ts", code, { lang: "js" });
|
||
expect(program).toEqual(withoutTsFields);
|
||
});
|
||
|
||
it('`astType` option is "js"', () => {
|
||
const { program } = parseSync("test.ts", code, { lang: "ts", astType: "js" });
|
||
expect(program).toEqual(withoutTsFields);
|
||
});
|
||
});
|
||
});
|
||
|
||
it("`Infinity` is represented as `Infinity` number", () => {
|
||
const ret = parseSync("test.js", "1e+350");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 6,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 0,
|
||
end: 6,
|
||
value: Infinity,
|
||
raw: "1e+350",
|
||
},
|
||
});
|
||
});
|
||
|
||
it("`BigIntLiteral` has `value` as `BigInt`", () => {
|
||
const ret = parseSync("test.js", "123_456n");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 8,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 0,
|
||
end: 8,
|
||
value: 123456n,
|
||
raw: "123_456n",
|
||
bigint: "123456",
|
||
},
|
||
});
|
||
});
|
||
|
||
describe("`StringLiteral`", () => {
|
||
it("lone surrogates", () => {
|
||
const ret = parseSync("test.js", ';"\\uD800\\uDBFF";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(2);
|
||
expect(ret.program.body[1]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 1,
|
||
end: 16,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 1,
|
||
end: 15,
|
||
value: "\ud800\udbff",
|
||
raw: '"\\uD800\\uDBFF"',
|
||
},
|
||
});
|
||
});
|
||
|
||
it("lossy replacement character", () => {
|
||
const ret = parseSync("test.js", ';"<22>\\u{FFFD}";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(2);
|
||
expect(ret.program.body[1]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 1,
|
||
end: 13,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 1,
|
||
end: 12,
|
||
value: "<22><>",
|
||
raw: '"<22>\\u{FFFD}"',
|
||
},
|
||
});
|
||
});
|
||
|
||
it("lone surrogates and lossy replacement characters", () => {
|
||
const ret = parseSync("test.js", ';"<22>\\u{FFFD}\\uD800\\uDBFF<46>\\u{FFFD}";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(2);
|
||
expect(ret.program.body[1]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 1,
|
||
end: 34,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 1,
|
||
end: 33,
|
||
value: "<22><>\ud800\udbff<66><66>",
|
||
raw: '"<22>\\u{FFFD}\\uD800\\uDBFF<46>\\u{FFFD}"',
|
||
},
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("`RegExpLiteral`", () => {
|
||
it("has `value` as `RegExp` when valid regexp", () => {
|
||
const ret = parseSync("test.js", "/abc/gu");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 7,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 0,
|
||
end: 7,
|
||
value: /abc/gu,
|
||
raw: "/abc/gu",
|
||
regex: {
|
||
pattern: "abc",
|
||
flags: "gu",
|
||
},
|
||
},
|
||
});
|
||
});
|
||
|
||
it("has `value` as `null` when invalid regexp", () => {
|
||
const ret = parseSync("test.js", "/+/");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 3,
|
||
expression: {
|
||
type: "Literal",
|
||
start: 0,
|
||
end: 3,
|
||
value: null,
|
||
raw: "/+/",
|
||
regex: {
|
||
pattern: "+",
|
||
flags: "",
|
||
},
|
||
},
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("`TemplateLiteral`", () => {
|
||
it("lone surrogates", () => {
|
||
const ret = parseSync("test.js", "`\\uD800\\uDBFF${x}\\uD800\\uDBFF`;");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 31,
|
||
expression: {
|
||
type: "TemplateLiteral",
|
||
start: 0,
|
||
end: 30,
|
||
expressions: [
|
||
{
|
||
type: "Identifier",
|
||
start: 15,
|
||
end: 16,
|
||
name: "x",
|
||
},
|
||
],
|
||
quasis: [
|
||
{
|
||
type: "TemplateElement",
|
||
start: 1,
|
||
end: 13,
|
||
value: {
|
||
raw: "\\uD800\\uDBFF",
|
||
cooked: "\ud800\udbff",
|
||
},
|
||
tail: false,
|
||
},
|
||
{
|
||
type: "TemplateElement",
|
||
start: 17,
|
||
end: 29,
|
||
value: {
|
||
raw: "\\uD800\\uDBFF",
|
||
cooked: "\ud800\udbff",
|
||
},
|
||
tail: true,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
});
|
||
|
||
it("lossy replacement character", () => {
|
||
const ret = parseSync("test.js", "`<60>\\u{FFFD}${x}<7D>\\u{FFFD}`;");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 25,
|
||
expression: {
|
||
type: "TemplateLiteral",
|
||
start: 0,
|
||
end: 24,
|
||
expressions: [
|
||
{
|
||
type: "Identifier",
|
||
start: 12,
|
||
end: 13,
|
||
name: "x",
|
||
},
|
||
],
|
||
quasis: [
|
||
{
|
||
type: "TemplateElement",
|
||
start: 1,
|
||
end: 10,
|
||
value: {
|
||
raw: "<22>\\u{FFFD}",
|
||
cooked: "<22><>",
|
||
},
|
||
tail: false,
|
||
},
|
||
{
|
||
type: "TemplateElement",
|
||
start: 14,
|
||
end: 23,
|
||
value: {
|
||
raw: "<22>\\u{FFFD}",
|
||
cooked: "<22><>",
|
||
},
|
||
tail: true,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
});
|
||
|
||
it("lone surrogates and lossy replacement characters", () => {
|
||
const ret = parseSync("test.js", "`<60>\\u{FFFD}\\uD800${x}\\uDBFF<46>\\u{FFFD}`;");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ExpressionStatement",
|
||
start: 0,
|
||
end: 37,
|
||
expression: {
|
||
type: "TemplateLiteral",
|
||
start: 0,
|
||
end: 36,
|
||
expressions: [
|
||
{
|
||
type: "Identifier",
|
||
start: 18,
|
||
end: 19,
|
||
name: "x",
|
||
},
|
||
],
|
||
quasis: [
|
||
{
|
||
type: "TemplateElement",
|
||
start: 1,
|
||
end: 16,
|
||
value: {
|
||
raw: "<22>\\u{FFFD}\\uD800",
|
||
cooked: "<22><>\ud800",
|
||
},
|
||
tail: false,
|
||
},
|
||
{
|
||
type: "TemplateElement",
|
||
start: 20,
|
||
end: 35,
|
||
value: {
|
||
raw: "\\uDBFF<46>\\u{FFFD}",
|
||
cooked: "\udbff<66><66>",
|
||
},
|
||
tail: true,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("`ImportDeclaration`", () => {
|
||
describe("import defer", () => {
|
||
it("ESTree", () => {
|
||
const ret = parseSync("test.js", 'import defer * as ns from "x";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ImportDeclaration",
|
||
start: 0,
|
||
end: 30,
|
||
specifiers: [
|
||
{
|
||
type: "ImportNamespaceSpecifier",
|
||
start: 13,
|
||
end: 20,
|
||
local: { type: "Identifier", start: 18, end: 20, name: "ns" },
|
||
},
|
||
],
|
||
source: { type: "Literal", start: 26, end: 29, value: "x", raw: '"x"' },
|
||
attributes: [],
|
||
phase: "defer",
|
||
});
|
||
});
|
||
|
||
it("TS-ESTree", () => {
|
||
const ret = parseSync("test.ts", 'import defer * as ns from "x";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ImportDeclaration",
|
||
start: 0,
|
||
end: 30,
|
||
specifiers: [
|
||
{
|
||
type: "ImportNamespaceSpecifier",
|
||
start: 13,
|
||
end: 20,
|
||
local: {
|
||
type: "Identifier",
|
||
start: 18,
|
||
end: 20,
|
||
decorators: [],
|
||
name: "ns",
|
||
optional: false,
|
||
typeAnnotation: null,
|
||
},
|
||
},
|
||
],
|
||
source: { type: "Literal", start: 26, end: 29, value: "x", raw: '"x"' },
|
||
attributes: [],
|
||
phase: "defer",
|
||
importKind: "value",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("import source", () => {
|
||
it("ESTree", () => {
|
||
const ret = parseSync("test.js", 'import source src from "x";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ImportDeclaration",
|
||
start: 0,
|
||
end: 27,
|
||
specifiers: [
|
||
{
|
||
type: "ImportDefaultSpecifier",
|
||
start: 14,
|
||
end: 17,
|
||
local: { type: "Identifier", start: 14, end: 17, name: "src" },
|
||
},
|
||
],
|
||
source: { type: "Literal", start: 23, end: 26, value: "x", raw: '"x"' },
|
||
attributes: [],
|
||
phase: "source",
|
||
});
|
||
});
|
||
|
||
it("TS-ESTree", () => {
|
||
const ret = parseSync("test.ts", 'import source src from "x";');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.body[0]).toEqual({
|
||
type: "ImportDeclaration",
|
||
start: 0,
|
||
end: 27,
|
||
specifiers: [
|
||
{
|
||
type: "ImportDefaultSpecifier",
|
||
start: 14,
|
||
end: 17,
|
||
local: {
|
||
type: "Identifier",
|
||
start: 14,
|
||
end: 17,
|
||
decorators: [],
|
||
name: "src",
|
||
optional: false,
|
||
typeAnnotation: null,
|
||
},
|
||
},
|
||
],
|
||
source: { type: "Literal", start: 23, end: 26, value: "x", raw: '"x"' },
|
||
attributes: [],
|
||
phase: "source",
|
||
importKind: "value",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("`ImportExpression`", () => {
|
||
describe("import.defer()", () => {
|
||
it("ESTree", () => {
|
||
const ret = parseSync("test.js", 'import.defer("x");');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
// @ts-expect-error - ignore
|
||
expect(ret.program.body[0].expression).toEqual({
|
||
type: "ImportExpression",
|
||
start: 0,
|
||
end: 17,
|
||
source: { type: "Literal", start: 13, end: 16, value: "x", raw: '"x"' },
|
||
options: null,
|
||
phase: "defer",
|
||
});
|
||
});
|
||
|
||
// This does *not* align with TS-ESLint.
|
||
// See https://github.com/oxc-project/oxc/pull/11193.
|
||
it("TS-ESTree", () => {
|
||
const ret = parseSync("test.ts", 'import.defer("x");');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
// @ts-expect-error - ignore
|
||
expect(ret.program.body[0].expression).toEqual({
|
||
type: "ImportExpression",
|
||
start: 0,
|
||
end: 17,
|
||
source: { type: "Literal", start: 13, end: 16, value: "x", raw: '"x"' },
|
||
options: null,
|
||
phase: "defer",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("import.source()", () => {
|
||
it("ESTree", () => {
|
||
const ret = parseSync("test.js", 'import.source("x");');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
// @ts-expect-error - ignore
|
||
expect(ret.program.body[0].expression).toEqual({
|
||
type: "ImportExpression",
|
||
start: 0,
|
||
end: 18,
|
||
source: { type: "Literal", start: 14, end: 17, value: "x", raw: '"x"' },
|
||
options: null,
|
||
phase: "source",
|
||
});
|
||
});
|
||
|
||
// This does *not* align with TS-ESLint.
|
||
// See https://github.com/oxc-project/oxc/pull/11193.
|
||
it("TS-ESTree", () => {
|
||
const ret = parseSync("test.ts", 'import.source("x");');
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
// @ts-expect-error - ignore
|
||
expect(ret.program.body[0].expression).toEqual({
|
||
type: "ImportExpression",
|
||
start: 0,
|
||
end: 18,
|
||
source: { type: "Literal", start: 14, end: 17, value: "x", raw: '"x"' },
|
||
options: null,
|
||
phase: "source",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("hashbang", () => {
|
||
it("is `null` when no hashbang", () => {
|
||
const ret = parseSync("test.js", "let x;");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.hashbang).toBeNull();
|
||
expect(ret.comments).toHaveLength(0);
|
||
});
|
||
|
||
it("is defined when hashbang", () => {
|
||
const ret = parseSync("test.js", "#!/usr/bin/env node\nlet x;");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.program.hashbang).toEqual({
|
||
type: "Hashbang",
|
||
start: 0,
|
||
end: 19,
|
||
value: "/usr/bin/env node",
|
||
});
|
||
});
|
||
|
||
it("is also recorded as a line comment in JS files", () => {
|
||
const ret = parseSync("test.js", "#!/usr/bin/env node\nlet x; // foo");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.comments).toHaveLength(2);
|
||
expect(ret.comments[0]).toEqual({
|
||
type: "Line",
|
||
start: 0,
|
||
end: 19,
|
||
value: "/usr/bin/env node",
|
||
});
|
||
});
|
||
|
||
it("is not recorded as a line comment in TS files", () => {
|
||
const ret = parseSync("test.ts", "#!/usr/bin/env node\nlet x; // foo");
|
||
expect(ret.errors.length).toBe(0);
|
||
expect(ret.program.body.length).toBe(1);
|
||
expect(ret.comments).toHaveLength(1);
|
||
expect(ret.comments[0].value).toBe(" foo");
|
||
});
|
||
});
|
||
|
||
describe("preserveParens", () => {
|
||
it("should include parens when true", () => {
|
||
let ret = parseSync("test.js", "(x)");
|
||
expect((ret.program.body[0] as ExpressionStatement).expression.type).toBe(
|
||
"ParenthesizedExpression",
|
||
);
|
||
|
||
ret = parseSync("test.ts", "type Foo = (x)");
|
||
expect((ret.program.body[0] as TSTypeAliasDeclaration).typeAnnotation.type).toBe(
|
||
"TSParenthesizedType",
|
||
);
|
||
});
|
||
|
||
it("should omit parens when false", () => {
|
||
const options = { preserveParens: false };
|
||
let ret = parseSync("test.js", "(x)", options);
|
||
expect((ret.program.body[0] as ExpressionStatement).expression.type).toBe("Identifier");
|
||
|
||
ret = parseSync("test.ts", "type Foo = (x)", options);
|
||
expect((ret.program.body[0] as TSTypeAliasDeclaration).typeAnnotation.type).toBe(
|
||
"TSTypeReference",
|
||
);
|
||
});
|
||
});
|
||
|
||
describe("ranges", () => {
|
||
it("should include range when true", () => {
|
||
const ret = parseSync("test.js", "(x)", { range: true });
|
||
expect(ret.program.body[0].start).toBe(0);
|
||
expect(ret.program.body[0].range).toEqual([0, 3]);
|
||
});
|
||
|
||
it("should not include range when false", () => {
|
||
const ret = parseSync("test.js", "(x)", { range: false });
|
||
expect(ret.program.body[0].range).toBeUndefined();
|
||
});
|
||
|
||
it("should not include range by default", () => {
|
||
const ret = parseSync("test.js", "(x)");
|
||
expect(ret.program.body[0].range).toBeUndefined();
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("UTF-16 span", () => {
|
||
it("basic", async () => {
|
||
const code = "'🤨'";
|
||
const utf16 = await parse("test.js", code);
|
||
expect(utf16.program.end).toMatchInlineSnapshot(`4`);
|
||
});
|
||
|
||
it("comment", async () => {
|
||
const ret = await parse("test.js", `// ∞`);
|
||
expect(ret.comments).toMatchInlineSnapshot(`
|
||
[
|
||
{
|
||
"end": 4,
|
||
"start": 0,
|
||
"type": "Line",
|
||
"value": " ∞",
|
||
},
|
||
]
|
||
`);
|
||
});
|
||
|
||
it("module record", async () => {
|
||
const ret = await parse(
|
||
"test.js",
|
||
`"🤨";import x from "x"; export { x };import("y");import.meta.z`,
|
||
);
|
||
expect(ret.module).toMatchInlineSnapshot(`
|
||
{
|
||
"dynamicImports": [
|
||
{
|
||
"end": 48,
|
||
"moduleRequest": {
|
||
"end": 47,
|
||
"start": 44,
|
||
},
|
||
"start": 37,
|
||
},
|
||
],
|
||
"hasModuleSyntax": true,
|
||
"importMetas": [
|
||
{
|
||
"end": 60,
|
||
"start": 49,
|
||
},
|
||
],
|
||
"staticExports": [
|
||
{
|
||
"end": 23,
|
||
"entries": [
|
||
{
|
||
"end": 34,
|
||
"exportName": {
|
||
"end": 34,
|
||
"kind": "Name",
|
||
"name": "x",
|
||
"start": 33,
|
||
},
|
||
"importName": {
|
||
"end": 13,
|
||
"kind": "Name",
|
||
"name": "x",
|
||
"start": 12,
|
||
},
|
||
"isType": false,
|
||
"localName": {
|
||
"end": null,
|
||
"kind": "None",
|
||
"name": null,
|
||
"start": null,
|
||
},
|
||
"moduleRequest": {
|
||
"end": 22,
|
||
"start": 19,
|
||
"value": "x",
|
||
},
|
||
"start": 33,
|
||
},
|
||
],
|
||
"start": 5,
|
||
},
|
||
],
|
||
"staticImports": [
|
||
{
|
||
"end": 23,
|
||
"entries": [
|
||
{
|
||
"importName": {
|
||
"end": 13,
|
||
"kind": "Default",
|
||
"name": null,
|
||
"start": 12,
|
||
},
|
||
"isType": false,
|
||
"localName": {
|
||
"end": 13,
|
||
"start": 12,
|
||
"value": "x",
|
||
},
|
||
},
|
||
],
|
||
"moduleRequest": {
|
||
"end": 22,
|
||
"start": 19,
|
||
"value": "x",
|
||
},
|
||
"start": 5,
|
||
},
|
||
],
|
||
}
|
||
`);
|
||
});
|
||
|
||
it("error", async () => {
|
||
const ret = await parse("test.js", `"🤨";asdf asdf`);
|
||
expect(ret.errors).toMatchInlineSnapshot(`
|
||
[
|
||
{
|
||
"codeframe": "
|
||
x Expected a semicolon or an implicit semicolon after a statement, but found
|
||
| none
|
||
,-[test.js:1:12]
|
||
1 | "🤨";asdf asdf
|
||
: ^
|
||
\`----
|
||
help: Try inserting a semicolon here
|
||
",
|
||
"helpMessage": "Try inserting a semicolon here",
|
||
"labels": [
|
||
{
|
||
"end": 9,
|
||
"message": null,
|
||
"start": 9,
|
||
},
|
||
],
|
||
"message": "Expected a semicolon or an implicit semicolon after a statement, but found none",
|
||
"severity": "Error",
|
||
},
|
||
]
|
||
`);
|
||
});
|
||
});
|
||
|
||
describe("error", () => {
|
||
const code = "asdf asdf";
|
||
|
||
it("returns structured error", () => {
|
||
const ret = parseSync("test.js", code);
|
||
expect(ret.errors.length).toBe(1);
|
||
delete ret.errors[0].codeframe;
|
||
expect(ret.errors[0]).toStrictEqual({
|
||
helpMessage: "Try inserting a semicolon here",
|
||
labels: [
|
||
{
|
||
message: null,
|
||
start: 4,
|
||
end: 4,
|
||
},
|
||
],
|
||
message: "Expected a semicolon or an implicit semicolon after a statement, but found none",
|
||
severity: "Error",
|
||
});
|
||
});
|
||
|
||
it("renders raw-transfer diagnostics", () => {
|
||
const standard = parseSync("test.js", code);
|
||
const raw = parseRawSync("test.js", code, { experimentalRawTransfer: true });
|
||
|
||
expect(raw.errors[0].codeframe).toBe(standard.errors[0].codeframe);
|
||
});
|
||
});
|
||
|
||
describe("worker", () => {
|
||
it("should run", async () => {
|
||
const code = await new Promise((resolve, reject) => {
|
||
const worker = new Worker("./test/worker.js");
|
||
worker.on("error", (err) => {
|
||
reject(err);
|
||
});
|
||
worker.on("exit", (code) => {
|
||
resolve(code);
|
||
});
|
||
});
|
||
expect(code).toBe(0);
|
||
});
|
||
});
|