• 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 <any>(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        });
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 => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); });
117
118                for (const childName in node) {
119                    if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" ||
120                        // for now ignore jsdoc comments
121                        childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") {
122                        continue;
123                    }
124                    const child = (<any>node)[childName];
125                    if (isNodeOrArray(child)) {
126                        assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
127                            "Missing child when forEach'ing over node: " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
128                    }
129                }
130            }
131        }
132    }
133
134    function isNodeOrArray(a: any): boolean {
135        return a !== undefined && typeof a.pos === "number";
136    }
137
138    export function convertDiagnostics(diagnostics: readonly ts.Diagnostic[]) {
139        return diagnostics.map(convertDiagnostic);
140    }
141
142    function convertDiagnostic(diagnostic: ts.Diagnostic) {
143        return {
144            start: diagnostic.start,
145            length: diagnostic.length,
146            messageText: ts.flattenDiagnosticMessageText(diagnostic.messageText, Harness.IO.newLine()),
147            category: ts.diagnosticCategoryName(diagnostic, /*lowerCase*/ false),
148            code: diagnostic.code
149        };
150    }
151
152    export function sourceFileToJSON(file: ts.Node): string {
153        return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, "    ");
154
155        function getKindName(k: number | string): string {
156            if (ts.isString(k)) {
157                return k;
158            }
159
160            // For some markers in SyntaxKind, we should print its original syntax name instead of
161            // the marker name in tests.
162            if (k === (<any>ts).SyntaxKind.FirstJSDocNode ||
163                k === (<any>ts).SyntaxKind.LastJSDocNode ||
164                k === (<any>ts).SyntaxKind.FirstJSDocTagNode ||
165                k === (<any>ts).SyntaxKind.LastJSDocTagNode) {
166                for (const kindName in (<any>ts).SyntaxKind) {
167                    if ((<any>ts).SyntaxKind[kindName] === k) {
168                        return kindName;
169                    }
170                }
171            }
172
173            return (<any>ts).SyntaxKind[k];
174        }
175
176        function getFlagName(flags: any, f: number): any {
177            if (f === 0) {
178                return 0;
179            }
180
181            let result = "";
182            ts.forEach(Object.getOwnPropertyNames(flags), (v: any) => {
183                if (isFinite(v)) {
184                    v = +v;
185                    if (f === +v) {
186                        result = flags[v];
187                        return true;
188                    }
189                    else if ((f & v) > 0) {
190                        if (result.length) {
191                            result += " | ";
192                        }
193                        result += flags[v];
194                        return false;
195                    }
196                }
197            });
198            return result;
199        }
200
201        function getNodeFlagName(f: number) { return getFlagName((<any>ts).NodeFlags, f); }
202
203        function serializeNode(n: ts.Node): any {
204            const o: any = { kind: getKindName(n.kind) };
205            if (ts.containsParseError(n)) {
206                o.containsParseError = true;
207            }
208
209            for (const propertyName of Object.getOwnPropertyNames(n) as readonly (keyof ts.SourceFile | keyof ts.Identifier)[]) {
210                switch (propertyName) {
211                    case "parent":
212                    case "symbol":
213                    case "locals":
214                    case "localSymbol":
215                    case "kind":
216                    case "id":
217                    case "nodeCount":
218                    case "symbolCount":
219                    case "identifierCount":
220                    case "scriptSnapshot":
221                        // Blocklist of items we never put in the baseline file.
222                        break;
223
224                    case "originalKeywordKind":
225                        o[propertyName] = getKindName((<any>n)[propertyName]);
226                        break;
227
228                    case "flags":
229                        // Clear the flags that are produced by aggregating child values. That is ephemeral
230                        // data we don't care about in the dump. We only care what the parser set directly
231                        // on the AST.
232                        const flags = n.flags & ~(ts.NodeFlags.JavaScriptFile | ts.NodeFlags.HasAggregatedChildData);
233                        if (flags) {
234                            o[propertyName] = getNodeFlagName(flags);
235                        }
236                        break;
237
238                    case "parseDiagnostics":
239                        o[propertyName] = convertDiagnostics((<any>n)[propertyName]);
240                        break;
241
242                    case "nextContainer":
243                        if (n.nextContainer) {
244                            o[propertyName] = { kind: n.nextContainer.kind, pos: n.nextContainer.pos, end: n.nextContainer.end };
245                        }
246                        break;
247
248                    case "text":
249                        // Include 'text' field for identifiers/literals, but not for source files.
250                        if (n.kind !== ts.SyntaxKind.SourceFile) {
251                            o[propertyName] = (<any>n)[propertyName];
252                        }
253                        break;
254
255                    default:
256                        o[propertyName] = (<any>n)[propertyName];
257                }
258            }
259
260            return o;
261        }
262    }
263
264    export function assertDiagnosticsEquals(array1: readonly ts.Diagnostic[], array2: readonly ts.Diagnostic[]) {
265        if (array1 === array2) {
266            return;
267        }
268
269        assert(array1, "array1");
270        assert(array2, "array2");
271
272        assert.equal(array1.length, array2.length, "array1.length !== array2.length");
273
274        for (let i = 0; i < array1.length; i++) {
275            const d1 = array1[i];
276            const d2 = array2[i];
277
278            assert.equal(d1.start, d2.start, "d1.start !== d2.start");
279            assert.equal(d1.length, d2.length, "d1.length !== d2.length");
280            assert.equal(
281                ts.flattenDiagnosticMessageText(d1.messageText, Harness.IO.newLine()),
282                ts.flattenDiagnosticMessageText(d2.messageText, Harness.IO.newLine()), "d1.messageText !== d2.messageText");
283            assert.equal(d1.category, d2.category, "d1.category !== d2.category");
284            assert.equal(d1.code, d2.code, "d1.code !== d2.code");
285        }
286    }
287
288    export function assertStructuralEquals(node1: ts.Node, node2: ts.Node) {
289        if (node1 === node2) {
290            return;
291        }
292
293        assert(node1, "node1");
294        assert(node2, "node2");
295        assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos");
296        assert.equal(node1.end, node2.end, "node1.end !== node2.end");
297        assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind");
298
299        // call this on both nodes to ensure all propagated flags have been set (and thus can be
300        // compared).
301        assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
302        assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags");
303
304        ts.forEachChild(node1,
305            child1 => {
306                const childName = findChildName(node1, child1);
307                const child2: ts.Node = (<any>node2)[childName];
308
309                assertStructuralEquals(child1, child2);
310            },
311            array1 => {
312                const childName = findChildName(node1, array1);
313                const array2: ts.NodeArray<ts.Node> = (<any>node2)[childName];
314
315                assertArrayStructuralEquals(array1, array2);
316            });
317    }
318
319    function assertArrayStructuralEquals(array1: ts.NodeArray<ts.Node>, array2: ts.NodeArray<ts.Node>) {
320        if (array1 === array2) {
321            return;
322        }
323
324        assert(array1, "array1");
325        assert(array2, "array2");
326        assert.equal(array1.pos, array2.pos, "array1.pos !== array2.pos");
327        assert.equal(array1.end, array2.end, "array1.end !== array2.end");
328        assert.equal(array1.length, array2.length, "array1.length !== array2.length");
329
330        for (let i = 0; i < array1.length; i++) {
331            assertStructuralEquals(array1[i], array2[i]);
332        }
333    }
334
335    function findChildName(parent: any, child: any) {
336        for (const name in parent) {
337            if (parent.hasOwnProperty(name) && parent[name] === child) {
338                return name;
339            }
340        }
341
342        throw new Error("Could not find child in parent");
343    }
344
345    const maxHarnessFrames = 1;
346
347    export function filterStack(error: Error, stackTraceLimit = Infinity) {
348        const stack = <string>(<any>error).stack;
349        if (stack) {
350            const lines = stack.split(/\r\n?|\n/g);
351            const filtered: string[] = [];
352            let frameCount = 0;
353            let harnessFrameCount = 0;
354            for (let line of lines) {
355                if (isStackFrame(line)) {
356                    if (frameCount >= stackTraceLimit
357                        || isMocha(line)
358                        || isNode(line)) {
359                        continue;
360                    }
361
362                    if (isHarness(line)) {
363                        if (harnessFrameCount >= maxHarnessFrames) {
364                            continue;
365                        }
366
367                        harnessFrameCount++;
368                    }
369
370                    line = line.replace(/\bfile:\/\/\/(.*?)(?=(:\d+)*($|\)))/, (_, path) => ts.sys.resolvePath(path));
371                    frameCount++;
372                }
373
374                filtered.push(line);
375            }
376
377            (<any>error).stack = filtered.join(Harness.IO.newLine());
378        }
379
380        return error;
381    }
382
383    function isStackFrame(line: string) {
384        return /^\s+at\s/.test(line);
385    }
386
387    function isMocha(line: string) {
388        return /[\\/](node_modules|components)[\\/]mocha(js)?[\\/]|[\\/]mocha\.js/.test(line);
389    }
390
391    function isNode(line: string) {
392        return /\((timers|events|node|module)\.js:/.test(line);
393    }
394
395    function isHarness(line: string) {
396        return /[\\/]src[\\/]harness[\\/]|[\\/]run\.js/.test(line);
397    }
398}
399