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