193fb6ee3Sopenharmony_ciimport { Writable, finished as finishedCb, type Readable } from 'node:stream';
293fb6ee3Sopenharmony_ciimport * as assert from 'node:assert';
393fb6ee3Sopenharmony_ciimport { type TreeAdapter, type Token, defaultTreeAdapter } from 'parse5';
493fb6ee3Sopenharmony_ciimport { adapter as htmlparser2Adapter } from 'parse5-htmlparser2-tree-adapter';
593fb6ee3Sopenharmony_ci
693fb6ee3Sopenharmony_ci// Ensure the default tree adapter matches the expected type.
793fb6ee3Sopenharmony_ciexport const treeAdapters = {
893fb6ee3Sopenharmony_ci    default: defaultTreeAdapter,
993fb6ee3Sopenharmony_ci    htmlparser2: htmlparser2Adapter,
1093fb6ee3Sopenharmony_ci} as const;
1193fb6ee3Sopenharmony_ci
1293fb6ee3Sopenharmony_ciexport function addSlashes(str: string): string {
1393fb6ee3Sopenharmony_ci    return str
1493fb6ee3Sopenharmony_ci        .replace(/\t/g, '\\t')
1593fb6ee3Sopenharmony_ci        .replace(/\n/g, '\\n')
1693fb6ee3Sopenharmony_ci        .replace(/\f/g, '\\f')
1793fb6ee3Sopenharmony_ci        .replace(/\r/g, '\\r')
1893fb6ee3Sopenharmony_ci        .replace(/\0/g, '\\u0000');
1993fb6ee3Sopenharmony_ci}
2093fb6ee3Sopenharmony_ci
2193fb6ee3Sopenharmony_cifunction createDiffMarker(markerPosition: number): string {
2293fb6ee3Sopenharmony_ci    return '^\n'.padStart(markerPosition + 1, ' ');
2393fb6ee3Sopenharmony_ci}
2493fb6ee3Sopenharmony_ci
2593fb6ee3Sopenharmony_cifunction getRandomChunkSize(min = 1, max = 10): number {
2693fb6ee3Sopenharmony_ci    return min + Math.floor(Math.random() * (max - min + 1));
2793fb6ee3Sopenharmony_ci}
2893fb6ee3Sopenharmony_ci
2993fb6ee3Sopenharmony_ciexport function makeChunks(str: string, minSize?: number, maxSize?: number): string[] {
3093fb6ee3Sopenharmony_ci    if (str.length === 0) {
3193fb6ee3Sopenharmony_ci        return [''];
3293fb6ee3Sopenharmony_ci    }
3393fb6ee3Sopenharmony_ci
3493fb6ee3Sopenharmony_ci    const chunks = [];
3593fb6ee3Sopenharmony_ci    let start = 0;
3693fb6ee3Sopenharmony_ci
3793fb6ee3Sopenharmony_ci    // NOTE: start with 1, so we avoid situation when we have just one huge chunk
3893fb6ee3Sopenharmony_ci    let end = 1;
3993fb6ee3Sopenharmony_ci
4093fb6ee3Sopenharmony_ci    while (start < str.length) {
4193fb6ee3Sopenharmony_ci        chunks.push(str.substring(start, end));
4293fb6ee3Sopenharmony_ci        start = end;
4393fb6ee3Sopenharmony_ci        end = Math.min(end + getRandomChunkSize(minSize, maxSize), str.length);
4493fb6ee3Sopenharmony_ci    }
4593fb6ee3Sopenharmony_ci
4693fb6ee3Sopenharmony_ci    return chunks;
4793fb6ee3Sopenharmony_ci}
4893fb6ee3Sopenharmony_ci
4993fb6ee3Sopenharmony_ciexport class WritableStreamStub extends Writable {
5093fb6ee3Sopenharmony_ci    writtenData = '';
5193fb6ee3Sopenharmony_ci
5293fb6ee3Sopenharmony_ci    constructor() {
5393fb6ee3Sopenharmony_ci        super({ decodeStrings: false });
5493fb6ee3Sopenharmony_ci    }
5593fb6ee3Sopenharmony_ci
5693fb6ee3Sopenharmony_ci    override _write(chunk: string, _encoding: string, callback: () => void): void {
5793fb6ee3Sopenharmony_ci        assert.strictEqual(typeof chunk, 'string', 'Expected output to be a string stream');
5893fb6ee3Sopenharmony_ci        this.writtenData += chunk;
5993fb6ee3Sopenharmony_ci        callback();
6093fb6ee3Sopenharmony_ci    }
6193fb6ee3Sopenharmony_ci}
6293fb6ee3Sopenharmony_ci
6393fb6ee3Sopenharmony_ciexport function normalizeNewLine(str: string): string {
6493fb6ee3Sopenharmony_ci    return str.replace(/\r\n/g, '\n');
6593fb6ee3Sopenharmony_ci}
6693fb6ee3Sopenharmony_ci
6793fb6ee3Sopenharmony_ciexport function removeNewLines(str: string): string {
6893fb6ee3Sopenharmony_ci    return str.replace(/\r/g, '').replace(/\n/g, '');
6993fb6ee3Sopenharmony_ci}
7093fb6ee3Sopenharmony_ci
7193fb6ee3Sopenharmony_ciexport function writeChunkedToStream(str: string, stream: Writable): void {
7293fb6ee3Sopenharmony_ci    const chunks = makeChunks(str);
7393fb6ee3Sopenharmony_ci    const lastChunkIdx = chunks.length - 1;
7493fb6ee3Sopenharmony_ci
7593fb6ee3Sopenharmony_ci    for (const [idx, chunk] of chunks.entries()) {
7693fb6ee3Sopenharmony_ci        if (idx === lastChunkIdx) {
7793fb6ee3Sopenharmony_ci            stream.end(chunk);
7893fb6ee3Sopenharmony_ci        } else {
7993fb6ee3Sopenharmony_ci            stream.write(chunk);
8093fb6ee3Sopenharmony_ci        }
8193fb6ee3Sopenharmony_ci    }
8293fb6ee3Sopenharmony_ci}
8393fb6ee3Sopenharmony_ci
8493fb6ee3Sopenharmony_ciexport function generateTestsForEachTreeAdapter(name: string, ctor: (adapter: TreeAdapter) => void): void {
8593fb6ee3Sopenharmony_ci    describe(name, () => {
8693fb6ee3Sopenharmony_ci        for (const adapterName of Object.keys(treeAdapters)) {
8793fb6ee3Sopenharmony_ci            const adapter = treeAdapters[adapterName as keyof typeof treeAdapters] as TreeAdapter;
8893fb6ee3Sopenharmony_ci
8993fb6ee3Sopenharmony_ci            describe(`Tree adapter: ${adapterName}`, () => {
9093fb6ee3Sopenharmony_ci                ctor(adapter);
9193fb6ee3Sopenharmony_ci            });
9293fb6ee3Sopenharmony_ci        }
9393fb6ee3Sopenharmony_ci    });
9493fb6ee3Sopenharmony_ci}
9593fb6ee3Sopenharmony_ci
9693fb6ee3Sopenharmony_ciexport function getStringDiffMsg(actual: string, expected: string): string {
9793fb6ee3Sopenharmony_ci    for (let i = 0; i < expected.length; i++) {
9893fb6ee3Sopenharmony_ci        if (actual[i] !== expected[i]) {
9993fb6ee3Sopenharmony_ci            let diffMsg = `\nString differ at index ${i}\n`;
10093fb6ee3Sopenharmony_ci
10193fb6ee3Sopenharmony_ci            const expectedStr = `Expected: ${addSlashes(expected.substring(i - 100, i + 1))}`;
10293fb6ee3Sopenharmony_ci            const expectedDiffMarker = createDiffMarker(expectedStr.length);
10393fb6ee3Sopenharmony_ci
10493fb6ee3Sopenharmony_ci            diffMsg += `${expectedStr}${addSlashes(expected.substring(i + 1, i + 20))}\n${expectedDiffMarker}`;
10593fb6ee3Sopenharmony_ci
10693fb6ee3Sopenharmony_ci            const actualStr = `Actual:   ${addSlashes(actual.substring(i - 100, i + 1))}`;
10793fb6ee3Sopenharmony_ci            const actualDiffMarker = createDiffMarker(actualStr.length);
10893fb6ee3Sopenharmony_ci
10993fb6ee3Sopenharmony_ci            diffMsg += `${actualStr}${addSlashes(actual.substring(i + 1, i + 20))}\n${actualDiffMarker}`;
11093fb6ee3Sopenharmony_ci
11193fb6ee3Sopenharmony_ci            return diffMsg;
11293fb6ee3Sopenharmony_ci        }
11393fb6ee3Sopenharmony_ci    }
11493fb6ee3Sopenharmony_ci
11593fb6ee3Sopenharmony_ci    return '';
11693fb6ee3Sopenharmony_ci}
11793fb6ee3Sopenharmony_ci
11893fb6ee3Sopenharmony_ciexport function getSubstringByLineCol(lines: string[], loc: Token.Location): string {
11993fb6ee3Sopenharmony_ci    lines = lines.slice(loc.startLine - 1, loc.endLine);
12093fb6ee3Sopenharmony_ci
12193fb6ee3Sopenharmony_ci    const last = lines.length - 1;
12293fb6ee3Sopenharmony_ci
12393fb6ee3Sopenharmony_ci    lines[last] = lines[last].substring(0, loc.endCol - 1);
12493fb6ee3Sopenharmony_ci    lines[0] = lines[0].substring(loc.startCol - 1);
12593fb6ee3Sopenharmony_ci
12693fb6ee3Sopenharmony_ci    return lines.join('\n');
12793fb6ee3Sopenharmony_ci}
12893fb6ee3Sopenharmony_ci
12993fb6ee3Sopenharmony_ci// TODO [engine:node@>=16]: Replace this with `finished` from 'node:stream/promises'.
13093fb6ee3Sopenharmony_ci
13193fb6ee3Sopenharmony_ciexport function finished(stream: Writable | Readable): Promise<void> {
13293fb6ee3Sopenharmony_ci    return new Promise((resolve, reject) => finishedCb(stream, (err) => (err ? reject(err) : resolve())));
13393fb6ee3Sopenharmony_ci}
134