-
-
Notifications
You must be signed in to change notification settings - Fork 232
/
generate-location-info-parser-tests.ts
158 lines (126 loc) · 5.71 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
import { ParserOptions } from 'parse5/dist/parser/index';
import { Location, ElementLocation } from 'parse5/dist/common/token';
import { TreeAdapter, TreeAdapterTypeMap } from 'parse5/dist/tree-adapters/interface';
import * as assert from 'node:assert';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { escapeString } from 'parse5/dist/serializer/index.js';
import * as parse5 from 'parse5/dist/index.js';
import {
removeNewLines,
getSubstringByLineCol,
getStringDiffMsg,
normalizeNewLine,
generateTestsForEachTreeAdapter,
} from './common.js';
function walkTree<T extends TreeAdapterTypeMap>(
document: T['document'],
treeAdapter: TreeAdapter<T>,
handler: (node: T['node']) => void
): void {
const stack = [...treeAdapter.getChildNodes(document)];
let node;
while ((node = stack.shift())) {
const children = treeAdapter.getChildNodes(node);
handler(node);
if (children?.length) {
stack.unshift(...children);
}
}
}
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, attr.endOffset);
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 the location info.
//Then for each node in the tree we run serializer and compare results with the substring
//obtained via location info from the expected serialization results.
it(`Location info (Parser) - ${test.name}`, async () => {
const serializerOpts = { treeAdapter };
const html = escapeString(test.data);
const lines = html.split(/\r?\n/g);
const parserOpts = {
treeAdapter,
sourceCodeLocationInfo: true,
};
const parsingResult = await parse(html, parserOpts);
const document = parsingResult.node;
walkTree(document, treeAdapter, (node) => {
const location = treeAdapter.getNodeSourceCodeLocation(node);
if (location) {
const fragment = treeAdapter.createDocumentFragment();
treeAdapter.appendChild(fragment, node);
const serializedNode = parse5.serialize(fragment, serializerOpts);
assertNodeLocation(location, serializedNode, html, lines);
// TODO: None of the cases below are ever matched.
if (location.startTag) {
assertStartTagLocation(location, serializedNode, html, lines);
}
if (location.endTag) {
assertEndTagLocation(location, serializedNode, html, lines);
}
if (location.attrs) {
assertAttrsLocation(location, serializedNode, html, lines);
}
}
});
});
}
});
}