193fb6ee3Sopenharmony_ciimport type { ParserOptions, TreeAdapter, TreeAdapterTypeMap, ParserError } from 'parse5';
293fb6ee3Sopenharmony_ciimport * as fs from 'node:fs';
393fb6ee3Sopenharmony_ciimport * as path from 'node:path';
493fb6ee3Sopenharmony_ciimport * as assert from 'node:assert';
593fb6ee3Sopenharmony_ciimport { serializeToDatFileFormat } from './serialize-to-dat-file-format.js';
693fb6ee3Sopenharmony_ciimport { generateTestsForEachTreeAdapter } from './common.js';
793fb6ee3Sopenharmony_ciimport { parseDatFile, type DatFile } from './parse-dat-file.js';
893fb6ee3Sopenharmony_ci
993fb6ee3Sopenharmony_ciexport interface TreeConstructionTestData<T extends TreeAdapterTypeMap> extends DatFile<T> {
1093fb6ee3Sopenharmony_ci    idx: number;
1193fb6ee3Sopenharmony_ci    setName: string;
1293fb6ee3Sopenharmony_ci    dirName: string;
1393fb6ee3Sopenharmony_ci}
1493fb6ee3Sopenharmony_ci
1593fb6ee3Sopenharmony_ciexport function loadTreeConstructionTestData<T extends TreeAdapterTypeMap>(
1693fb6ee3Sopenharmony_ci    dataDir: URL,
1793fb6ee3Sopenharmony_ci    treeAdapter: TreeAdapter<T>
1893fb6ee3Sopenharmony_ci): TreeConstructionTestData<T>[] {
1993fb6ee3Sopenharmony_ci    const tests: TreeConstructionTestData<T>[] = [];
2093fb6ee3Sopenharmony_ci
2193fb6ee3Sopenharmony_ci    const dataDirPath = dataDir.pathname;
2293fb6ee3Sopenharmony_ci    const testSetFileNames = fs.readdirSync(dataDir);
2393fb6ee3Sopenharmony_ci    const dirName = path.basename(dataDirPath);
2493fb6ee3Sopenharmony_ci
2593fb6ee3Sopenharmony_ci    for (const fileName of testSetFileNames) {
2693fb6ee3Sopenharmony_ci        if (path.extname(fileName) !== '.dat') {
2793fb6ee3Sopenharmony_ci            continue;
2893fb6ee3Sopenharmony_ci        }
2993fb6ee3Sopenharmony_ci
3093fb6ee3Sopenharmony_ci        const filePath = path.join(dataDirPath, fileName);
3193fb6ee3Sopenharmony_ci        const testSet = fs.readFileSync(filePath, 'utf8');
3293fb6ee3Sopenharmony_ci        const setName = fileName.replace('.dat', '');
3393fb6ee3Sopenharmony_ci
3493fb6ee3Sopenharmony_ci        for (const [idx, test] of parseDatFile(testSet, treeAdapter).entries()) {
3593fb6ee3Sopenharmony_ci            tests.push({
3693fb6ee3Sopenharmony_ci                ...test,
3793fb6ee3Sopenharmony_ci                idx,
3893fb6ee3Sopenharmony_ci                setName,
3993fb6ee3Sopenharmony_ci                dirName,
4093fb6ee3Sopenharmony_ci            });
4193fb6ee3Sopenharmony_ci        }
4293fb6ee3Sopenharmony_ci    }
4393fb6ee3Sopenharmony_ci
4493fb6ee3Sopenharmony_ci    return tests;
4593fb6ee3Sopenharmony_ci}
4693fb6ee3Sopenharmony_ci
4793fb6ee3Sopenharmony_cifunction prettyPrintParserAssertionArgs(actual: string, expected: string, chunks?: string[]): string {
4893fb6ee3Sopenharmony_ci    let msg = '\nExpected:\n';
4993fb6ee3Sopenharmony_ci
5093fb6ee3Sopenharmony_ci    msg += '-----------------\n';
5193fb6ee3Sopenharmony_ci    msg += `${expected}\n`;
5293fb6ee3Sopenharmony_ci    msg += '\nActual:\n';
5393fb6ee3Sopenharmony_ci    msg += '-----------------\n';
5493fb6ee3Sopenharmony_ci    msg += `${actual}\n`;
5593fb6ee3Sopenharmony_ci
5693fb6ee3Sopenharmony_ci    if (chunks) {
5793fb6ee3Sopenharmony_ci        msg += 'Chunks:\n';
5893fb6ee3Sopenharmony_ci        msg += JSON.stringify(chunks);
5993fb6ee3Sopenharmony_ci    }
6093fb6ee3Sopenharmony_ci
6193fb6ee3Sopenharmony_ci    return msg;
6293fb6ee3Sopenharmony_ci}
6393fb6ee3Sopenharmony_ci
6493fb6ee3Sopenharmony_ciinterface ParseMethodOptions<T extends TreeAdapterTypeMap> extends ParserOptions<T> {
6593fb6ee3Sopenharmony_ci    treeAdapter: TreeAdapter<T>;
6693fb6ee3Sopenharmony_ci}
6793fb6ee3Sopenharmony_ci
6893fb6ee3Sopenharmony_ciinterface ParseResult<T extends TreeAdapterTypeMap> {
6993fb6ee3Sopenharmony_ci    node: T['node'];
7093fb6ee3Sopenharmony_ci    chunks?: string[];
7193fb6ee3Sopenharmony_ci}
7293fb6ee3Sopenharmony_ci
7393fb6ee3Sopenharmony_citype ParseMethod<T extends TreeAdapterTypeMap> = (
7493fb6ee3Sopenharmony_ci    input: TreeConstructionTestData<T>,
7593fb6ee3Sopenharmony_ci    options: ParseMethodOptions<T>
7693fb6ee3Sopenharmony_ci) => ParseResult<T> | Promise<ParseResult<T>>;
7793fb6ee3Sopenharmony_ci
7893fb6ee3Sopenharmony_cifunction createParsingTest<T extends TreeAdapterTypeMap>(
7993fb6ee3Sopenharmony_ci    test: TreeConstructionTestData<T>,
8093fb6ee3Sopenharmony_ci    treeAdapter: TreeAdapter<T>,
8193fb6ee3Sopenharmony_ci    parse: ParseMethod<T>,
8293fb6ee3Sopenharmony_ci    { withoutErrors, expectError }: { withoutErrors?: boolean; expectError?: boolean } = {}
8393fb6ee3Sopenharmony_ci): () => Promise<void> {
8493fb6ee3Sopenharmony_ci    return async (): Promise<void> => {
8593fb6ee3Sopenharmony_ci        const errs: string[] = [];
8693fb6ee3Sopenharmony_ci
8793fb6ee3Sopenharmony_ci        const opts = {
8893fb6ee3Sopenharmony_ci            scriptingEnabled: test.scriptingEnabled,
8993fb6ee3Sopenharmony_ci            treeAdapter,
9093fb6ee3Sopenharmony_ci
9193fb6ee3Sopenharmony_ci            onParseError: (err: ParserError): void => {
9293fb6ee3Sopenharmony_ci                let errStr = `(${err.startLine}:${err.startCol}`;
9393fb6ee3Sopenharmony_ci
9493fb6ee3Sopenharmony_ci                // NOTE: use ranges for token errors
9593fb6ee3Sopenharmony_ci                if (err.startLine !== err.endLine || err.startCol !== err.endCol) {
9693fb6ee3Sopenharmony_ci                    errStr += `-${err.endLine}:${err.endCol}`;
9793fb6ee3Sopenharmony_ci                }
9893fb6ee3Sopenharmony_ci
9993fb6ee3Sopenharmony_ci                errStr += `) ${err.code}`;
10093fb6ee3Sopenharmony_ci
10193fb6ee3Sopenharmony_ci                errs.push(errStr);
10293fb6ee3Sopenharmony_ci            },
10393fb6ee3Sopenharmony_ci        };
10493fb6ee3Sopenharmony_ci
10593fb6ee3Sopenharmony_ci        const { node, chunks } = await parse(test, opts);
10693fb6ee3Sopenharmony_ci        const actual = serializeToDatFileFormat(node, opts.treeAdapter);
10793fb6ee3Sopenharmony_ci        const msg = prettyPrintParserAssertionArgs(actual, test.expected, chunks);
10893fb6ee3Sopenharmony_ci        let sawError = false;
10993fb6ee3Sopenharmony_ci
11093fb6ee3Sopenharmony_ci        try {
11193fb6ee3Sopenharmony_ci            assert.ok(actual === test.expected, msg);
11293fb6ee3Sopenharmony_ci
11393fb6ee3Sopenharmony_ci            if (!withoutErrors) {
11493fb6ee3Sopenharmony_ci                assert.deepEqual(errs.sort(), test.expectedErrors.sort());
11593fb6ee3Sopenharmony_ci            }
11693fb6ee3Sopenharmony_ci        } catch (error) {
11793fb6ee3Sopenharmony_ci            if (expectError) {
11893fb6ee3Sopenharmony_ci                return;
11993fb6ee3Sopenharmony_ci            }
12093fb6ee3Sopenharmony_ci            sawError = true;
12193fb6ee3Sopenharmony_ci
12293fb6ee3Sopenharmony_ci            throw error;
12393fb6ee3Sopenharmony_ci        }
12493fb6ee3Sopenharmony_ci
12593fb6ee3Sopenharmony_ci        if (!sawError && expectError) {
12693fb6ee3Sopenharmony_ci            throw new Error(`Expected error but none was thrown`);
12793fb6ee3Sopenharmony_ci        }
12893fb6ee3Sopenharmony_ci    };
12993fb6ee3Sopenharmony_ci}
13093fb6ee3Sopenharmony_ci
13193fb6ee3Sopenharmony_ci// TODO: Stop using the fork here.
13293fb6ee3Sopenharmony_ciconst treePath = new URL('../data/html5lib-tests-fork/tree-construction', import.meta.url);
13393fb6ee3Sopenharmony_ci
13493fb6ee3Sopenharmony_ciexport function generateParsingTests(
13593fb6ee3Sopenharmony_ci    name: string,
13693fb6ee3Sopenharmony_ci    prefix: string,
13793fb6ee3Sopenharmony_ci    {
13893fb6ee3Sopenharmony_ci        withoutErrors,
13993fb6ee3Sopenharmony_ci        expectErrors: expectError = [],
14093fb6ee3Sopenharmony_ci        suitePath = treePath,
14193fb6ee3Sopenharmony_ci    }: { withoutErrors?: boolean; expectErrors?: string[]; suitePath?: URL },
14293fb6ee3Sopenharmony_ci    parse: ParseMethod<TreeAdapterTypeMap>
14393fb6ee3Sopenharmony_ci): void {
14493fb6ee3Sopenharmony_ci    generateTestsForEachTreeAdapter(name, (treeAdapter) => {
14593fb6ee3Sopenharmony_ci        const errorsToExpect = new Set(expectError);
14693fb6ee3Sopenharmony_ci
14793fb6ee3Sopenharmony_ci        for (const test of loadTreeConstructionTestData(suitePath, treeAdapter)) {
14893fb6ee3Sopenharmony_ci            const expectError = errorsToExpect.delete(`${test.idx}.${test.setName}`);
14993fb6ee3Sopenharmony_ci
15093fb6ee3Sopenharmony_ci            it(
15193fb6ee3Sopenharmony_ci                `${prefix}(${test.dirName}) - ${test.idx}.${test.setName} - \`${test.input}\` (line ${test.lineNum})`,
15293fb6ee3Sopenharmony_ci                createParsingTest<TreeAdapterTypeMap>(test, treeAdapter, parse, {
15393fb6ee3Sopenharmony_ci                    withoutErrors,
15493fb6ee3Sopenharmony_ci                    expectError,
15593fb6ee3Sopenharmony_ci                })
15693fb6ee3Sopenharmony_ci            );
15793fb6ee3Sopenharmony_ci        }
15893fb6ee3Sopenharmony_ci
15993fb6ee3Sopenharmony_ci        if (errorsToExpect.size > 0) {
16093fb6ee3Sopenharmony_ci            throw new Error(`Expected errors were not found: ${[...errorsToExpect].join(', ')}`);
16193fb6ee3Sopenharmony_ci        }
16293fb6ee3Sopenharmony_ci    });
16393fb6ee3Sopenharmony_ci}
164