mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
Fixes #25187, closes #25204. Previously, this check was performed by semantic phase, but since it can be detected in advance, it has been moved to the parser as per our previous policy. [Babel](https://ast-explorer.dev/#eNotjDEOwjAMRa8See4JunIFNsLgRgaC0thKXASKcnfcNtt/78lukGCGN36whhJFYQIxseBCyXawTV/hoq51YzZuPjvnofJWAl1/Qh7mnY97D9OZJW3PmOvebvfhEgfUyKd9YKo0AqpieF14XSnbixF97tD/NgY0iA==) also reports this in the parser. Conformance test harness also updated: - codegen: `if() { import }` is now invalid input - => use `declare module {}` for indent instead - transformer: Revert #26495 change - => defaults to `unambiguous` (Correct me if I'm wrong 🙏🏻)
929 lines
26 KiB
TypeScript
929 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);
|
|
});
|
|
|
|
it("rejects module syntax in script without semantic checks", () => {
|
|
const ret = parseSync("test.js", 'import "foo";\nexport {};', { sourceType: "script" });
|
|
// Reported at `import` and `export` keywords
|
|
expect(ret.errors.map((error) => error.labels[0].start)).toEqual([0, 14]);
|
|
});
|
|
|
|
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", ';"�\\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: "��",
|
|
raw: '"�\\u{FFFD}"',
|
|
},
|
|
});
|
|
});
|
|
|
|
it("lone surrogates and lossy replacement characters", () => {
|
|
const ret = parseSync("test.js", ';"�\\u{FFFD}\\uD800\\uDBFF�\\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: "��\ud800\udbff��",
|
|
raw: '"�\\u{FFFD}\\uD800\\uDBFF�\\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", "`�\\u{FFFD}${x}�\\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: "�\\u{FFFD}",
|
|
cooked: "��",
|
|
},
|
|
tail: false,
|
|
},
|
|
{
|
|
type: "TemplateElement",
|
|
start: 14,
|
|
end: 23,
|
|
value: {
|
|
raw: "�\\u{FFFD}",
|
|
cooked: "��",
|
|
},
|
|
tail: true,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
it("lone surrogates and lossy replacement characters", () => {
|
|
const ret = parseSync("test.js", "`�\\u{FFFD}\\uD800${x}\\uDBFF�\\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: "�\\u{FFFD}\\uD800",
|
|
cooked: "��\ud800",
|
|
},
|
|
tail: false,
|
|
},
|
|
{
|
|
type: "TemplateElement",
|
|
start: 20,
|
|
end: 35,
|
|
value: {
|
|
raw: "\\uDBFF�\\u{FFFD}",
|
|
cooked: "\udbff��",
|
|
},
|
|
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);
|
|
});
|
|
});
|