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