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