From bdded7e80db85ce9baec9e2a5bbdcaa711e202c8 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:13:50 +0000 Subject: [PATCH] test(ast/estree): add tests for JSX via raw transfer (#10241) Add tests for JSX syntax parsing via raw transfer. --- napi/parser/test/parse-raw.test.ts | 75 ++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/napi/parser/test/parse-raw.test.ts b/napi/parser/test/parse-raw.test.ts index 2b13c4282d4a7..61745cfa91c73 100644 --- a/napi/parser/test/parse-raw.test.ts +++ b/napi/parser/test/parse-raw.test.ts @@ -4,10 +4,15 @@ import { describe, expect, it } from 'vitest'; import { parseSync } from '../index.js'; -const TARGET_DIR_PATH = pathJoin(import.meta.dirname, '../../../target'); -const TEST262_DIR_PATH = pathJoin(import.meta.dirname, '../../../tasks/coverage/test262/test'); -const ACORN_TEST262_DIR_PATH = pathJoin(import.meta.dirname, '../../../tasks/coverage/acorn-test262/test'); -const ESTREE_SNAPSHOT_PATH = pathJoin(import.meta.dirname, '../../../tasks/coverage/snapshots/estree_test262.snap'); +const ROOT_DIR = pathJoin(import.meta.dirname, '../../..'); +const TARGET_DIR_PATH = pathJoin(ROOT_DIR, 'target'); +const TEST262_SHORT_DIR_PATH = 'tasks/coverage/test262/test'; +const TEST262_DIR_PATH = pathJoin(ROOT_DIR, TEST262_SHORT_DIR_PATH); +const ACORN_TEST262_DIR_PATH = pathJoin(ROOT_DIR, 'tasks/coverage/acorn-test262/test'); +const JSX_SHORT_DIR_PATH = 'tasks/coverage/acorn-test262/test-acorn-jsx/pass'; +const JSX_DIR_PATH = pathJoin(ROOT_DIR, JSX_SHORT_DIR_PATH); +const TEST262_SNAPSHOT_PATH = pathJoin(ROOT_DIR, 'tasks/coverage/snapshots/estree_test262.snap'); +const JSX_SNAPSHOT_PATH = pathJoin(ROOT_DIR, 'tasks/coverage/snapshots/estree_acorn_jsx.snap'); const INFINITY_PLACEHOLDER = '__INFINITY__INFINITY__INFINITY__'; const INFINITY_REGEXP = new RegExp(`"${INFINITY_PLACEHOLDER}"`, 'g'); @@ -46,26 +51,20 @@ const benchFixtures = await Promise.all(benchFixtureUrls.map(async (url) => { return [filename, sourceText]; })); +// Test raw transfer output matches JSON snapshots for Test262 test cases. +// // Only test Test262 fixtures which Acorn is able to parse. -// Skip tests which we know we can't pass (listed as failing in `ESTree` snapshot file), +// Skip tests which we know we can't pass (listed as failing in `estree_test262.snap` snapshot file), // and skip tests related to hashbangs (where output is correct, but Acorn doesn't parse hashbangs). -const SNAPSHOT_FAIL_PREFIX = 'Mismatch: tasks/coverage/test262/test/'; -const snapshotFailPaths = new Set( - (await readFile(ESTREE_SNAPSHOT_PATH, 'utf8')) - .split('\n') - .filter(line => line.startsWith(SNAPSHOT_FAIL_PREFIX)) - .map(line => line.slice(SNAPSHOT_FAIL_PREFIX.length)), -); - +const test262FailPaths = await getTestFailurePaths(TEST262_SNAPSHOT_PATH, TEST262_SHORT_DIR_PATH); const test262FixturePaths = []; for (let path of await readdir(ACORN_TEST262_DIR_PATH, { recursive: true })) { if (!path.endsWith('.json')) continue; path = path.slice(0, -2); - if (snapshotFailPaths.has(path) || path.startsWith('language/comments/hashbang/')) continue; + if (test262FailPaths.has(path) || path.startsWith('language/comments/hashbang/')) continue; test262FixturePaths.push(path); } -// Test raw transfer output matches standard (via JSON) output for Test262 test cases describe('test262', () => { it.each(test262FixturePaths)('%s', async (path) => { const filename = basename(path); @@ -90,6 +89,39 @@ describe('test262', () => { }); }); +// Test raw transfer output matches JSON snapshots for Acorn-JSX test cases. +// +// Only test Acorn-JSX fixtures which Acorn is able to parse. +// Skip tests which we know we can't pass (listed as failing in `estree_acron_jsx.snap` snapshot file). +const jsxFailPaths = await getTestFailurePaths(JSX_SNAPSHOT_PATH, JSX_SHORT_DIR_PATH); +const jsxFixturePaths = (await readdir(JSX_DIR_PATH, { recursive: true })) + .filter(path => path.endsWith('.jsx') && !jsxFailPaths.has(path)); + +describe('JSX', () => { + it.each(jsxFixturePaths)('%s', async (filename) => { + const sourcePath = pathJoin(JSX_DIR_PATH, filename), + jsonPath = sourcePath.slice(0, -1) + 'on'; // `.jsx` -> `.json` + const [sourceText, acornJson] = await Promise.all([ + readFile(sourcePath, 'utf8'), + readFile(jsonPath, 'utf8'), + ]); + + // Acorn JSON files always end with: + // ``` + // "sourceType": "script", + // "hashbang": null + // } + // ``` + // For speed, extract `sourceType` with a slice, rather than parsing the JSON. + const sourceType = acornJson.slice(-29, -23); + + // @ts-ignore + const { program } = parseSync(filename, sourceText, { sourceType, experimentalRawTransfer: true }); + const json = stringifyAcornTest262Style(program); + expect(json).toEqual(acornJson); + }); +}); + // Test raw transfer output matches standard (via JSON) output for edge cases not covered by Test262 describe('edge cases', () => { it.each([ @@ -142,6 +174,19 @@ function assertRawAndStandardMatch(filename, sourceText) { expect(jsonRaw).toEqual(jsonStandard); } +// Get `Set` containing test paths which failed from snapshot file +async function getTestFailurePaths(snapshotPath, pathPrefix) { + const mismatchPrefix = `Mismatch: ${pathPrefix}/`, + mismatchPrefixLen = mismatchPrefix.length; + + const snapshot = await readFile(snapshotPath, 'utf8'); + return new Set( + snapshot.split('\n') + .filter(line => line.startsWith(mismatchPrefix)) + .map(line => line.slice(mismatchPrefixLen)), + ); +} + // Stringify to JSON, removing values which are invalid in JSON function stringify(obj) { return JSON.stringify(obj, (_key, value) => {