• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * Common utilities
3 */
4namespace Utils {
5    const testPathPrefixRegExp = /(?:(file:\/{3})|\/)\.(ts|lib|src)\//g;
6    export function removeTestPathPrefixes(text: string, retainTrailingDirectorySeparator?: boolean): string {
7        return text !== undefined ? text.replace(testPathPrefixRegExp, (_, scheme) => scheme || (retainTrailingDirectorySeparator ? "/" : "")) : undefined!; // TODO: GH#18217
8    }
9
10    function createDiagnosticMessageReplacer<R extends (messageArgs: string[], ...args: string[]) => string[]>(diagnosticMessage: ts.DiagnosticMessage, replacer: R) {
11        const messageParts = diagnosticMessage.message.split(/{\d+}/g);
12        const regExp = new RegExp(`^(?:${messageParts.map(ts.regExpEscape).join("(.*?)")})$`);
13        type Args<R> = R extends (messageArgs: string[], ...args: infer A) => string[] ? A : [];
14        return (text: string, ...args: Args<R>) => text.replace(regExp, (_, ...fixedArgs) => ts.formatStringFromArgs(diagnosticMessage.message, replacer(fixedArgs, ...args)));
15    }
16
17    const replaceTypesVersionsMessage = createDiagnosticMessageReplacer(
18        ts.Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2,
19        ([entry, , moduleName], compilerVersion) => [entry, compilerVersion, moduleName]);
20
21    export function sanitizeTraceResolutionLogEntry(text: string) {
22        return text && removeTestPathPrefixes(replaceTypesVersionsMessage(text, "3.1.0-dev"));
23    }
24
25    /**
26     * Removes leading indentation from a template literal string.
27     */
28    export function dedent(array: TemplateStringsArray, ...args: any[]) {
29        let text = array[0];
30        for (let i = 0; i < args.length; i++) {
31            text += args[i];
32            text += array[i + 1];
33        }
34
35        const lineTerminatorRegExp = /\r\n?|\n/g;
36        const lines: string[] = [];
37        const lineTerminators: string[] = [];
38        let match: RegExpExecArray | null;
39        let lineStart = 0;
40        while (match = lineTerminatorRegExp.exec(text)) {
41            if (lineStart !== match.index || lines.length > 0) {
42                lines.push(text.slice(lineStart, match.index));
43                lineTerminators.push(match[0]);
44            }
45            lineStart = match.index + match[0].length;
46        }
47
48        if (lineStart < text.length) {
49            lines.push(text.slice(lineStart));
50        }
51
52        const indentation = guessIndentation(lines);
53
54        let result = "";
55        for (let i = 0; i < lines.length; i++) {
56            const lineText = lines[i];
57            const line = indentation ? lineText.slice(indentation) : lineText;
58            result += line;
59            if (i < lineTerminators.length) {
60                result += lineTerminators[i];
61            }
62        }
63        return result;
64    }
65
66    function guessIndentation(lines: string[]) {
67        let indentation: number | undefined;
68        for (const line of lines) {
69            for (let i = 0; i < line.length && (indentation === undefined || i < indentation); i++) {
70                if (!ts.isWhiteSpaceLike(line.charCodeAt(i))) {
71                    if (indentation === undefined || i < indentation) {
72                        indentation = i;
73                        break;
74                    }
75                }
76            }
77        }
78        return indentation;
79    }
80
81    export function getByteOrderMarkLength(text: string): number {
82        if (text.length >= 1) {
83            const ch0 = text.charCodeAt(0);
84            if (ch0 === 0xfeff) return 1;
85            if (ch0 === 0xfe) return text.length >= 2 && text.charCodeAt(1) === 0xff ? 2 : 0;
86            if (ch0 === 0xff) return text.length >= 2 && text.charCodeAt(1) === 0xfe ? 2 : 0;
87            if (ch0 === 0xef) return text.length >= 3 && text.charCodeAt(1) === 0xbb && text.charCodeAt(2) === 0xbf ? 3 : 0;
88        }
89        return 0;
90    }
91
92    export function removeByteOrderMark(text: string): string {
93        const length = getByteOrderMarkLength(text);
94        return length ? text.slice(length) : text;
95    }
96
97    export function addUTF8ByteOrderMark(text: string) {
98        return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
99    }
100
101    export function theory<T extends any[]>(name: string, cb: (...args: T) => void, data: T[]) {
102        for (const entry of data) {
103            it(`${name}(${entry.map(formatTheoryDatum).join(", ")})`, () => cb(...entry));
104        }
105    }
106
107    function formatTheoryDatum(value: any) {
108        return typeof value === "function" ? value.name || "<anonymous function>" :
109            value === undefined ? "undefined" :
110            JSON.stringify(value);
111    }
112
113    export interface Deferred<T> {
114        resolve: (value: T | PromiseLike<T>) => void;
115        reject: (reason: unknown) => void;
116        promise: Promise<T>;
117    }
118
119    export function defer<T = void>(): Deferred<T> {
120        let resolve!: (value: T | PromiseLike<T>) => void;
121        let reject!: (reason: unknown) => void;
122        const promise = new Promise<T>((_resolve, _reject) => {
123            resolve = _resolve;
124            reject = _reject;
125        });
126        return { resolve, reject, promise };
127    }
128}