thenavisapp
Version:
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
130 lines (109 loc) • 4.83 kB
text/typescript
import { readFile, writeFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { Parser, type DefaultTreeAdapterMap, type TreeAdapterTypeMap, type Token, defaultTreeAdapter } from 'parse5';
import type { HtmlLibToken } from 'parse5-test-utils/utils/generate-tokenization-tests.js';
import { parseDatFile } from 'parse5-test-utils/utils/parse-dat-file.js';
import { addSlashes } from 'parse5-test-utils/utils/common.js';
for (const file of process.argv.slice(2)) {
const content = await readFile(file, 'utf8');
const feedbackTestContent = generateParserFeedbackTest(content);
const feedbackTestFile = `test/data/parser-feedback/${basename(file, '.dat')}.test`;
await writeFile(feedbackTestFile, feedbackTestContent);
}
function collectParserTokens(html: string): HtmlLibToken[] {
const tokens: HtmlLibToken[] = [];
class ExtendedParser<T extends TreeAdapterTypeMap> extends Parser<T> {
private isTopLevel = true;
/**
* We only want to add tokens once. We guard against recursive calls
* using the `isTopLevel` flag.
*/
private guardTopLevel(fn: () => void, getToken: () => HtmlLibToken): void {
const { isTopLevel } = this;
this.isTopLevel = false;
fn();
if (isTopLevel) {
this.isTopLevel = true;
const token = getToken();
if (token[0] === 'Character') {
if (token[1] == null || token[1].length === 0) {
return;
}
const lastToken = tokens.at(-1);
if (lastToken?.[0] === 'Character') {
lastToken[1] += token[1];
return;
}
}
tokens.push(token);
}
}
override onComment(token: Token.CommentToken): void {
this.guardTopLevel(
() => super.onComment(token),
() => ['Comment', token.data],
);
}
override onDoctype(token: Token.DoctypeToken): void {
this.guardTopLevel(
() => super.onDoctype(token),
() => ['DOCTYPE', token.name, token.publicId, token.systemId, !token.forceQuirks],
);
}
override onStartTag(token: Token.TagToken): void {
this.guardTopLevel(
() => super.onStartTag(token),
() => {
const reformatedAttrs = Object.fromEntries(token.attrs.map(({ name, value }) => [name, value]));
const startTagEntry: HtmlLibToken = ['StartTag', token.tagName, reformatedAttrs];
if (token.selfClosing) {
startTagEntry.push(true);
}
return startTagEntry;
},
);
}
override onEndTag(token: Token.TagToken): void {
this.guardTopLevel(
() => super.onEndTag(token),
// NOTE: parser feedback simulator can produce adjusted SVG
// tag names for end tag tokens so we need to lower case it
() => ['EndTag', token.tagName.toLowerCase()],
);
}
override onCharacter(token: Token.CharacterToken): void {
this.guardTopLevel(
() => super.onCharacter(token),
() => ['Character', token.chars],
);
}
override onNullCharacter(token: Token.CharacterToken): void {
this.guardTopLevel(
() => super.onNullCharacter(token),
() => ['Character', token.chars],
);
}
override onWhitespaceCharacter(token: Token.CharacterToken): void {
const { skipNextNewLine } = this;
const { chars } = token;
this.guardTopLevel(
() => super.onWhitespaceCharacter(token),
() => ['Character', skipNextNewLine && chars.startsWith('\n') ? chars.slice(1) : chars],
);
}
}
ExtendedParser.parse(html);
return tokens;
}
function generateParserFeedbackTest(parserTestFile: string): string {
const tests = parseDatFile<DefaultTreeAdapterMap>(parserTestFile, defaultTreeAdapter);
const feedbackTest = {
tests: tests.map(({ input, fragmentContext }) => ({
fragmentContext: fragmentContext?.tagName ?? null,
description: addSlashes(input),
input,
output: collectParserTokens(input),
})),
};
return JSON.stringify(feedbackTest, null, 4);
}