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