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