193fb6ee3Sopenharmony_ciimport * as assert from 'node:assert'; 293fb6ee3Sopenharmony_ciimport * as fs from 'node:fs'; 393fb6ee3Sopenharmony_ciimport * as path from 'node:path'; 493fb6ee3Sopenharmony_ciimport { type TreeAdapterTypeMap, type TreeAdapter, type ParserOptions, type Token, serializeOuter } from 'parse5'; 593fb6ee3Sopenharmony_ciimport { 693fb6ee3Sopenharmony_ci removeNewLines, 793fb6ee3Sopenharmony_ci getSubstringByLineCol, 893fb6ee3Sopenharmony_ci getStringDiffMsg, 993fb6ee3Sopenharmony_ci normalizeNewLine, 1093fb6ee3Sopenharmony_ci generateTestsForEachTreeAdapter, 1193fb6ee3Sopenharmony_ci} from './common.js'; 1293fb6ee3Sopenharmony_ciimport { serializeDoctypeContent } from 'parse5-htmlparser2-tree-adapter'; 1393fb6ee3Sopenharmony_ci 1493fb6ee3Sopenharmony_cifunction walkTree<T extends TreeAdapterTypeMap>( 1593fb6ee3Sopenharmony_ci parent: T['parentNode'], 1693fb6ee3Sopenharmony_ci treeAdapter: TreeAdapter<T>, 1793fb6ee3Sopenharmony_ci handler: (node: T['node']) => void 1893fb6ee3Sopenharmony_ci): void { 1993fb6ee3Sopenharmony_ci for (const node of treeAdapter.getChildNodes(parent)) { 2093fb6ee3Sopenharmony_ci if (treeAdapter.isElementNode(node)) { 2193fb6ee3Sopenharmony_ci walkTree(node, treeAdapter, handler); 2293fb6ee3Sopenharmony_ci } 2393fb6ee3Sopenharmony_ci 2493fb6ee3Sopenharmony_ci handler(node); 2593fb6ee3Sopenharmony_ci } 2693fb6ee3Sopenharmony_ci} 2793fb6ee3Sopenharmony_ci 2893fb6ee3Sopenharmony_cifunction assertLocation(loc: Token.Location, expected: string, html: string, lines: string[]): void { 2993fb6ee3Sopenharmony_ci //Offsets 3093fb6ee3Sopenharmony_ci let actual = html.substring(loc.startOffset, loc.endOffset); 3193fb6ee3Sopenharmony_ci 3293fb6ee3Sopenharmony_ci expected = removeNewLines(expected); 3393fb6ee3Sopenharmony_ci actual = removeNewLines(actual); 3493fb6ee3Sopenharmony_ci 3593fb6ee3Sopenharmony_ci assert.ok(expected === actual, getStringDiffMsg(actual, expected)); 3693fb6ee3Sopenharmony_ci 3793fb6ee3Sopenharmony_ci //Line/col 3893fb6ee3Sopenharmony_ci actual = getSubstringByLineCol(lines, loc); 3993fb6ee3Sopenharmony_ci actual = removeNewLines(actual); 4093fb6ee3Sopenharmony_ci 4193fb6ee3Sopenharmony_ci assert.ok(actual === expected, getStringDiffMsg(actual, expected)); 4293fb6ee3Sopenharmony_ci} 4393fb6ee3Sopenharmony_ci 4493fb6ee3Sopenharmony_ci//NOTE: Based on the idea that the serialized fragment starts with the startTag 4593fb6ee3Sopenharmony_ciexport function assertStartTagLocation( 4693fb6ee3Sopenharmony_ci location: Token.ElementLocation, 4793fb6ee3Sopenharmony_ci serializedNode: string, 4893fb6ee3Sopenharmony_ci html: string, 4993fb6ee3Sopenharmony_ci lines: string[] 5093fb6ee3Sopenharmony_ci): void { 5193fb6ee3Sopenharmony_ci assert.ok(location.startTag, 'Expected startTag to be defined'); 5293fb6ee3Sopenharmony_ci const length = location.startTag.endOffset - location.startTag.startOffset; 5393fb6ee3Sopenharmony_ci const expected = serializedNode.substring(0, length); 5493fb6ee3Sopenharmony_ci 5593fb6ee3Sopenharmony_ci assertLocation(location.startTag, expected, html, lines); 5693fb6ee3Sopenharmony_ci} 5793fb6ee3Sopenharmony_ci 5893fb6ee3Sopenharmony_ci//NOTE: Based on the idea that the serialized fragment ends with the endTag 5993fb6ee3Sopenharmony_cifunction assertEndTagLocation( 6093fb6ee3Sopenharmony_ci location: Token.ElementLocation, 6193fb6ee3Sopenharmony_ci serializedNode: string, 6293fb6ee3Sopenharmony_ci html: string, 6393fb6ee3Sopenharmony_ci lines: string[] 6493fb6ee3Sopenharmony_ci): void { 6593fb6ee3Sopenharmony_ci assert.ok(location.endTag, 'Expected endTag to be defined'); 6693fb6ee3Sopenharmony_ci const length = location.endTag.endOffset - location.endTag.startOffset; 6793fb6ee3Sopenharmony_ci const expected = serializedNode.slice(-length); 6893fb6ee3Sopenharmony_ci 6993fb6ee3Sopenharmony_ci assertLocation(location.endTag, expected, html, lines); 7093fb6ee3Sopenharmony_ci} 7193fb6ee3Sopenharmony_ci 7293fb6ee3Sopenharmony_cifunction assertAttrsLocation( 7393fb6ee3Sopenharmony_ci location: Token.ElementLocation, 7493fb6ee3Sopenharmony_ci serializedNode: string, 7593fb6ee3Sopenharmony_ci html: string, 7693fb6ee3Sopenharmony_ci lines: string[] 7793fb6ee3Sopenharmony_ci): void { 7893fb6ee3Sopenharmony_ci assert.ok(location.attrs, 'Expected attrs to be defined'); 7993fb6ee3Sopenharmony_ci 8093fb6ee3Sopenharmony_ci for (const attr of Object.values(location.attrs)) { 8193fb6ee3Sopenharmony_ci const expected = serializedNode.slice( 8293fb6ee3Sopenharmony_ci attr.startOffset - location.startOffset, 8393fb6ee3Sopenharmony_ci attr.endOffset - location.startOffset 8493fb6ee3Sopenharmony_ci ); 8593fb6ee3Sopenharmony_ci 8693fb6ee3Sopenharmony_ci assertLocation(attr, expected, html, lines); 8793fb6ee3Sopenharmony_ci } 8893fb6ee3Sopenharmony_ci} 8993fb6ee3Sopenharmony_ci 9093fb6ee3Sopenharmony_ciexport function assertNodeLocation( 9193fb6ee3Sopenharmony_ci location: Token.Location, 9293fb6ee3Sopenharmony_ci serializedNode: string, 9393fb6ee3Sopenharmony_ci html: string, 9493fb6ee3Sopenharmony_ci lines: string[] 9593fb6ee3Sopenharmony_ci): void { 9693fb6ee3Sopenharmony_ci const expected = removeNewLines(serializedNode); 9793fb6ee3Sopenharmony_ci 9893fb6ee3Sopenharmony_ci assertLocation(location, expected, html, lines); 9993fb6ee3Sopenharmony_ci} 10093fb6ee3Sopenharmony_ci 10193fb6ee3Sopenharmony_cifunction loadParserLocationInfoTestData(): { name: string; data: string }[] { 10293fb6ee3Sopenharmony_ci const dataDirPath = new URL('../data/location-info', import.meta.url); 10393fb6ee3Sopenharmony_ci const testSetFileDirs = fs.readdirSync(dataDirPath); 10493fb6ee3Sopenharmony_ci 10593fb6ee3Sopenharmony_ci return testSetFileDirs.map((dirName) => { 10693fb6ee3Sopenharmony_ci const dataFilePath = path.join(dataDirPath.pathname, dirName, 'data.html'); 10793fb6ee3Sopenharmony_ci const data = fs.readFileSync(dataFilePath).toString(); 10893fb6ee3Sopenharmony_ci 10993fb6ee3Sopenharmony_ci return { 11093fb6ee3Sopenharmony_ci name: dirName, 11193fb6ee3Sopenharmony_ci data: normalizeNewLine(data), 11293fb6ee3Sopenharmony_ci }; 11393fb6ee3Sopenharmony_ci }); 11493fb6ee3Sopenharmony_ci} 11593fb6ee3Sopenharmony_ci 11693fb6ee3Sopenharmony_ciexport function generateLocationInfoParserTests( 11793fb6ee3Sopenharmony_ci name: string, 11893fb6ee3Sopenharmony_ci parse: (html: string, opts: ParserOptions<TreeAdapterTypeMap>) => { node: TreeAdapterTypeMap['node'] } 11993fb6ee3Sopenharmony_ci): void { 12093fb6ee3Sopenharmony_ci generateTestsForEachTreeAdapter(name, (treeAdapter) => { 12193fb6ee3Sopenharmony_ci for (const test of loadParserLocationInfoTestData()) { 12293fb6ee3Sopenharmony_ci //NOTE: How it works: we parse document with location info. 12393fb6ee3Sopenharmony_ci //Then for each node in the tree we run the serializer and compare results with the substring 12493fb6ee3Sopenharmony_ci //obtained via the location info from the expected serialization results. 12593fb6ee3Sopenharmony_ci it(`Location info (Parser) - ${test.name}`, async () => { 12693fb6ee3Sopenharmony_ci const html = test.data; 12793fb6ee3Sopenharmony_ci const lines = html.split(/\r?\n/g); 12893fb6ee3Sopenharmony_ci 12993fb6ee3Sopenharmony_ci const parserOpts = { 13093fb6ee3Sopenharmony_ci treeAdapter, 13193fb6ee3Sopenharmony_ci sourceCodeLocationInfo: true, 13293fb6ee3Sopenharmony_ci }; 13393fb6ee3Sopenharmony_ci 13493fb6ee3Sopenharmony_ci const parsingResult = parse(html, parserOpts); 13593fb6ee3Sopenharmony_ci const document = parsingResult.node; 13693fb6ee3Sopenharmony_ci 13793fb6ee3Sopenharmony_ci walkTree(document, treeAdapter, (node) => { 13893fb6ee3Sopenharmony_ci const location = treeAdapter.getNodeSourceCodeLocation(node); 13993fb6ee3Sopenharmony_ci 14093fb6ee3Sopenharmony_ci assert.ok(location); 14193fb6ee3Sopenharmony_ci 14293fb6ee3Sopenharmony_ci const serializedNode = treeAdapter.isDocumentTypeNode(node) 14393fb6ee3Sopenharmony_ci ? `<${serializeDoctypeContent( 14493fb6ee3Sopenharmony_ci treeAdapter.getDocumentTypeNodeName(node), 14593fb6ee3Sopenharmony_ci treeAdapter.getDocumentTypeNodePublicId(node), 14693fb6ee3Sopenharmony_ci treeAdapter.getDocumentTypeNodeSystemId(node) 14793fb6ee3Sopenharmony_ci )}>` 14893fb6ee3Sopenharmony_ci : serializeOuter(node, { treeAdapter }); 14993fb6ee3Sopenharmony_ci 15093fb6ee3Sopenharmony_ci assertLocation(location, serializedNode, html, lines); 15193fb6ee3Sopenharmony_ci 15293fb6ee3Sopenharmony_ci if (treeAdapter.isElementNode(node)) { 15393fb6ee3Sopenharmony_ci assertStartTagLocation(location, serializedNode, html, lines); 15493fb6ee3Sopenharmony_ci 15593fb6ee3Sopenharmony_ci if (location.endTag) { 15693fb6ee3Sopenharmony_ci assertEndTagLocation(location, serializedNode, html, lines); 15793fb6ee3Sopenharmony_ci } 15893fb6ee3Sopenharmony_ci 15993fb6ee3Sopenharmony_ci if (location.attrs) { 16093fb6ee3Sopenharmony_ci assertAttrsLocation(location, serializedNode, html, lines); 16193fb6ee3Sopenharmony_ci } else { 16293fb6ee3Sopenharmony_ci // If we don't have `location.attrs`, we expect that the node has no attributes. 16393fb6ee3Sopenharmony_ci assert.strictEqual(treeAdapter.getAttrList(node).length, 0); 16493fb6ee3Sopenharmony_ci } 16593fb6ee3Sopenharmony_ci } 16693fb6ee3Sopenharmony_ci }); 16793fb6ee3Sopenharmony_ci }); 16893fb6ee3Sopenharmony_ci } 16993fb6ee3Sopenharmony_ci }); 17093fb6ee3Sopenharmony_ci} 171