-
-
Notifications
You must be signed in to change notification settings - Fork 232
/
generate-location-info-parser-tests.ts
159 lines (130 loc) · 5.99 KB
/
generate-location-info-parser-tests.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { ParserOptions } from 'parse5/dist/parser/index';
import { Location, ElementLocation } from 'parse5/dist/common/token.js';
import { TreeAdapter, TreeAdapterTypeMap } from 'parse5/dist/tree-adapters/interface.js';
import * as assert from 'node:assert';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as parse5 from 'parse5/dist/index.js';
import {
removeNewLines,
getSubstringByLineCol,
getStringDiffMsg,
normalizeNewLine,
generateTestsForEachTreeAdapter,
} from './common.js';
import * as doctype from 'parse5/dist/common/doctype.js';
function walkTree<T extends TreeAdapterTypeMap>(
parent: T['parentNode'],
treeAdapter: TreeAdapter<T>,
handler: (node: T['node']) => void
): void {
for (const node of treeAdapter.getChildNodes(parent)) {
if (treeAdapter.isElementNode(node)) {
walkTree(node, treeAdapter, handler);
}
handler(node);
}
}
function assertLocation(loc: Location, expected: string, html: string, lines: string[]): void {
//Offsets
let actual = html.substring(loc.startOffset, loc.endOffset);
expected = removeNewLines(expected);
actual = removeNewLines(actual);
assert.ok(expected === actual, getStringDiffMsg(actual, expected));
//Line/col
actual = getSubstringByLineCol(lines, loc);
actual = removeNewLines(actual);
assert.ok(actual === expected, getStringDiffMsg(actual, expected));
}
//NOTE: Based on the idea that the serialized fragment starts with the startTag
export function assertStartTagLocation(
location: ElementLocation,
serializedNode: string,
html: string,
lines: string[]
): void {
assert.ok(location.startTag, 'Expected startTag to be defined');
const length = location.startTag.endOffset - location.startTag.startOffset;
const expected = serializedNode.substring(0, length);
assertLocation(location.startTag, expected, html, lines);
}
//NOTE: Based on the idea that the serialized fragment ends with the endTag
function assertEndTagLocation(location: ElementLocation, serializedNode: string, html: string, lines: string[]): void {
assert.ok(location.endTag, 'Expected endTag to be defined');
const length = location.endTag.endOffset - location.endTag.startOffset;
const expected = serializedNode.slice(-length);
assertLocation(location.endTag, expected, html, lines);
}
function assertAttrsLocation(location: ElementLocation, serializedNode: string, html: string, lines: string[]): void {
assert.ok(location.attrs, 'Expected attrs to be defined');
for (const attr of Object.values(location.attrs)) {
const expected = serializedNode.slice(
attr.startOffset - location.startOffset,
attr.endOffset - location.startOffset
);
assertLocation(attr, expected, html, lines);
}
}
export function assertNodeLocation(location: Location, serializedNode: string, html: string, lines: string[]): void {
const expected = removeNewLines(serializedNode);
assertLocation(location, expected, html, lines);
}
function loadParserLocationInfoTestData(): { name: string; data: string }[] {
const dataDirPath = new URL('../data/location-info', import.meta.url);
const testSetFileDirs = fs.readdirSync(dataDirPath);
return testSetFileDirs.map((dirName) => {
const dataFilePath = path.join(dataDirPath.pathname, dirName, 'data.html');
const data = fs.readFileSync(dataFilePath).toString();
return {
name: dirName,
data: normalizeNewLine(data),
};
});
}
export function generateLocationInfoParserTests(
name: string,
_prefix: string,
parse: (html: string, opts: ParserOptions<TreeAdapterTypeMap>) => { node: TreeAdapterTypeMap['node'] }
): void {
generateTestsForEachTreeAdapter(name, (treeAdapter) => {
for (const test of loadParserLocationInfoTestData()) {
//NOTE: How it works: we parse document with location info.
//Then for each node in the tree we run the serializer and compare results with the substring
//obtained via the location info from the expected serialization results.
it(`Location info (Parser) - ${test.name}`, async () => {
const html = test.data;
const lines = html.split(/\r?\n/g);
const parserOpts = {
treeAdapter,
sourceCodeLocationInfo: true,
};
const parsingResult = parse(html, parserOpts);
const document = parsingResult.node;
walkTree(document, treeAdapter, (node) => {
const location = treeAdapter.getNodeSourceCodeLocation(node);
assert.ok(location);
const serializedNode = treeAdapter.isDocumentTypeNode(node)
? `<${doctype.serializeContent(
treeAdapter.getDocumentTypeNodeName(node),
treeAdapter.getDocumentTypeNodePublicId(node),
treeAdapter.getDocumentTypeNodeSystemId(node)
)}>`
: parse5.serializeOuter(node, { treeAdapter });
assertLocation(location, serializedNode, html, lines);
if (treeAdapter.isElementNode(node)) {
assertStartTagLocation(location, serializedNode, html, lines);
if (location.endTag) {
assertEndTagLocation(location, serializedNode, html, lines);
}
if (location.attrs) {
assertAttrsLocation(location, serializedNode, html, lines);
} else {
// If we don't have `location.attrs`, we expect that the node has no attributes.
assert.strictEqual(treeAdapter.getAttrList(node).length, 0);
}
}
});
});
}
});
}