• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace Utils {
2    export function encodeString(s: string): string {
3        return ts.sys.bufferFrom!(s).toString("utf8");
4    }
5
6    export function byteLength(s: string, encoding?: string): number {
7        // stub implementation if Buffer is not available (in-browser case)
8        return Buffer.byteLength(s, encoding as ts.BufferEncoding | undefined);
9    }
10
11    export function evalFile(fileContents: string, fileName: string, nodeContext?: any) {
12        const vm = require("vm");
13        if (nodeContext) {
14            vm.runInNewContext(fileContents, nodeContext, fileName);
15        }
16        else {
17            vm.runInThisContext(fileContents, fileName);
18        }
19    }
20
21    /** Splits the given string on \r\n, or on only \n if that fails, or on only \r if *that* fails. */
22    export function splitContentByNewlines(content: string) {
23        // Split up the input file by line
24        // Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so
25        // we have to use string-based splitting instead and try to figure out the delimiting chars
26        let lines = content.split("\r\n");
27        if (lines.length === 1) {
28            lines = content.split("\n");
29
30            if (lines.length === 1) {
31                lines = content.split("\r");
32            }
33        }
34        return lines;
35    }
36
37    /** Reads a file under /tests */
38    export function readTestFile(path: string) {
39        if (path.indexOf("tests") < 0) {
40            path = "tests/" + path;
41        }
42
43        let content: string | undefined;
44        try {
45            content = Harness.IO.readFile(Harness.userSpecifiedRoot + path);
46        }
47        catch (err) {
48            return undefined;
49        }
50
51        return content;
52    }
53
54    export function memoize<T extends ts.AnyFunction>(f: T, memoKey: (...anything: any[]) => string): T {
55        const cache = new ts.Map<string, any>();
56
57        return (function (this: any, ...args: any[]) {
58            const key = memoKey(...args);
59            if (cache.has(key)) {
60                return cache.get(key);
61            }
62            else {
63                const value = f.apply(this, args);
64                cache.set(key, value);
65                return value;
66            }
67        } as any);
68    }
69
70    export const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
71
72    export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined) {
73        const queue: [ts.Node | undefined, ts.Node | undefined][] = [[node, parent]];
74        for (const [node, parent] of queue) {
75            assertInvariantsWorker(node, parent);
76        }
77
78        function assertInvariantsWorker(node: ts.Node | undefined, parent: ts.Node | undefined): void {
79            if (node) {
80                assert.isFalse(node.pos < 0, "node.pos < 0");
81                assert.isFalse(node.end < 0, "node.end < 0");
82                assert.isFalse(node.end < node.pos, "node.end < node.pos");
83                assert.equal(node.parent, parent, "node.parent !== parent");
84
85                if (parent) {
86                    // Make sure each child is contained within the parent.
87                    assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos");
88                    assert.isFalse(node.end > parent.end, "node.end > parent.end");
89                }
90
91                ts.forEachChild(node, child => {
92                    queue.push([child, node]);
93                });
94
95                // Make sure each of the children is in order.
96                let currentPos = 0;
97                ts.forEachChild(node,
98                    child => {
99                        assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
100                        currentPos = child.end;
101                    },
102                    array => {
103                        assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
104                        assert.isFalse(array.end > node.end, "array.end > node.end");
105                        assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
106
107                        for (const item of array) {
108                            assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos");
109                            currentPos = item.end;
110                        }
111
112                        currentPos = array.end;
113                    });
114
115                const childNodesAndArrays: any[] = [];
116                ts.forEachChild(node, child => {
117                    childNodesAndArrays.push(child);
118                }, array => {
119                    childNodesAndArrays.push(array);
120                });
121
122                for (const childName in node) {
123                    if (childName === "parent" ||
124                        childName === "nextContainer" ||
125                        childName === "modifiers" ||
126                        childName === "externalModuleIndicator" ||
127                        // for now ignore jsdoc comments
128                        childName === "jsDocComment" ||
129                        childName === "checkJsDirective" ||
130                        childName === "commonJsModuleIndicator" ||
131                        // ignore nodes added only to report grammar errors
132                        childName === "illegalInitializer" ||
133                        childName === "illegalDecorators" ||
134                        childName === "illegalModifiers" ||
135                        childName === "illegalQuestionToken" ||
136                        childName === "illegalExclamationToken" ||
137                        childName === "illegalTypeParameters" ||
138                        childName === "illegalType") {
139                        continue;
140                    }
141                    const child = (node as any)[childName];
142                    if (isNodeOrArray(child)) {
143                        assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
144                            "Missing child when forEach'ing over node: " + ts.Debug.formatSyntaxKind(node.kind) + "-" + childName);
145                    }
146                }
147            }
148        }
149    }
150
151    function isNodeOrArray(a: any): boolean {
152        return a !== undefined && typeof a.pos === "number";
153    }
154
155    export function convertDiagnostics(diagnostics: readonly ts.Diagnostic[]) {
156        return diagnostics.map(convertDiagnostic);
157    }
158
159    function convertDiagnostic(diagnostic: ts.Diagnostic) {
160        return {
161            start: diagnostic.start,
162            length: diagnostic.length,
163            messageText: ts.flattenDiagnosticMessageText(diagnostic.messageText, Harness.IO.newLine()),
164            category: ts.diagnosticCategoryName(diagnostic, /*lowerCase*/ false),
165            code: diagnostic.code
166        };
167    }
168
169    export function sourceFileToJSON(file: ts.Node): string {
170        return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, "    ");
171
172        function getKindName(k: number | string | undefined): string | undefined {
173            if (k === undefined || ts.isString(k)) {
174                return k;
175            }
176            return ts.Debug.formatSyntaxKind(k);
177        }
178
179        function getNodeFlagName(f: number) {
180            return ts.Debug.formatNodeFlags(f);
181        }
182
183        function serializeNode(n: ts.Node): any {
184            const o: any = { kind: getKindName(n.kind) };
185            if (ts.containsParseError(n)) {
186                o.containsParseError = true;
187            }
188
189            for (const propertyName of Object.getOwnPropertyNames(n) as readonly (keyof ts.SourceFile | keyof ts.Identifier)[]) {
190                switch (propertyName) {
191                    case "parent":
192                    case "symbol":
193                    case "locals":
194                    case "localSymbol":
195                    case "kind":
196                    case "id":
197                    case "nodeCount":
198                    case "symbolCount":
199                    case "identifierCount":
200                    case "scriptSnapshot":
201                        // Blocklist of items we never put in the baseline file.
202                        break;
203
204                    case "originalKeywordKind":
205                        o[propertyName] = getKindName((n as any)[propertyName]);
206                        break;
207
208                    case "flags":
209                        // Clear the flags that are produced by aggregating child values. That is ephemeral
210                        // data we don't care about in the dump. We only care what the parser set directly
211                        // on the AST.
212                        const flags = n.flags & ~(ts.NodeFlags.JavaScriptFile | ts.NodeFlags.HasAggregatedChildData);
213                        if (flags) {
214                            o[propertyName] = getNodeFlagName(flags);
215                        }
216                        break;
217
218                    case "parseDiagnostics":
219                        o[propertyName] = convertDiagnostics((n as any)[propertyName]);
220                        break;
221
222                    case "nextContainer":
223                        if (n.nextContainer) {
224                            o[propertyName] = { kind: n.nextContainer.kind, pos: n.nextContainer.pos, end: n.nextContainer.end };
225                        }
226                        break;
227
228                    case "text":
229                        // Include 'text' field for identifiers/literals, but not for source files.
230                        if (n.kind !== ts.SyntaxKind.SourceFile) {
231                            o[propertyName] = (n as any)[propertyName];
232                        }
233                        break;
234
235                    default:
236                        o[propertyName] = (n as any)[propertyName];
237                }
238            }
239
240            return o;
241        }
242    }
243
244    export function assertDiagnosticsEquals(array1: readonly ts.Diagnostic[], array2: readonly ts.Diagnostic[]) {
245        if (array1 === array2) {
246            return;
247        }
248
249        assert(array1, "array1");
250        assert(array2, "array2");
251
252        assert.equal(array1.length, array2.length, "array1.length !== array2.length");
253
254        for (let i = 0; i < array1.length; i++) {
255            const d1 = array1[i];
256            const d2 = array2[i];
257
258            assert.equal(d1.start, d2.start, "d1.start !== d2.start");
259            assert.equal(d1.length, d2.length, "d1.length !== d2.length");
260            assert.equal(
261                ts.flattenDiagnosticMessageText(d1.messageText, Harness.IO.newLine()),
262                ts.flattenDiagnosticMessageText(d2.messageText, Harness.IO.newLine()), "d1.messageText !== d2.messageText");
263            assert.equal(d1.category, d2.category, "d1.category !== d2.category");
264            assert.equal(d1.code, d2.code, "d1.code !== d2.code");
265        }
266    }
267
268    export function assertStructuralEquals(node1: ts.Node, node2: ts.Node) {
269        if (node1 === node2) {
270            return;
271        }
272
273        assert(node1, "node1");
274        assert(node2, "node2");
275        assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos");
276        assert.equal(node1.end, node2.end, "node1.end !== node2.end");
277        assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind");
278
279        // call this on both nodes to ensure all propagated flags have been set (and thus can be
280        // compared).
281        assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
282        assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags");
283
284        ts.forEachChild(node1,
285            child1 => {
286                const childName = findChildName(node1, child1);
287                const child2: ts.Node = (node2 as any)[childName];
288
289                assertStructuralEquals(child1, child2);
290            },
291            array1 => {
292                const childName = findChildName(node1, array1);
293                const array2: ts.NodeArray<ts.Node> = (node2 as any)[childName];
294
295                assertArrayStructuralEquals(array1, array2);
296            });
297    }
298
299    function assertArrayStructuralEquals(array1: ts.NodeArray<ts.Node>, array2: ts.NodeArray<ts.Node>) {
300        if (array1 === array2) {
301            return;
302        }
303
304        assert(array1, "array1");
305        assert(array2, "array2");
306        assert.equal(array1.pos, array2.pos, "array1.pos !== array2.pos");
307        assert.equal(array1.end, array2.end, "array1.end !== array2.end");
308        assert.equal(array1.length, array2.length, "array1.length !== array2.length");
309
310        for (let i = 0; i < array1.length; i++) {
311            assertStructuralEquals(array1[i], array2[i]);
312        }
313    }
314
315    function findChildName(parent: any, child: any) {
316        for (const name in parent) {
317            if (ts.hasProperty(parent, name) && parent[name] === child) {
318                return name;
319            }
320        }
321
322        throw new Error("Could not find child in parent");
323    }
324
325    const maxHarnessFrames = 1;
326
327    export function filterStack(error: Error, stackTraceLimit = Infinity) {
328        const stack = (error as any).stack as string;
329        if (stack) {
330            const lines = stack.split(/\r\n?|\n/g);
331            const filtered: string[] = [];
332            let frameCount = 0;
333            let harnessFrameCount = 0;
334            for (let line of lines) {
335                if (isStackFrame(line)) {
336                    if (frameCount >= stackTraceLimit
337                        || isMocha(line)
338                        || isNode(line)) {
339                        continue;
340                    }
341
342                    if (isHarness(line)) {
343                        if (harnessFrameCount >= maxHarnessFrames) {
344                            continue;
345                        }
346
347                        harnessFrameCount++;
348                    }
349
350                    line = line.replace(/\bfile:\/\/\/(.*?)(?=(:\d+)*($|\)))/, (_, path) => ts.sys.resolvePath(path));
351                    frameCount++;
352                }
353
354                filtered.push(line);
355            }
356
357            (error as any).stack = filtered.join(Harness.IO.newLine());
358        }
359
360        return error;
361    }
362
363    function isStackFrame(line: string) {
364        return /^\s+at\s/.test(line);
365    }
366
367    function isMocha(line: string) {
368        return /[\\/](node_modules|components)[\\/]mocha(js)?[\\/]|[\\/]mocha\.js/.test(line);
369    }
370
371    function isNode(line: string) {
372        return /\((timers|events|node|module)\.js:/.test(line);
373    }
374
375    function isHarness(line: string) {
376        return /[\\/]src[\\/]harness[\\/]|[\\/]run\.js/.test(line);
377    }
378}
379