1//// [parserharness.ts] 2// 3// Copyright (c) Microsoft Corporation. All rights reserved. 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17///<reference path='..\compiler\io.ts'/> 18///<reference path='..\compiler\typescript.ts'/> 19///<reference path='..\services\typescriptServices.ts' /> 20///<reference path='diff.ts'/> 21 22declare var assert: Harness.Assert; 23declare var it; 24declare var describe; 25declare var run; 26declare var IO: IIO; 27declare var __dirname; // Node-specific 28 29function switchToForwardSlashes(path: string) { 30 return path.replace(/\\/g, "/"); 31} 32 33function filePath(fullPath: string) { 34 fullPath = switchToForwardSlashes(fullPath); 35 var components = fullPath.split("/"); 36 var path: string[] = components.slice(0, components.length - 1); 37 return path.join("/") + "/"; 38} 39 40var typescriptServiceFileName = filePath(IO.getExecutingFilePath()) + "typescriptServices.js"; 41var typescriptServiceFile = IO.readFile(typescriptServiceFileName); 42if (typeof ActiveXObject === "function") { 43 eval(typescriptServiceFile); 44} else if (typeof require === "function") { 45 var vm = require('vm'); 46 vm.runInThisContext(typescriptServiceFile, 'typescriptServices.js'); 47} else { 48 throw new Error('Unknown context'); 49} 50 51declare module process { 52 export function nextTick(callback: () => any): void; 53 export function on(event: string, listener: Function); 54} 55 56module Harness { 57 // Settings 58 export var userSpecifiedroot = ""; 59 var global = <any>Function("return this").call(null); 60 export var usePull = false; 61 62 export interface ITestMetadata { 63 id: string; 64 desc: string; 65 pass: boolean; 66 perfResults: { 67 mean: number; 68 min: number; 69 max: number; 70 stdDev: number; 71 trials: number[]; 72 }; 73 } 74 export interface IScenarioMetadata { 75 id: string; 76 desc: string; 77 pass: boolean; 78 bugs: string[]; 79 } 80 81 // Assert functions 82 export module Assert { 83 export var bugIds: string[] = []; 84 export var throwAssertError = (error: Error) => { 85 throw error; 86 }; 87 88 // Marks that the current scenario is impacted by a bug 89 export function bug(id: string) { 90 if (bugIds.indexOf(id) < 0) { 91 bugIds.push(id); 92 } 93 } 94 95 // If there are any bugs in the test code, mark the scenario as impacted appropriately 96 export function bugs(content: string) { 97 var bugs = content.match(/\bbug (\d+)/i); 98 if (bugs) { 99 bugs.forEach(bug => assert.bug(bug)); 100 } 101 } 102 103 export function is(result: boolean, msg?: string) { 104 if (!result) { 105 throwAssertError(new Error(msg || "Expected true, got false.")); 106 } 107 } 108 109 export function arrayLengthIs(arr: any[], length: number) { 110 if (arr.length != length) { 111 var actual = ''; 112 arr.forEach(n => actual = actual + '\n ' + n.toString()); 113 throwAssertError(new Error('Expected array to have ' + length + ' elements. Actual elements were:' + actual)); 114 } 115 } 116 117 export function equal(actual, expected) { 118 if (actual !== expected) { 119 throwAssertError(new Error("Expected " + actual + " to equal " + expected)); 120 } 121 } 122 123 export function notEqual(actual, expected) { 124 if (actual === expected) { 125 throwAssertError(new Error("Expected " + actual + " to *not* equal " + expected)); 126 } 127 } 128 129 export function notNull(result) { 130 if (result === null) { 131 throwAssertError(new Error("Expected " + result + " to *not* be null")); 132 } 133 } 134 135 export function compilerWarning(result: Compiler.CompilerResult, line: number, column: number, desc: string) { 136 if (!result.isErrorAt(line, column, desc)) { 137 var actual = ''; 138 result.errors.forEach(err => { 139 actual = actual + '\n ' + err.toString(); 140 }); 141 142 throwAssertError(new Error("Expected compiler warning at (" + line + ", " + column + "): " + desc + "\nActual errors follow: " + actual)); 143 } 144 } 145 146 export function noDiff(text1, text2) { 147 text1 = text1.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n"); 148 text2 = text2.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n"); 149 150 if (text1 !== text2) { 151 var errorString = ""; 152 var text1Lines = text1.split(/\n/); 153 var text2Lines = text2.split(/\n/); 154 for (var i = 0; i < text1Lines.length; i++) { 155 if (text1Lines[i] !== text2Lines[i]) { 156 errorString += "Difference at line " + (i + 1) + ":\n"; 157 errorString += " Left File: " + text1Lines[i] + "\n"; 158 errorString += " Right File: " + text2Lines[i] + "\n\n"; 159 } 160 } 161 throwAssertError(new Error(errorString)); 162 } 163 } 164 165 export function arrayContains(arr: any[], contains: any[]) { 166 var found; 167 168 for (var i = 0; i < contains.length; i++) { 169 found = false; 170 171 for (var j = 0; j < arr.length; j++) { 172 if (arr[j] === contains[i]) { 173 found = true; 174 break; 175 } 176 } 177 178 if (!found) { 179 throwAssertError(new Error("Expected array to contain \"" + contains[i] + "\"")); 180 } 181 } 182 } 183 184 export function arrayContainsOnce(arr: any[], filter: (item: any) => boolean) { 185 var foundCount = 0; 186 187 for (var i = 0; i < arr.length; i++) { 188 if (filter(arr[i])) { 189 foundCount++; 190 } 191 } 192 193 if (foundCount !== 1) { 194 throwAssertError(new Error("Expected array to match element only once (instead of " + foundCount + " times)")); 195 } 196 } 197 } 198 199 /** Splits the given string on \r\n or on only \n if that fails */ 200 export function splitContentByNewlines(content: string) { 201 // Split up the input file by line 202 // Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so 203 // we have to string-based splitting instead and try to figure out the delimiting chars 204 var lines = content.split('\r\n'); 205 if (lines.length === 1) { 206 lines = content.split('\n'); 207 } 208 return lines; 209 } 210 211 /** Reads a file under /tests */ 212 export function readFile(path: string) { 213 214 if (path.indexOf('tests') < 0) { 215 path = "tests/" + path; 216 } 217 218 var content = IO.readFile(Harness.userSpecifiedroot + path); 219 if (content == null) { 220 throw new Error("failed to read file at: '" + Harness.userSpecifiedroot + path + "'"); 221 } 222 223 return content; 224 } 225 226 // Logger 227 export interface ILogger { 228 start: (fileName?: string, priority?: number) => void; 229 end: (fileName?: string) => void; 230 scenarioStart: (scenario: IScenarioMetadata) => void; 231 scenarioEnd: (scenario: IScenarioMetadata, error?: Error) => void; 232 testStart: (test: ITestMetadata) => void; 233 pass: (test: ITestMetadata) => void; 234 bug: (test: ITestMetadata) => void; 235 fail: (test: ITestMetadata) => void; 236 error: (test: ITestMetadata, error: Error) => void; 237 comment: (comment: string) => void; 238 verify: (test: ITestMetadata, passed: boolean, actual: any, expected: any, message: string) => void; 239 } 240 241 export class Logger implements ILogger { 242 public start(fileName?: string, priority?: number) { } 243 public end(fileName?: string) { } 244 public scenarioStart(scenario: IScenarioMetadata) { } 245 public scenarioEnd(scenario: IScenarioMetadata, error?: Error) { } 246 public testStart(test: ITestMetadata) { } 247 public pass(test: ITestMetadata) { } 248 public bug(test: ITestMetadata) { } 249 public fail(test: ITestMetadata) { } 250 public error(test: ITestMetadata, error: Error) { } 251 public comment(comment: string) { } 252 public verify(test: ITestMetadata, passed: boolean, actual: any, expected: any, message: string) { } 253 } 254 255 // Logger-related functions 256 var loggers: ILogger[] = []; 257 export function registerLogger(logger: ILogger) { 258 loggers.push(logger); 259 } 260 export function emitLog(field: string, ...params: any[]) { 261 for (var i = 0; i < loggers.length; i++) { 262 if (typeof loggers[i][field] === 'function') { 263 loggers[i][field].apply(loggers[i], params); 264 } 265 } 266 } 267 268 // BDD Framework 269 export interface IDone { 270 (e?: Error): void; 271 } 272 export class Runnable { 273 constructor(public description: string, public block: any) { } 274 275 // The current stack of Runnable objects 276 static currentStack: Runnable[] = []; 277 278 // The error, if any, that occurred when running 'block' 279 public error: Error = null; 280 281 // Whether or not this object has any failures (including in its descendants) 282 public passed = null; 283 284 // A list of bugs impacting this object 285 public bugs: string[] = []; 286 287 // A list of all our child Runnables 288 public children: Runnable[] = []; 289 290 public addChild(child: Runnable): void { 291 this.children.push(child); 292 } 293 294 /** Call function fn, which may take a done function and may possibly execute 295 * asynchronously, calling done when finished. Returns true or false depending 296 * on whether the function was asynchronous or not. 297 */ 298 public call(fn: (done?: IDone) => void , done: IDone) { 299 var isAsync = true; 300 301 try { 302 if (fn.length === 0) { 303 // No async. 304 fn(); 305 done(); 306 307 return false; 308 } else { 309 // Possibly async 310 311 Runnable.pushGlobalErrorHandler(done); 312 313 fn(function () { 314 isAsync = false; // If we execute synchronously, this will get called before the return below. 315 Runnable.popGlobalErrorHandler(); 316 done(); 317 }); 318 319 return isAsync; 320 } 321 322 } catch (e) { 323 done(e); 324 325 return false; 326 } 327 } 328 329 public run(done: IDone) { } 330 331 public runBlock(done: IDone) { 332 return this.call(this.block, done); 333 } 334 335 public runChild(index: number, done: IDone) { 336 return this.call(<any>((done) => this.children[index].run(done)), done); 337 } 338 339 static errorHandlerStack: { (e: Error): void; }[] = []; 340 341 static pushGlobalErrorHandler(done: IDone) { 342 errorHandlerStack.push(function (e) { 343 done(e); 344 }); 345 } 346 347 static popGlobalErrorHandler() { 348 errorHandlerStack.pop(); 349 } 350 351 static handleError(e: Error) { 352 if (errorHandlerStack.length === 0) { 353 IO.printLine('Global error: ' + e); 354 } else { 355 errorHandlerStack[errorHandlerStack.length - 1](e); 356 } 357 } 358 } 359 export class TestCase extends Runnable { 360 public description: string; 361 public block; 362 363 constructor(description: string, block: any) { 364 super(description, block); 365 this.description = description; 366 this.block = block; 367 } 368 369 public addChild(child: Runnable): void { 370 throw new Error("Testcases may not be nested inside other testcases"); 371 } 372 373 /** Run the test case block and fail the test if it raised an error. If no error is raised, the test passes. */ 374 public run(done: IDone) { 375 var that = this; 376 377 Runnable.currentStack.push(this); 378 379 emitLog('testStart', { desc: this.description }); 380 381 if (this.block) { 382 var async = this.runBlock(<any>function (e) { 383 if (e) { 384 that.passed = false; 385 that.error = e; 386 emitLog('error', { desc: this.description, pass: false }, e); 387 } else { 388 that.passed = true; 389 390 emitLog('pass', { desc: this.description, pass: true }); 391 } 392 393 Runnable.currentStack.pop(); 394 395 done() 396 }); 397 } 398 399 } 400 } 401 402 export class Scenario extends Runnable { 403 public description: string; 404 public block; 405 406 constructor(description: string, block: any) { 407 super(description, block); 408 this.description = description; 409 this.block = block; 410 } 411 412 /** Run the block, and if the block doesn't raise an error, run the children. */ 413 public run(done: IDone) { 414 var that = this; 415 416 Runnable.currentStack.push(this); 417 418 emitLog('scenarioStart', { desc: this.description }); 419 420 var async = this.runBlock(<any>function (e) { 421 Runnable.currentStack.pop(); 422 if (e) { 423 that.passed = false; 424 that.error = e; 425 var metadata: IScenarioMetadata = { id: undefined, desc: this.description, pass: false, bugs: assert.bugIds }; 426 // Report all bugs affecting this scenario 427 assert.bugIds.forEach(desc => emitLog('bug', metadata, desc)); 428 emitLog('scenarioEnd', metadata, e); 429 done(); 430 } else { 431 that.passed = true; // so far so good. 432 that.runChildren(done); 433 } 434 }); 435 } 436 437 /** Run the children of the scenario (other scenarios and test cases). If any fail, 438 * set this scenario to failed. Synchronous tests will run synchronously without 439 * adding stack frames. 440 */ 441 public runChildren(done: IDone, index = 0) { 442 var that = this; 443 var async = false; 444 445 for (; index < this.children.length; index++) { 446 async = this.runChild(index, <any>function (e) { 447 that.passed = that.passed && that.children[index].passed; 448 449 if (async) 450 that.runChildren(done, index + 1); 451 }); 452 453 if (async) 454 return; 455 } 456 457 var metadata: IScenarioMetadata = { id: undefined, desc: this.description, pass: this.passed, bugs: assert.bugIds }; 458 // Report all bugs affecting this scenario 459 assert.bugIds.forEach(desc => emitLog('bug', metadata, desc)); 460 emitLog('scenarioEnd', metadata); 461 462 done(); 463 } 464 } 465 export class Run extends Runnable { 466 constructor() { 467 super('Test Run', null); 468 } 469 470 public run() { 471 emitLog('start'); 472 this.runChildren(); 473 } 474 475 public runChildren(index = 0) { 476 var async = false; 477 var that = this; 478 479 for (; index < this.children.length; index++) { 480 // Clear out bug descriptions 481 assert.bugIds = []; 482 483 async = this.runChild(index, <any>function (e) { 484 if (async) { 485 that.runChildren(index + 1); 486 } 487 }); 488 489 if (async) { 490 return; 491 } 492 } 493 494 Perf.runBenchmarks(); 495 emitLog('end'); 496 } 497 } 498 499 // Performance test 500 export module Perf { 501 export module Clock { 502 export var now: () => number; 503 export var resolution: number; 504 505 declare module WScript { 506 export function InitializeProjection(); 507 } 508 509 declare module TestUtilities { 510 export function QueryPerformanceCounter(): number; 511 export function QueryPerformanceFrequency(): number; 512 } 513 514 if (typeof WScript !== "undefined" && typeof global['WScript'].InitializeProjection !== "undefined") { 515 // Running in JSHost. 516 global['WScript'].InitializeProjection(); 517 518 now = function () { 519 return TestUtilities.QueryPerformanceCounter(); 520 } 521 522 resolution = TestUtilities.QueryPerformanceFrequency(); 523 } else { 524 now = function () { 525 return Date.now(); 526 } 527 528 resolution = 1000; 529 } 530 } 531 532 export class Timer { 533 public startTime; 534 public time = 0; 535 536 public start() { 537 this.time = 0; 538 this.startTime = Clock.now(); 539 } 540 541 public end() { 542 // Set time to MS. 543 this.time = (Clock.now() - this.startTime) / Clock.resolution * 1000; 544 } 545 } 546 547 export class Dataset { 548 public data: number[] = []; 549 550 public add(value: number) { 551 this.data.push(value); 552 } 553 554 public mean() { 555 var sum = 0; 556 for (var i = 0; i < this.data.length; i++) { 557 sum += this.data[i]; 558 } 559 560 return sum / this.data.length; 561 } 562 563 public min() { 564 var min = this.data[0]; 565 566 for (var i = 1; i < this.data.length; i++) { 567 if (this.data[i] < min) { 568 min = this.data[i]; 569 } 570 } 571 572 return min; 573 } 574 575 public max() { 576 var max = this.data[0]; 577 578 for (var i = 1; i < this.data.length; i++) { 579 if (this.data[i] > max) { 580 max = this.data[i]; 581 } 582 } 583 584 return max; 585 } 586 587 public stdDev() { 588 var sampleMean = this.mean(); 589 var sumOfSquares = 0; 590 for (var i = 0; i < this.data.length; i++) { 591 sumOfSquares += Math.pow(this.data[i] - sampleMean, 2); 592 } 593 594 return Math.sqrt(sumOfSquares / this.data.length); 595 } 596 } 597 598 // Base benchmark class with some defaults. 599 export class Benchmark { 600 public iterations = 10; 601 public description = ""; 602 public bench(subBench?: () => void ) { } 603 public before() { } 604 public beforeEach() { } 605 public after() { } 606 public afterEach() { } 607 public results: { [x: string]: Dataset; } = <{ [x: string]: Dataset; }>{}; 608 609 public addTimingFor(name: string, timing: number) { 610 this.results[name] = this.results[name] || new Dataset(); 611 this.results[name].add(timing); 612 } 613 } 614 615 export var benchmarks: { new (): Benchmark; }[] = []; 616 617 var timeFunction: ( 618 benchmark: Benchmark, 619 description?: string, 620 name?: string, 621 f?: (bench?: { (): void; }) => void 622 ) => void; 623 624 timeFunction = function ( 625 benchmark: Benchmark, 626 description: string = benchmark.description, 627 name: string = '', 628 f = benchmark.bench 629 ): void { 630 631 var t = new Timer(); 632 t.start(); 633 634 var subBenchmark = function (name, f): void { 635 timeFunction(benchmark, description, name, f); 636 } 637 638 f.call(benchmark, subBenchmark); 639 640 t.end(); 641 642 benchmark.addTimingFor(name, t.time); 643 } 644 645 export function runBenchmarks() { 646 for (var i = 0; i < benchmarks.length; i++) { 647 var b = new benchmarks[i](); 648 649 650 var t = new Timer(); 651 b.before(); 652 for (var j = 0; j < b.iterations; j++) { 653 b.beforeEach(); 654 timeFunction(b); 655 b.afterEach(); 656 } 657 b.after(); 658 659 for (var prop in b.results) { 660 var description = b.description + (prop ? ": " + prop : ''); 661 662 emitLog('testStart', { desc: description }); 663 664 emitLog('pass', { 665 desc: description, pass: true, perfResults: { 666 mean: b.results[prop].mean(), 667 min: b.results[prop].min(), 668 max: b.results[prop].max(), 669 stdDev: b.results[prop].stdDev(), 670 trials: b.results[prop].data 671 } 672 }); 673 } 674 675 } 676 } 677 678 // Replace with better type when classes are assignment compatible with 679 // the below type. 680 // export function addBenchmark(BenchmarkClass: {new(): Benchmark;}) { 681 export function addBenchmark(BenchmarkClass: any) { 682 benchmarks.push(BenchmarkClass); 683 } 684 685 } 686 687 /** Functionality for compiling TypeScript code */ 688 export module Compiler { 689 /** Aggregate various writes into a single array of lines. Useful for passing to the 690 * TypeScript compiler to fill with source code or errors. 691 */ 692 export class WriterAggregator implements ITextWriter { 693 public lines: string[] = []; 694 public currentLine = ""; 695 696 public Write(str) { 697 this.currentLine += str; 698 } 699 700 public WriteLine(str) { 701 this.lines.push(this.currentLine + str); 702 this.currentLine = ""; 703 } 704 705 public Close() { 706 if (this.currentLine.length > 0) { this.lines.push(this.currentLine); } 707 this.currentLine = ""; 708 } 709 710 public reset() { 711 this.lines = []; 712 this.currentLine = ""; 713 } 714 } 715 716 /** Mimics having multiple files, later concatenated to a single file. */ 717 export class EmitterIOHost implements TypeScript.EmitterIOHost { 718 719 private fileCollection = {}; 720 721 /** create file gets the whole path to create, so this works as expected with the --out parameter */ 722 public createFile(s: string, useUTF8?: boolean): ITextWriter { 723 724 if (this.fileCollection[s]) { 725 return <ITextWriter>this.fileCollection[s]; 726 } 727 728 var writer = new Harness.Compiler.WriterAggregator(); 729 this.fileCollection[s] = writer; 730 return writer; 731 } 732 733 public directoryExists(s: string) { return false; } 734 public fileExists(s: string) { return typeof this.fileCollection[s] !== 'undefined'; } 735 public resolvePath(s: string) { return s; } 736 737 public reset() { this.fileCollection = {}; } 738 739 public toArray(): { filename: string; file: WriterAggregator; }[] { 740 var result: { filename: string; file: WriterAggregator; }[] = []; 741 742 for (var p in this.fileCollection) { 743 if (this.fileCollection.hasOwnProperty(p)) { 744 var current = <Harness.Compiler.WriterAggregator>this.fileCollection[p]; 745 if (current.lines.length > 0) { 746 if (p !== '0.js') { current.lines.unshift('////[' + p + ']'); } 747 result.push({ filename: p, file: this.fileCollection[p] }); 748 } 749 } 750 } 751 return result; 752 } 753 } 754 755 var libFolder: string = global['WScript'] ? TypeScript.filePath(global['WScript'].ScriptFullName) : (__dirname + '/'); 756 export var libText = IO ? IO.readFile(libFolder + "lib.d.ts") : ''; 757 758 var stdout = new EmitterIOHost(); 759 var stderr = new WriterAggregator(); 760 761 export function isDeclareFile(filename: string) { 762 return /\.d\.ts$/.test(filename); 763 } 764 765 export function makeDefaultCompilerForTest(c?: TypeScript.TypeScriptCompiler) { 766 var compiler = c || new TypeScript.TypeScriptCompiler(stderr); 767 compiler.parser.errorRecovery = true; 768 compiler.settings.codeGenTarget = TypeScript.CodeGenTarget.ES5; 769 compiler.settings.controlFlow = true; 770 compiler.settings.controlFlowUseDef = true; 771 if (Harness.usePull) { 772 compiler.settings.usePull = true; 773 compiler.settings.useFidelity = true; 774 } 775 776 compiler.parseEmitOption(stdout); 777 TypeScript.moduleGenTarget = TypeScript.ModuleGenTarget.Synchronous; 778 compiler.addUnit(Harness.Compiler.libText, "lib.d.ts", true); 779 return compiler; 780 } 781 782 var compiler: TypeScript.TypeScriptCompiler; 783 recreate(); 784 785 // pullUpdateUnit is sufficient if an existing unit is updated, if a new unit is added we need to do a full typecheck 786 var needsFullTypeCheck = true; 787 export function compile(code?: string, filename?: string) { 788 if (usePull) { 789 if (needsFullTypeCheck) { 790 compiler.pullTypeCheck(true); 791 needsFullTypeCheck = false; 792 } 793 else { 794 // requires unit to already exist in the compiler 795 compiler.pullUpdateUnit(new TypeScript.StringSourceText(""), filename, true); 796 compiler.pullUpdateUnit(new TypeScript.StringSourceText(code), filename, true); 797 } 798 } 799 else { 800 compiler.reTypeCheck(); 801 } 802 } 803 804 // Types 805 export class Type { 806 constructor(public type, public code, public identifier) { } 807 808 public normalizeToArray(arg: any) { 809 if ((Array.isArray && Array.isArray(arg)) || arg instanceof Array) 810 return arg; 811 812 return [arg]; 813 } 814 815 public compilesOk(testCode): boolean { 816 var errors = null; 817 compileString(testCode, 'test.ts', function (compilerResult) { 818 errors = compilerResult.errors; 819 }) 820 821 return errors.length === 0; 822 } 823 824 public isSubtypeOf(other: Type) { 825 var testCode = 'class __test1__ {\n'; 826 testCode += ' public test() {\n'; 827 testCode += ' ' + other.code + ';\n'; 828 testCode += ' return ' + other.identifier + ';\n'; 829 testCode += ' }\n'; 830 testCode += '}\n'; 831 testCode += 'class __test2__ extends __test1__ {\n'; 832 testCode += ' public test() {\n'; 833 testCode += ' ' + this.code + ';\n'; 834 testCode += ' return ' + other.identifier + ';\n'; 835 testCode += ' }\n'; 836 testCode += '}\n'; 837 838 return this.compilesOk(testCode); 839 } 840 841 // TODO: Find an implementation of isIdenticalTo that works. 842 //public isIdenticalTo(other: Type) { 843 // var testCode = 'module __test1__ {\n'; 844 // testCode += ' ' + this.code + ';\n'; 845 // testCode += ' export var __val__ = ' + this.identifier + ';\n'; 846 // testCode += '}\n'; 847 // testCode += 'var __test1__val__ = __test1__.__val__;\n'; 848 849 // testCode += 'module __test2__ {\n'; 850 // testCode += ' ' + other.code + ';\n'; 851 // testCode += ' export var __val__ = ' + other.identifier + ';\n'; 852 // testCode += '}\n'; 853 // testCode += 'var __test2__val__ = __test2__.__val__;\n'; 854 855 // testCode += 'function __test__function__() { if(true) { return __test1__val__ }; return __test2__val__; }'; 856 857 // return this.compilesOk(testCode); 858 //} 859 860 public assertSubtypeOf(others: any) { 861 others = this.normalizeToArray(others); 862 863 for (var i = 0; i < others.length; i++) { 864 if (!this.isSubtypeOf(others[i])) { 865 throw new Error("Expected " + this.type + " to be a subtype of " + others[i].type); 866 } 867 } 868 } 869 870 public assertNotSubtypeOf(others: any) { 871 others = this.normalizeToArray(others); 872 873 for (var i = 0; i < others.length; i++) { 874 if (this.isSubtypeOf(others[i])) { 875 throw new Error("Expected " + this.type + " to be a subtype of " + others[i].type); 876 } 877 } 878 } 879 880 //public assertIdenticalTo(other: Type) { 881 // if (!this.isIdenticalTo(other)) { 882 // throw new Error("Expected " + this.type + " to be identical to " + other.type); 883 // } 884 //} 885 886 //public assertNotIdenticalTo(other: Type) { 887 // if (!this.isIdenticalTo(other)) { 888 // throw new Error("Expected " + this.type + " to not be identical to " + other.type); 889 // } 890 //} 891 892 public isAssignmentCompatibleWith(other: Type) { 893 var testCode = 'module __test1__ {\n'; 894 testCode += ' ' + this.code + ';\n'; 895 testCode += ' export var __val__ = ' + this.identifier + ';\n'; 896 testCode += '}\n'; 897 testCode += 'var __test1__val__ = __test1__.__val__;\n'; 898 899 testCode += 'module __test2__ {\n'; 900 testCode += ' export ' + other.code + ';\n'; 901 testCode += ' export var __val__ = ' + other.identifier + ';\n'; 902 testCode += '}\n'; 903 testCode += 'var __test2__val__ = __test2__.__val__;\n'; 904 905 testCode += '__test2__val__ = __test1__val__;'; 906 907 return this.compilesOk(testCode); 908 } 909 910 public assertAssignmentCompatibleWith(others: any) { 911 others = this.normalizeToArray(others); 912 913 for (var i = 0; i < others.length; i++) { 914 var other = others[i]; 915 916 if (!this.isAssignmentCompatibleWith(other)) { 917 throw new Error("Expected " + this.type + " to be assignment compatible with " + other.type); 918 } 919 } 920 } 921 922 public assertNotAssignmentCompatibleWith(others: any) { 923 others = this.normalizeToArray(others); 924 925 for (var i = 0; i < others.length; i++) { 926 var other = others[i]; 927 928 if (this.isAssignmentCompatibleWith(other)) { 929 throw new Error("Expected " + this.type + " to not be assignment compatible with " + other.type); 930 } 931 } 932 } 933 934 public assertThisCanBeAssignedTo(desc: string, these: any[], notThese: any[]) { 935 it(desc + " is assignable to ", () => { 936 this.assertAssignmentCompatibleWith(these); 937 }); 938 939 it(desc + " not assignable to ", () => { 940 this.assertNotAssignmentCompatibleWith(notThese); 941 }); 942 } 943 944 } 945 946 export class TypeFactory { 947 public any: Type; 948 public number: Type; 949 public string: Type; 950 public boolean: Type; 951 952 constructor() { 953 this.any = this.get('var x : any', 'x'); 954 this.number = this.get('var x : number', 'x'); 955 this.string = this.get('var x : string', 'x'); 956 this.boolean = this.get('var x : boolean', 'x'); 957 } 958 959 public get (code: string, target: any) { 960 var targetIdentifier = ''; 961 var targetPosition = -1; 962 if (typeof target === "string") { 963 targetIdentifier = target; 964 } 965 else if (typeof target === "number") { 966 targetPosition = target; 967 } 968 else { 969 throw new Error("Expected string or number not " + (typeof target)); 970 } 971 972 var errors = null; 973 compileString(code, 'test.ts', function (compilerResult) { 974 errors = compilerResult.errors; 975 }) 976 977 if (errors.length > 0) 978 throw new Error("Type definition contains errors: " + errors.join(",")); 979 980 var matchingIdentifiers: Type[] = []; 981 982 if (!usePull) { 983 // This will find the requested identifier in the first script where it's present, a naive search of each member in each script, 984 // which means this won't play nicely if the same identifier is used in multiple units, but it will enable this to work on multi-file tests. 985 // m = 1 because the first script will always be lib.d.ts which we don't want to search. 986 for (var m = 1; m < compiler.scripts.members.length; m++) { 987 var script = compiler.scripts.members[m]; 988 var enclosingScopeContext = TypeScript.findEnclosingScopeAt(new TypeScript.NullLogger(), <TypeScript.Script>script, new TypeScript.StringSourceText(code), 0, false); 989 var entries = new TypeScript.ScopeTraversal(compiler).getScopeEntries(enclosingScopeContext); 990 991 for (var i = 0; i < entries.length; i++) { 992 if (entries[i].name === targetIdentifier) { 993 matchingIdentifiers.push(new Type(entries[i].type, code, targetIdentifier)); 994 } 995 } 996 } 997 } 998 else { 999 for (var m = 0; m < compiler.scripts.members.length; m++) { 1000 var script2 = <TypeScript.Script>compiler.scripts.members[m]; 1001 if (script2.locationInfo.filename !== 'lib.d.ts') { 1002 if (targetPosition > -1) { 1003 var tyInfo = compiler.pullGetTypeInfoAtPosition(targetPosition, script2); 1004 var name = this.getTypeInfoName(tyInfo.ast); 1005 var foundValue = new Type(tyInfo.typeInfo, code, name); 1006 if (!matchingIdentifiers.some(value => (value.identifier === foundValue.identifier) && (value.code === foundValue.code) && (value.type === foundValue.type))) { 1007 matchingIdentifiers.push(foundValue); 1008 } 1009 } 1010 else { 1011 for (var pos = 0; pos < code.length; pos++) { 1012 var tyInfo = compiler.pullGetTypeInfoAtPosition(pos, script2); 1013 var name = this.getTypeInfoName(tyInfo.ast); 1014 if (name === targetIdentifier) { 1015 var foundValue = new Type(tyInfo.typeInfo, code, targetIdentifier); 1016 if (!matchingIdentifiers.some(value => (value.identifier === foundValue.identifier) && (value.code === foundValue.code) && (value.type === foundValue.type))) { 1017 matchingIdentifiers.push(foundValue); 1018 } 1019 } 1020 } 1021 } 1022 } 1023 } 1024 } 1025 1026 if (matchingIdentifiers.length === 0) { 1027 if (targetPosition > -1) { 1028 throw new Error("Could not find an identifier at position " + targetPosition); 1029 } 1030 else { 1031 throw new Error("Could not find an identifier " + targetIdentifier + " in any known scopes"); 1032 } 1033 } 1034 else if (matchingIdentifiers.length > 1) { 1035 throw new Error("Found multiple matching identifiers for " + target); 1036 } 1037 else { 1038 return matchingIdentifiers[0]; 1039 } 1040 } 1041 1042 private getTypeInfoName(ast : TypeScript.AST) { 1043 var name = ''; 1044 switch (ast.nodeType) { 1045 case TypeScript.NodeType.Name: // Type Name? 1046 case TypeScript.NodeType.Null: 1047 case TypeScript.NodeType.List: 1048 case TypeScript.NodeType.Empty: 1049 case TypeScript.NodeType.EmptyExpr: 1050 case TypeScript.NodeType.Asg: 1051 case TypeScript.NodeType.True: 1052 case TypeScript.NodeType.False: 1053 case TypeScript.NodeType.ArrayLit: 1054 case TypeScript.NodeType.TypeRef: 1055 break; 1056 case TypeScript.NodeType.Super: 1057 name = (<any>ast).text; 1058 break; 1059 case TypeScript.NodeType.Regex: 1060 name = (<TypeScript.RegexLiteral>ast).text; 1061 break; 1062 case TypeScript.NodeType.QString: 1063 name = (<any>ast).text; 1064 break; 1065 case TypeScript.NodeType.NumberLit: 1066 name = (<TypeScript.NumberLiteral>ast).text; 1067 break; 1068 case TypeScript.NodeType.Return: 1069 //name = (<TypeScript.ReturnStatement>tyInfo.ast).returnExpression.actualText; // why is this complaining? 1070 break; 1071 case TypeScript.NodeType.InterfaceDeclaration: 1072 name = (<TypeScript.InterfaceDeclaration>ast).name.actualText; 1073 break; 1074 case TypeScript.NodeType.ModuleDeclaration: 1075 name = (<TypeScript.ModuleDeclaration>ast).name.actualText; 1076 break; 1077 case TypeScript.NodeType.ClassDeclaration: 1078 name = (<TypeScript.ClassDeclaration>ast).name.actualText; 1079 break; 1080 case TypeScript.NodeType.FuncDecl: 1081 name = !(<TypeScript.FuncDecl>ast).name ? "" : (<TypeScript.FuncDecl>ast).name.actualText; // name == null for lambdas 1082 break; 1083 default: 1084 // TODO: is there a reason to mess with all the special cases above and not just do this (ie take whatever property is there and works?) 1085 var a = <any>ast; 1086 name = (a.id) ? (a.id.actualText) : (a.name) ? a.name.actualText : (a.text) ? a.text : ''; 1087 break; 1088 } 1089 1090 return name; 1091 } 1092 1093 public isOfType(expr: string, expectedType: string) { 1094 var actualType = this.get('var _v_a_r_ = ' + expr, '_v_a_r_'); 1095 1096 it('Expression "' + expr + '" is of type "' + expectedType + '"', function () { 1097 assert.equal(actualType.type, expectedType); 1098 }); 1099 } 1100 } 1101 1102 /** Generates a .d.ts file for the given code 1103 * @param verifyNoDeclFile pass true when the given code should generate no decl file, false otherwise 1104 * @param unitName add the given code under thie name, else use '0.ts' 1105 * @param compilationContext a set of functions to be run before and after compiling this code for doing things like adding dependencies first 1106 * @param references the set of referenced files used by the given code 1107 */ 1108 export function generateDeclFile(code: string, verifyNoDeclFile: boolean, unitName?: string, compilationContext?: Harness.Compiler.CompilationContext, references?: TypeScript.IFileReference[]): string { 1109 reset(); 1110 1111 compiler.settings.generateDeclarationFiles = true; 1112 var oldOutputOption = compiler.settings.outputOption; 1113 var oldEmitterIOHost = compiler.emitSettings.ioHost; 1114 try { 1115 if (compilationContext && compilationContext.preCompile) { 1116 compilationContext.preCompile(); 1117 } 1118 1119 addUnit(code, unitName, false, false, references); 1120 compiler.reTypeCheck(); 1121 1122 var outputs = {}; 1123 1124 compiler.settings.outputOption = ""; 1125 compiler.parseEmitOption( 1126 { 1127 createFile: (fn: string) => { 1128 outputs[fn] = new Harness.Compiler.WriterAggregator(); 1129 return outputs[fn]; 1130 }, 1131 directoryExists: (path: string) => true, 1132 fileExists: (path: string) => true, 1133 resolvePath: (path: string) => path 1134 }); 1135 compiler.emitDeclarations(); 1136 1137 var results: string = null; 1138 for (var fn in outputs) { 1139 if (fn.indexOf('.d.ts') >= 0) { 1140 var writer = <Harness.Compiler.WriterAggregator>outputs[fn]; 1141 writer.Close(); 1142 results = writer.lines.join('\n'); 1143 if (verifyNoDeclFile && results != "") { 1144 throw new Error('Compilation should not produce ' + fn); 1145 } 1146 } 1147 } 1148 1149 if (results) { 1150 return results; 1151 } 1152 1153 if (!verifyNoDeclFile) { 1154 throw new Error('Compilation did not produce .d.ts files'); 1155 } 1156 } finally { 1157 compiler.settings.generateDeclarationFiles = false; 1158 compiler.settings.outputOption = oldOutputOption; 1159 compiler.parseEmitOption(oldEmitterIOHost); 1160 if (compilationContext && compilationContext.postCompile) { 1161 compilationContext.postCompile(); 1162 } 1163 1164 var uName = unitName || '0.ts'; 1165 updateUnit('', uName); 1166 } 1167 1168 return ''; 1169 } 1170 1171 /** Contains the code and errors of a compilation and some helper methods to check its status. */ 1172 export class CompilerResult { 1173 public code: string; 1174 public errors: CompilerError[]; 1175 1176 /** @param fileResults an array of strings for the filename and an ITextWriter with its code */ 1177 constructor(public fileResults: { filename: string; file: WriterAggregator; }[], errorLines: string[], public scripts: TypeScript.Script[]) { 1178 var lines = []; 1179 fileResults.forEach(v => lines = lines.concat(v.file.lines)); 1180 this.code = lines.join("\n") 1181 1182 this.errors = []; 1183 1184 for (var i = 0; i < errorLines.length; i++) { 1185 if (Harness.usePull) { 1186 var err = <any>errorLines[i]; // TypeScript.PullError 1187 this.errors.push(new CompilerError(err.filename, 0, 0, err.message)); 1188 } else { 1189 var match = errorLines[i].match(/([^\(]*)\((\d+),(\d+)\):\s+((.*[\s\r\n]*.*)+)\s*$/); 1190 if (match) { 1191 this.errors.push(new CompilerError(match[1], parseFloat(match[2]), parseFloat(match[3]), match[4])); 1192 } 1193 else { 1194 WScript.Echo("non-match on: " + errorLines[i]); 1195 } 1196 } 1197 } 1198 } 1199 1200 public isErrorAt(line: number, column: number, message: string) { 1201 for (var i = 0; i < this.errors.length; i++) { 1202 if (this.errors[i].line === line && this.errors[i].column === column && this.errors[i].message === message) 1203 return true; 1204 } 1205 1206 return false; 1207 } 1208 } 1209 1210 // Compiler Error. 1211 export class CompilerError { 1212 constructor(public file: string, 1213 public line: number, 1214 public column: number, 1215 public message: string) { } 1216 1217 public toString() { 1218 return this.file + "(" + this.line + "," + this.column + "): " + this.message; 1219 } 1220 } 1221 1222 /** Create a new instance of the compiler with default settings and lib.d.ts, then typecheck */ 1223 export function recreate() { 1224 compiler = makeDefaultCompilerForTest(); 1225 if (usePull) { 1226 compiler.pullTypeCheck(true); 1227 } 1228 else { 1229 compiler.typeCheck(); 1230 } 1231 } 1232 1233 export function reset() { 1234 stdout.reset(); 1235 stderr.reset(); 1236 1237 var files = compiler.units.map((value) => value.filename); 1238 1239 for (var i = 0; i < files.length; i++) { 1240 var fname = files[i]; 1241 if(fname !== 'lib.d.ts') { 1242 updateUnit('', fname); 1243 } 1244 } 1245 1246 compiler.errorReporter.hasErrors = false; 1247 } 1248 1249 // Defines functions to invoke before compiling a piece of code and a post compile action intended to clean up the 1250 // effects of preCompile, preferably with something lighter weight than a full recreate() 1251 export interface CompilationContext { 1252 filename: string; 1253 preCompile: () => void; 1254 postCompile: () => void; 1255 } 1256 1257 export function addUnit(code: string, unitName?: string, isResident?: boolean, isDeclareFile?: boolean, references?: TypeScript.IFileReference[]) { 1258 var script: TypeScript.Script = null; 1259 var uName = unitName || '0' + (isDeclareFile ? '.d.ts' : '.ts'); 1260 1261 for (var i = 0; i < compiler.units.length; i++) { 1262 if (compiler.units[i].filename === uName) { 1263 updateUnit(code, uName); 1264 script = <TypeScript.Script>compiler.scripts.members[i]; 1265 } 1266 } 1267 if (!script) { 1268 // TODO: make this toggleable, shouldn't be necessary once typecheck bugs are cleaned up 1269 // but without it subsequent tests are treated as edits, making for somewhat useful stress testing 1270 // of persistent typecheck state 1271 //compiler.addUnit("", uName, isResident, references); // equivalent to compiler.deleteUnit(...) 1272 script = compiler.addUnit(code, uName, isResident, references); 1273 needsFullTypeCheck = true; 1274 } 1275 1276 return script; 1277 } 1278 1279 export function updateUnit(code: string, unitName: string, setRecovery?: boolean) { 1280 if (Harness.usePull) { 1281 compiler.pullUpdateUnit(new TypeScript.StringSourceText(code), unitName, setRecovery); 1282 } else { 1283 compiler.updateUnit(code, unitName, setRecovery); 1284 } 1285 } 1286 1287 export function compileFile(path: string, callback: (res: CompilerResult) => void , settingsCallback?: (settings?: TypeScript.CompilationSettings) => void , context?: CompilationContext, references?: TypeScript.IFileReference[]) { 1288 path = switchToForwardSlashes(path); 1289 var filename = path.match(/[^\/]*$/)[0]; 1290 var code = readFile(path); 1291 1292 compileUnit(code, filename, callback, settingsCallback, context, references); 1293 } 1294 1295 export function compileUnit(code: string, filename: string, callback: (res: CompilerResult) => void , settingsCallback?: (settings?: TypeScript.CompilationSettings) => void , context?: CompilationContext, references?: TypeScript.IFileReference[]) { 1296 // not recursive 1297 function clone/* <T> */(source: any, target: any) { 1298 for (var prop in source) { 1299 target[prop] = source[prop]; 1300 } 1301 } 1302 1303 var oldCompilerSettings = new TypeScript.CompilationSettings(); 1304 clone(compiler.settings, oldCompilerSettings); 1305 var oldEmitSettings = new TypeScript.EmitOptions(compiler.settings); 1306 clone(compiler.emitSettings, oldEmitSettings); 1307 1308 var oldModuleGenTarget = TypeScript.moduleGenTarget; 1309 1310 if (settingsCallback) { 1311 settingsCallback(compiler.settings); 1312 compiler.emitSettings = new TypeScript.EmitOptions(compiler.settings); 1313 } 1314 try { 1315 compileString(code, filename, callback, context, references); 1316 } finally { 1317 // If settingsCallback exists, assume that it modified the global compiler instance's settings in some way. 1318 // So that a test doesn't have side effects for tests run after it, restore the compiler settings to their previous state. 1319 if (settingsCallback) { 1320 compiler.settings = oldCompilerSettings; 1321 compiler.emitSettings = oldEmitSettings; 1322 TypeScript.moduleGenTarget = oldModuleGenTarget; 1323 } 1324 } 1325 } 1326 1327 export function compileUnits(units: TestCaseParser.TestUnitData[], callback: (res: Compiler.CompilerResult) => void , settingsCallback?: () => void ) { 1328 var lastUnit = units[units.length - 1]; 1329 var unitName = switchToForwardSlashes(lastUnit.name).match(/[^\/]*$/)[0]; 1330 1331 var dependencies = units.slice(0, units.length - 1); 1332 var compilationContext = Harness.Compiler.defineCompilationContextForTest(unitName, dependencies); 1333 1334 compileUnit(lastUnit.content, unitName, callback, settingsCallback, compilationContext, lastUnit.references); 1335 } 1336 1337 export function emitToOutfile(outfile: WriterAggregator) { 1338 compiler.emitToOutfile(outfile); 1339 } 1340 1341 export function emit(ioHost: TypeScript.EmitterIOHost, usePullEmitter?: boolean) { 1342 compiler.emit(ioHost, usePullEmitter); 1343 } 1344 1345 export function compileString(code: string, unitName: string, callback: (res: Compiler.CompilerResult) => void , context?: CompilationContext, references?: TypeScript.IFileReference[]) { 1346 var scripts: TypeScript.Script[] = []; 1347 1348 reset(); 1349 1350 if (context) { 1351 context.preCompile(); 1352 } 1353 1354 var isDeclareFile = Harness.Compiler.isDeclareFile(unitName); 1355 // for single file tests just add them as using the old '0.ts' naming scheme 1356 var uName = context ? unitName : ((isDeclareFile) ? '0.d.ts' : '0.ts'); 1357 scripts.push(addUnit(code, uName, false, isDeclareFile, references)); 1358 compile(code, uName); 1359 1360 var errors; 1361 if (usePull) { 1362 // TODO: no emit support with pull yet 1363 errors = compiler.pullGetErrorsForFile(uName); 1364 emit(stdout, true); 1365 } 1366 else { 1367 errors = stderr.lines; 1368 emit(stdout, false); 1369 //output decl file 1370 compiler.emitDeclarations(); 1371 } 1372 1373 if (context) { 1374 context.postCompile(); 1375 } 1376 1377 callback(new CompilerResult(stdout.toArray(), errors, scripts)); 1378 } 1379 1380 /** Returns a set of functions which can be later executed to add and remove given dependencies to the compiler so that 1381 * a file can be successfully compiled. These functions will add/remove named units and code to the compiler for each dependency. 1382 */ 1383 export function defineCompilationContextForTest(filename: string, dependencies: TestCaseParser.TestUnitData[]): CompilationContext { 1384 // if the given file has no dependencies, there is no context to return, it can be compiled without additional work 1385 if (dependencies.length == 0) { 1386 return null; 1387 } else { 1388 var addedFiles = []; 1389 var precompile = () => { 1390 // REVIEW: if any dependency has a triple slash reference then does postCompile potentially have to do a recreate since we can't update references with updateUnit? 1391 // easy enough to do if so, prefer to avoid the recreate cost until it proves to be an issue 1392 dependencies.forEach(dep => { 1393 addUnit(dep.content, dep.name, false, Harness.Compiler.isDeclareFile(dep.name)); 1394 addedFiles.push(dep.name); 1395 }); 1396 }; 1397 var postcompile = () => { 1398 addedFiles.forEach(file => { 1399 updateUnit('', file); 1400 }); 1401 }; 1402 var context = { 1403 filename: filename, 1404 preCompile: precompile, 1405 postCompile: postcompile 1406 }; 1407 return context; 1408 } 1409 } 1410 } 1411 1412 /** Parses the test cases files 1413 * extracts options and individual files in a multifile test 1414 */ 1415 export module TestCaseParser { 1416 /** all the necesarry information to set the right compiler settings */ 1417 export interface CompilerSetting { 1418 flag: string; 1419 value: string; 1420 } 1421 1422 /** All the necessary information to turn a multi file test into useful units for later compilation */ 1423 export interface TestUnitData { 1424 content: string; 1425 name: string; 1426 originalFilePath: string; 1427 references: TypeScript.IFileReference[]; 1428 } 1429 1430 // Regex for parsing options in the format "@Alpha: Value of any sort" 1431 private optionRegex = /^[\/]{2}\s*@(\w+):\s*(\S*)/gm; // multiple matches on multiple lines 1432 1433 // List of allowed metadata names 1434 var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out"]; 1435 1436 function extractCompilerSettings(content: string): CompilerSetting[] { 1437 1438 var opts = []; 1439 1440 var match; 1441 while ((match = optionRegex.exec(content)) != null) { 1442 opts.push({ flag: match[1], value: match[2] }); 1443 } 1444 1445 return opts; 1446 } 1447 1448 /** Given a test file containing // @Filename directives, return an array of named units of code to be added to an existing compiler instance */ 1449 export function makeUnitsFromTest(code: string, filename: string): { settings: CompilerSetting[]; testUnitData: TestUnitData[]; } { 1450 1451 var settings = extractCompilerSettings(code); 1452 1453 // List of all the subfiles we've parsed out 1454 var files: TestUnitData[] = []; 1455 1456 var lines = splitContentByNewlines(code); 1457 1458 // Stuff related to the subfile we're parsing 1459 var currentFileContent: string = null; 1460 var currentFileOptions = {}; 1461 var currentFileName = null; 1462 var refs: TypeScript.IFileReference[] = []; 1463 1464 for (var i = 0; i < lines.length; i++) { 1465 var line = lines[i]; 1466 var isTripleSlashReference = /[\/]{3}\s*<reference path/.test(line); 1467 var testMetaData = optionRegex.exec(line); 1468 // Triple slash references need to be tracked as they are added to the compiler as an additional parameter to addUnit 1469 if (isTripleSlashReference) { 1470 var isRef = line.match(/reference\spath='(\w*_?\w*\.?d?\.ts)'/); 1471 if (isRef) { 1472 var ref = { 1473 minChar: 0, 1474 limChar: 0, 1475 startLine:0, 1476 startCol:0, 1477 path: isRef[1], 1478 isResident: false 1479 }; 1480 1481 refs.push(ref); 1482 } 1483 } else if (testMetaData) { 1484 // Comment line, check for global/file @options and record them 1485 optionRegex.lastIndex = 0; 1486 var fileNameIndex = fileMetadataNames.indexOf(testMetaData[1].toLowerCase()); 1487 if (fileNameIndex == -1) { 1488 throw new Error('Unrecognized metadata name "' + testMetaData[1] + '". Available file metadata names are: ' + fileMetadataNames.join(', ')); 1489 } else if (fileNameIndex == 0) { 1490 currentFileOptions[testMetaData[1]] = testMetaData[2]; 1491 } else { 1492 continue; 1493 } 1494 1495 // New metadata statement after having collected some code to go with the previous metadata 1496 if (currentFileName) { 1497 // Store result file 1498 var newTestFile = 1499 { 1500 content: currentFileContent, 1501 name: currentFileName, 1502 fileOptions: currentFileOptions, 1503 originalFilePath: filename, 1504 references: refs 1505 }; 1506 files.push(newTestFile); 1507 1508 // Reset local data 1509 currentFileContent = null; 1510 currentFileOptions = {}; 1511 currentFileName = testMetaData[2]; 1512 refs = []; 1513 } else { 1514 // First metadata marker in the file 1515 currentFileName = testMetaData[2]; 1516 } 1517 } else { 1518 // Subfile content line 1519 // Append to the current subfile content, inserting a newline needed 1520 if (currentFileContent === null) { 1521 currentFileContent = ''; 1522 } else { 1523 // End-of-line 1524 currentFileContent = currentFileContent + '\n'; 1525 } 1526 currentFileContent = currentFileContent + line; 1527 } 1528 } 1529 1530 // normalize the filename for the single file case 1531 currentFileName = files.length > 0 ? currentFileName : '0.ts'; 1532 1533 // EOF, push whatever remains 1534 var newTestFile = { 1535 content: currentFileContent || '', 1536 name: currentFileName, 1537 fileOptions: currentFileOptions, 1538 originalFilePath: filename, 1539 references: refs 1540 }; 1541 files.push(newTestFile); 1542 1543 return { settings: settings, testUnitData: files }; 1544 } 1545 } 1546 1547 export class ScriptInfo { 1548 public version: number; 1549 public editRanges: { length: number; editRange: TypeScript.ScriptEditRange; }[] = []; 1550 1551 constructor(public name: string, public content: string, public isResident: boolean, public maxScriptVersions: number) { 1552 this.version = 1; 1553 } 1554 1555 public updateContent(content: string, isResident: boolean) { 1556 this.editRanges = []; 1557 this.content = content; 1558 this.isResident = isResident; 1559 this.version++; 1560 } 1561 1562 public editContent(minChar: number, limChar: number, newText: string) { 1563 // Apply edits 1564 var prefix = this.content.substring(0, minChar); 1565 var middle = newText; 1566 var suffix = this.content.substring(limChar); 1567 this.content = prefix + middle + suffix; 1568 1569 // Store edit range + new length of script 1570 this.editRanges.push({ 1571 length: this.content.length, 1572 editRange: new TypeScript.ScriptEditRange(minChar, limChar, (limChar - minChar) + newText.length) 1573 }); 1574 1575 if (this.editRanges.length > this.maxScriptVersions) { 1576 this.editRanges.splice(0, this.maxScriptVersions - this.editRanges.length); 1577 } 1578 1579 // Update version # 1580 this.version++; 1581 } 1582 1583 public getEditRangeSinceVersion(version: number): TypeScript.ScriptEditRange { 1584 if (this.version == version) { 1585 // No edits! 1586 return null; 1587 } 1588 1589 var initialEditRangeIndex = this.editRanges.length - (this.version - version); 1590 if (initialEditRangeIndex < 0 || initialEditRangeIndex >= this.editRanges.length) { 1591 // Too far away from what we know 1592 return TypeScript.ScriptEditRange.unknown(); 1593 } 1594 1595 var entries = this.editRanges.slice(initialEditRangeIndex); 1596 1597 var minDistFromStart = entries.map(x => x.editRange.minChar).reduce((prev, current) => Math.min(prev, current)); 1598 var minDistFromEnd = entries.map(x => x.length - x.editRange.limChar).reduce((prev, current) => Math.min(prev, current)); 1599 var aggDelta = entries.map(x => x.editRange.delta).reduce((prev, current) => prev + current); 1600 1601 return new TypeScript.ScriptEditRange(minDistFromStart, entries[0].length - minDistFromEnd, aggDelta); 1602 } 1603 } 1604 1605 export class TypeScriptLS implements Services.ILanguageServiceShimHost { 1606 private ls: Services.ILanguageServiceShim = null; 1607 1608 public scripts: ScriptInfo[] = []; 1609 public maxScriptVersions = 100; 1610 1611 public addDefaultLibrary() { 1612 this.addScript("lib.d.ts", Harness.Compiler.libText, true); 1613 } 1614 1615 public addFile(name: string, isResident = false) { 1616 var code: string = readFile(name); 1617 this.addScript(name, code, isResident); 1618 } 1619 1620 public addScript(name: string, content: string, isResident = false) { 1621 var script = new ScriptInfo(name, content, isResident, this.maxScriptVersions); 1622 this.scripts.push(script); 1623 } 1624 1625 public updateScript(name: string, content: string, isResident = false) { 1626 for (var i = 0; i < this.scripts.length; i++) { 1627 if (this.scripts[i].name == name) { 1628 this.scripts[i].updateContent(content, isResident); 1629 return; 1630 } 1631 } 1632 1633 this.addScript(name, content, isResident); 1634 } 1635 1636 public editScript(name: string, minChar: number, limChar: number, newText: string) { 1637 for (var i = 0; i < this.scripts.length; i++) { 1638 if (this.scripts[i].name == name) { 1639 this.scripts[i].editContent(minChar, limChar, newText); 1640 return; 1641 } 1642 } 1643 1644 throw new Error("No script with name '" + name + "'"); 1645 } 1646 1647 public getScriptContent(scriptIndex: number): string { 1648 return this.scripts[scriptIndex].content; 1649 } 1650 1651 ////////////////////////////////////////////////////////////////////// 1652 // ILogger implementation 1653 // 1654 public information(): boolean { return false; } 1655 public debug(): boolean { return true; } 1656 public warning(): boolean { return true; } 1657 public error(): boolean { return true; } 1658 public fatal(): boolean { return true; } 1659 1660 public log(s: string): void { 1661 // For debugging... 1662 //IO.printLine("TypeScriptLS:" + s); 1663 } 1664 1665 ////////////////////////////////////////////////////////////////////// 1666 // ILanguageServiceShimHost implementation 1667 // 1668 1669 public getCompilationSettings(): string/*json for Tools.CompilationSettings*/ { 1670 return ""; // i.e. default settings 1671 } 1672 1673 public getScriptCount(): number { 1674 return this.scripts.length; 1675 } 1676 1677 public getScriptSourceText(scriptIndex: number, start: number, end: number): string { 1678 return this.scripts[scriptIndex].content.substring(start, end); 1679 } 1680 1681 public getScriptSourceLength(scriptIndex: number): number { 1682 return this.scripts[scriptIndex].content.length; 1683 } 1684 1685 public getScriptId(scriptIndex: number): string { 1686 return this.scripts[scriptIndex].name; 1687 } 1688 1689 public getScriptIsResident(scriptIndex: number): boolean { 1690 return this.scripts[scriptIndex].isResident; 1691 } 1692 1693 public getScriptVersion(scriptIndex: number): number { 1694 return this.scripts[scriptIndex].version; 1695 } 1696 1697 public getScriptEditRangeSinceVersion(scriptIndex: number, scriptVersion: number): string { 1698 var range = this.scripts[scriptIndex].getEditRangeSinceVersion(scriptVersion); 1699 var result = (range.minChar + "," + range.limChar + "," + range.delta); 1700 return result; 1701 } 1702 1703 /** Return a new instance of the language service shim, up-to-date wrt to typecheck. 1704 * To access the non-shim (i.e. actual) language service, use the "ls.languageService" property. 1705 */ 1706 public getLanguageService(): Services.ILanguageServiceShim { 1707 var ls = new Services.TypeScriptServicesFactory().createLanguageServiceShim(this); 1708 ls.refresh(true); 1709 this.ls = ls; 1710 return ls; 1711 } 1712 1713 /** Parse file given its source text */ 1714 public parseSourceText(fileName: string, sourceText: TypeScript.ISourceText): TypeScript.Script { 1715 var parser = new TypeScript.Parser(); 1716 parser.setErrorRecovery(null); 1717 parser.errorCallback = (a, b, c, d) => { }; 1718 1719 var script = parser.parse(sourceText, fileName, 0); 1720 return script; 1721 } 1722 1723 /** Parse a file on disk given its filename */ 1724 public parseFile(fileName: string) { 1725 var sourceText = new TypeScript.StringSourceText(IO.readFile(fileName)) 1726 return this.parseSourceText(fileName, sourceText); 1727 } 1728 1729 /** 1730 * @param line 1 based index 1731 * @param col 1 based index 1732 */ 1733 public lineColToPosition(fileName: string, line: number, col: number): number { 1734 var script = this.ls.languageService.getScriptAST(fileName); 1735 assert.notNull(script); 1736 assert.is(line >= 1); 1737 assert.is(col >= 1); 1738 assert.is(line <= script.locationInfo.lineMap.length); 1739 1740 return TypeScript.getPositionFromZeroBasedLineColumn(script, line - 1, col - 1); 1741 } 1742 1743 /** 1744 * @param line 0 based index 1745 * @param col 0 based index 1746 */ 1747 public positionToZeroBasedLineCol(fileName: string, position: number): TypeScript.ILineCol { 1748 var script = this.ls.languageService.getScriptAST(fileName); 1749 assert.notNull(script); 1750 1751 var result = TypeScript.getZeroBasedLineColumnFromPosition(script, position); 1752 1753 assert.is(result.line >= 0); 1754 assert.is(result.col >= 0); 1755 return result; 1756 } 1757 1758 /** Verify that applying edits to sourceFileName result in the content of the file baselineFileName */ 1759 public checkEdits(sourceFileName: string, baselineFileName: string, edits: Services.TextEdit[]) { 1760 var script = readFile(sourceFileName); 1761 var formattedScript = this.applyEdits(script, edits); 1762 var baseline = readFile(baselineFileName); 1763 1764 assert.noDiff(formattedScript, baseline); 1765 assert.equal(formattedScript, baseline); 1766 } 1767 1768 1769 /** Apply an array of text edits to a string, and return the resulting string. */ 1770 public applyEdits(content: string, edits: Services.TextEdit[]): string { 1771 var result = content; 1772 edits = this.normalizeEdits(edits); 1773 1774 for (var i = edits.length - 1; i >= 0; i--) { 1775 var edit = edits[i]; 1776 var prefix = result.substring(0, edit.minChar); 1777 var middle = edit.text; 1778 var suffix = result.substring(edit.limChar); 1779 result = prefix + middle + suffix; 1780 } 1781 return result; 1782 } 1783 1784 /** Normalize an array of edits by removing overlapping entries and sorting entries on the minChar position. */ 1785 private normalizeEdits(edits: Services.TextEdit[]): Services.TextEdit[] { 1786 var result: Services.TextEdit[] = []; 1787 1788 function mapEdits(edits: Services.TextEdit[]): { edit: Services.TextEdit; index: number; }[] { 1789 var result = []; 1790 for (var i = 0; i < edits.length; i++) { 1791 result.push({ edit: edits[i], index: i }); 1792 } 1793 return result; 1794 } 1795 1796 var temp = mapEdits(edits).sort(function (a, b) { 1797 var result = a.edit.minChar - b.edit.minChar; 1798 if (result == 0) 1799 result = a.index - b.index; 1800 return result; 1801 }); 1802 1803 var current = 0; 1804 var next = 1; 1805 while (current < temp.length) { 1806 var currentEdit = temp[current].edit; 1807 1808 // Last edit 1809 if (next >= temp.length) { 1810 result.push(currentEdit); 1811 current++; 1812 continue; 1813 } 1814 var nextEdit = temp[next].edit; 1815 1816 var gap = nextEdit.minChar - currentEdit.limChar; 1817 1818 // non-overlapping edits 1819 if (gap >= 0) { 1820 result.push(currentEdit); 1821 current = next; 1822 next++; 1823 continue; 1824 } 1825 1826 // overlapping edits: for now, we only support ignoring an next edit 1827 // entirely contained in the current edit. 1828 if (currentEdit.limChar >= nextEdit.limChar) { 1829 next++; 1830 continue; 1831 } 1832 else { 1833 throw new Error("Trying to apply overlapping edits"); 1834 } 1835 } 1836 1837 return result; 1838 } 1839 1840 public getHostSettings(): string { 1841 return JSON.stringify({ usePullLanguageService: usePull }); 1842 } 1843 } 1844 1845 // Describe/it definitions 1846 export function describe(description: string, block: () => any) { 1847 var newScenario = new Scenario(description, block); 1848 1849 if (Runnable.currentStack.length === 0) { 1850 Runnable.currentStack.push(currentRun); 1851 } 1852 1853 Runnable.currentStack[Runnable.currentStack.length - 1].addChild(newScenario); 1854 } 1855 export function it(description: string, block: () => void ) { 1856 var testCase = new TestCase(description, block); 1857 Runnable.currentStack[Runnable.currentStack.length - 1].addChild(testCase); 1858 } 1859 1860 export function run() { 1861 if (typeof process !== "undefined") { 1862 process.on('uncaughtException', Runnable.handleError); 1863 } 1864 1865 Baseline.reset(); 1866 currentRun.run(); 1867 } 1868 1869 /** Runs TypeScript or Javascript code. */ 1870 export module Runner { 1871 export function runCollateral(path: string, callback: (error: Error, result: any) => void ) { 1872 path = switchToForwardSlashes(path); 1873 runString(readFile(path), path.match(/[^\/]*$/)[0], callback); 1874 } 1875 1876 export function runJSString(code: string, callback: (error: Error, result: any) => void ) { 1877 // List of names that get overriden by various test code we eval 1878 var dangerNames: any = ['Array']; 1879 1880 var globalBackup: any = {}; 1881 var n: string = null; 1882 for (n in dangerNames) { 1883 globalBackup[dangerNames[n]] = global[dangerNames[n]]; 1884 } 1885 1886 try { 1887 var res = eval(code); 1888 1889 for (n in dangerNames) { 1890 global[dangerNames[n]] = globalBackup[dangerNames[n]]; 1891 } 1892 1893 callback(null, res); 1894 } catch (e) { 1895 for (n in dangerNames) { 1896 global[dangerNames[n]] = globalBackup[dangerNames[n]]; 1897 } 1898 1899 callback(e, null); 1900 } 1901 } 1902 1903 export function runString(code: string, unitName: string, callback: (error: Error, result: any) => void ) { 1904 Compiler.compileString(code, unitName, function (res) { 1905 runJSString(res.code, callback); 1906 }); 1907 } 1908 } 1909 1910 /** Support class for baseline files */ 1911 export module Baseline { 1912 var reportFilename = 'baseline-report.html'; 1913 1914 var firstRun = true; 1915 var htmlTrailer = '</body></html>'; 1916 var htmlLeader = '<html><head><title>Baseline Report</title>'; 1917 htmlLeader += ("<style>"); 1918 htmlLeader += '\r\n' + (".code { font: 9pt 'Courier New'; }"); 1919 htmlLeader += '\r\n' + (".old { background-color: #EE1111; }"); 1920 htmlLeader += '\r\n' + (".new { background-color: #FFFF11; }"); 1921 htmlLeader += '\r\n' + (".from { background-color: #EE1111; color: #1111EE; }"); 1922 htmlLeader += '\r\n' + (".to { background-color: #EEEE11; color: #1111EE; }"); 1923 htmlLeader += '\r\n' + ("h2 { margin-bottom: 0px; }"); 1924 htmlLeader += '\r\n' + ("h2 { padding-bottom: 0px; }"); 1925 htmlLeader += '\r\n' + ("h4 { font-weight: normal; }"); 1926 htmlLeader += '\r\n' + ("</style>"); 1927 1928 export interface BaselineOptions { 1929 LineEndingSensitive?: boolean; 1930 } 1931 1932 function localPath(filename: string) { 1933 if (global.runners[0].testType === 'prototyping') { 1934 return Harness.userSpecifiedroot + 'tests/baselines/prototyping/local/' + filename; 1935 } 1936 else { 1937 return Harness.userSpecifiedroot + 'tests/baselines/local/' + filename; 1938 } 1939 } 1940 1941 function referencePath(filename: string) { 1942 if (global.runners[0].testType === 'prototyping') { 1943 return Harness.userSpecifiedroot + 'tests/baselines/prototyping/reference/' + filename; 1944 } 1945 else { 1946 return Harness.userSpecifiedroot + 'tests/baselines/reference/' + filename; 1947 } 1948 } 1949 1950 export function reset() { 1951 if (IO.fileExists(reportFilename)) { 1952 IO.deleteFile(reportFilename); 1953 } 1954 } 1955 1956 function prepareBaselineReport(): string { 1957 var reportContent = htmlLeader; 1958 // Delete the baseline-report.html file if needed 1959 if (IO.fileExists(reportFilename)) { 1960 reportContent = IO.readFile(reportFilename); 1961 reportContent = reportContent.replace(htmlTrailer, ''); 1962 } else { 1963 reportContent = htmlLeader; 1964 } 1965 return reportContent; 1966 } 1967 1968 function generateActual(actualFilename: string, generateContent: () => string): string { 1969 // Create folders if needed 1970 IO.createDirectory(IO.dirName(IO.dirName(actualFilename))); 1971 IO.createDirectory(IO.dirName(actualFilename)); 1972 1973 // Delete the actual file in case it fails 1974 if (IO.fileExists(actualFilename)) { 1975 IO.deleteFile(actualFilename); 1976 } 1977 1978 var actual = generateContent(); 1979 1980 if (actual === undefined) { 1981 throw new Error('The generated content was "undefined". Return "null" if no baselining is required."'); 1982 } 1983 1984 // Store the content in the 'local' folder so we 1985 // can accept it later (manually) 1986 if (actual !== null) { 1987 IO.writeFile(actualFilename, actual); 1988 } 1989 1990 return actual; 1991 } 1992 1993 function compareToBaseline(actual: string, relativeFilename: string, opts: BaselineOptions) { 1994 // actual is now either undefined (the generator had an error), null (no file requested), 1995 // or some real output of the function 1996 if (actual === undefined) { 1997 // Nothing to do 1998 return; 1999 } 2000 2001 var refFilename = referencePath(relativeFilename); 2002 2003 if (actual === null) { 2004 actual = '<no content>'; 2005 } 2006 2007 var expected = '<no content>'; 2008 if (IO.fileExists(refFilename)) { 2009 expected = IO.readFile(refFilename); 2010 } 2011 2012 var lineEndingSensitive = opts && opts.LineEndingSensitive; 2013 2014 if (!lineEndingSensitive) { 2015 expected = expected.replace(/\r\n?/g, '\n') 2016 actual = actual.replace(/\r\n?/g, '\n') 2017 } 2018 2019 return { expected: expected, actual: actual }; 2020 } 2021 2022 function writeComparison(expected: string, actual: string, relativeFilename: string, actualFilename: string, descriptionForDescribe: string) { 2023 if (expected != actual) { 2024 // Overwrite & issue error 2025 var errMsg = 'The baseline file ' + relativeFilename + ' has changed. Please refer to baseline-report.html and '; 2026 errMsg += 'either fix the regression (if unintended) or run nmake baseline-accept (if intended).' 2027 2028 var refFilename = referencePath(relativeFilename); 2029 2030 // Append diff to the report 2031 var diff = new Diff.StringDiff(expected, actual); 2032 var header = '<h2>' + descriptionForDescribe + '</h2>'; 2033 header += '<h4>Left file: ' + actualFilename + '; Right file: ' + refFilename + '</h4>'; 2034 var trailer = '<hr>'; 2035 2036 var reportContentSoFar = prepareBaselineReport(); 2037 reportContentSoFar = reportContentSoFar + header + '<div class="code">' + diff.mergedHtml + '</div>' + trailer + htmlTrailer; 2038 IO.writeFile(reportFilename, reportContentSoFar); 2039 2040 throw new Error(errMsg); 2041 } 2042 } 2043 2044 export function runBaseline( 2045 descriptionForDescribe: string, 2046 relativeFilename: string, 2047 generateContent: () => string, 2048 runImmediately? = false, 2049 opts?: BaselineOptions) { 2050 2051 var actual = <string>undefined; 2052 var actualFilename = localPath(relativeFilename); 2053 2054 if (runImmediately) { 2055 var actual = generateActual(actualFilename, generateContent); 2056 var comparison = compareToBaseline(actual, relativeFilename, opts); 2057 writeComparison(comparison.expected, comparison.actual, relativeFilename, actualFilename, descriptionForDescribe); 2058 } else { 2059 describe(descriptionForDescribe, () => { 2060 var actual: string; 2061 2062 it('Can generate the content without error', () => { 2063 actual = generateActual(actualFilename, generateContent); 2064 }); 2065 2066 it('Matches the baseline file', () => { 2067 var comparison = compareToBaseline(actual, relativeFilename, opts); 2068 writeComparison(comparison.expected, comparison.actual, relativeFilename, actualFilename, descriptionForDescribe); 2069 }); 2070 }); 2071 } 2072 } 2073 } 2074 2075 var currentRun = new Run(); 2076 2077 global.describe = describe; 2078 global.run = run; 2079 global.it = it; 2080 global.assert = Harness.Assert; 2081} 2082 2083 2084//// [parserharness.js] 2085// 2086// Copyright (c) Microsoft Corporation. All rights reserved. 2087// 2088// Licensed under the Apache License, Version 2.0 (the "License"); 2089// you may not use this file except in compliance with the License. 2090// You may obtain a copy of the License at 2091// http://www.apache.org/licenses/LICENSE-2.0 2092// 2093// Unless required by applicable law or agreed to in writing, software 2094// distributed under the License is distributed on an "AS IS" BASIS, 2095// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2096// See the License for the specific language governing permissions and 2097// limitations under the License. 2098// 2099var __extends = (this && this.__extends) || (function () { 2100 var extendStatics = function (d, b) { 2101 extendStatics = Object.setPrototypeOf || 2102 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 2103 function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; 2104 return extendStatics(d, b); 2105 }; 2106 return function (d, b) { 2107 if (typeof b !== "function" && b !== null) 2108 throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); 2109 extendStatics(d, b); 2110 function __() { this.constructor = d; } 2111 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 2112 }; 2113})(); 2114///<reference path='..\compiler\io.ts'/> 2115///<reference path='..\compiler\typescript.ts'/> 2116///<reference path='..\services\typescriptServices.ts' /> 2117///<reference path='diff.ts'/> 2118function switchToForwardSlashes(path) { 2119 return path.replace(/\\/g, "/"); 2120} 2121function filePath(fullPath) { 2122 fullPath = switchToForwardSlashes(fullPath); 2123 var components = fullPath.split("/"); 2124 var path = components.slice(0, components.length - 1); 2125 return path.join("/") + "/"; 2126} 2127var typescriptServiceFileName = filePath(IO.getExecutingFilePath()) + "typescriptServices.js"; 2128var typescriptServiceFile = IO.readFile(typescriptServiceFileName); 2129if (typeof ActiveXObject === "function") { 2130 eval(typescriptServiceFile); 2131} 2132else if (typeof require === "function") { 2133 var vm = require('vm'); 2134 vm.runInThisContext(typescriptServiceFile, 'typescriptServices.js'); 2135} 2136else { 2137 throw new Error('Unknown context'); 2138} 2139var Harness; 2140(function (Harness) { 2141 // Settings 2142 Harness.userSpecifiedroot = ""; 2143 var global = Function("return this").call(null); 2144 Harness.usePull = false; 2145 // Assert functions 2146 var Assert; 2147 (function (Assert) { 2148 Assert.bugIds = []; 2149 Assert.throwAssertError = function (error) { 2150 throw error; 2151 }; 2152 // Marks that the current scenario is impacted by a bug 2153 function bug(id) { 2154 if (Assert.bugIds.indexOf(id) < 0) { 2155 Assert.bugIds.push(id); 2156 } 2157 } 2158 Assert.bug = bug; 2159 // If there are any bugs in the test code, mark the scenario as impacted appropriately 2160 function bugs(content) { 2161 var bugs = content.match(/\bbug (\d+)/i); 2162 if (bugs) { 2163 bugs.forEach(function (bug) { return assert.bug(bug); }); 2164 } 2165 } 2166 Assert.bugs = bugs; 2167 function is(result, msg) { 2168 if (!result) { 2169 Assert.throwAssertError(new Error(msg || "Expected true, got false.")); 2170 } 2171 } 2172 Assert.is = is; 2173 function arrayLengthIs(arr, length) { 2174 if (arr.length != length) { 2175 var actual = ''; 2176 arr.forEach(function (n) { return actual = actual + '\n ' + n.toString(); }); 2177 Assert.throwAssertError(new Error('Expected array to have ' + length + ' elements. Actual elements were:' + actual)); 2178 } 2179 } 2180 Assert.arrayLengthIs = arrayLengthIs; 2181 function equal(actual, expected) { 2182 if (actual !== expected) { 2183 Assert.throwAssertError(new Error("Expected " + actual + " to equal " + expected)); 2184 } 2185 } 2186 Assert.equal = equal; 2187 function notEqual(actual, expected) { 2188 if (actual === expected) { 2189 Assert.throwAssertError(new Error("Expected " + actual + " to *not* equal " + expected)); 2190 } 2191 } 2192 Assert.notEqual = notEqual; 2193 function notNull(result) { 2194 if (result === null) { 2195 Assert.throwAssertError(new Error("Expected " + result + " to *not* be null")); 2196 } 2197 } 2198 Assert.notNull = notNull; 2199 function compilerWarning(result, line, column, desc) { 2200 if (!result.isErrorAt(line, column, desc)) { 2201 var actual = ''; 2202 result.errors.forEach(function (err) { 2203 actual = actual + '\n ' + err.toString(); 2204 }); 2205 Assert.throwAssertError(new Error("Expected compiler warning at (" + line + ", " + column + "): " + desc + "\nActual errors follow: " + actual)); 2206 } 2207 } 2208 Assert.compilerWarning = compilerWarning; 2209 function noDiff(text1, text2) { 2210 text1 = text1.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n"); 2211 text2 = text2.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n"); 2212 if (text1 !== text2) { 2213 var errorString = ""; 2214 var text1Lines = text1.split(/\n/); 2215 var text2Lines = text2.split(/\n/); 2216 for (var i = 0; i < text1Lines.length; i++) { 2217 if (text1Lines[i] !== text2Lines[i]) { 2218 errorString += "Difference at line " + (i + 1) + ":\n"; 2219 errorString += " Left File: " + text1Lines[i] + "\n"; 2220 errorString += " Right File: " + text2Lines[i] + "\n\n"; 2221 } 2222 } 2223 Assert.throwAssertError(new Error(errorString)); 2224 } 2225 } 2226 Assert.noDiff = noDiff; 2227 function arrayContains(arr, contains) { 2228 var found; 2229 for (var i = 0; i < contains.length; i++) { 2230 found = false; 2231 for (var j = 0; j < arr.length; j++) { 2232 if (arr[j] === contains[i]) { 2233 found = true; 2234 break; 2235 } 2236 } 2237 if (!found) { 2238 Assert.throwAssertError(new Error("Expected array to contain \"" + contains[i] + "\"")); 2239 } 2240 } 2241 } 2242 Assert.arrayContains = arrayContains; 2243 function arrayContainsOnce(arr, filter) { 2244 var foundCount = 0; 2245 for (var i = 0; i < arr.length; i++) { 2246 if (filter(arr[i])) { 2247 foundCount++; 2248 } 2249 } 2250 if (foundCount !== 1) { 2251 Assert.throwAssertError(new Error("Expected array to match element only once (instead of " + foundCount + " times)")); 2252 } 2253 } 2254 Assert.arrayContainsOnce = arrayContainsOnce; 2255 })(Assert = Harness.Assert || (Harness.Assert = {})); 2256 /** Splits the given string on \r\n or on only \n if that fails */ 2257 function splitContentByNewlines(content) { 2258 // Split up the input file by line 2259 // Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so 2260 // we have to string-based splitting instead and try to figure out the delimiting chars 2261 var lines = content.split('\r\n'); 2262 if (lines.length === 1) { 2263 lines = content.split('\n'); 2264 } 2265 return lines; 2266 } 2267 Harness.splitContentByNewlines = splitContentByNewlines; 2268 /** Reads a file under /tests */ 2269 function readFile(path) { 2270 if (path.indexOf('tests') < 0) { 2271 path = "tests/" + path; 2272 } 2273 var content = IO.readFile(Harness.userSpecifiedroot + path); 2274 if (content == null) { 2275 throw new Error("failed to read file at: '" + Harness.userSpecifiedroot + path + "'"); 2276 } 2277 return content; 2278 } 2279 Harness.readFile = readFile; 2280 var Logger = /** @class */ (function () { 2281 function Logger() { 2282 } 2283 Logger.prototype.start = function (fileName, priority) { }; 2284 Logger.prototype.end = function (fileName) { }; 2285 Logger.prototype.scenarioStart = function (scenario) { }; 2286 Logger.prototype.scenarioEnd = function (scenario, error) { }; 2287 Logger.prototype.testStart = function (test) { }; 2288 Logger.prototype.pass = function (test) { }; 2289 Logger.prototype.bug = function (test) { }; 2290 Logger.prototype.fail = function (test) { }; 2291 Logger.prototype.error = function (test, error) { }; 2292 Logger.prototype.comment = function (comment) { }; 2293 Logger.prototype.verify = function (test, passed, actual, expected, message) { }; 2294 return Logger; 2295 }()); 2296 Harness.Logger = Logger; 2297 // Logger-related functions 2298 var loggers = []; 2299 function registerLogger(logger) { 2300 loggers.push(logger); 2301 } 2302 Harness.registerLogger = registerLogger; 2303 function emitLog(field) { 2304 var params = []; 2305 for (var _i = 1; _i < arguments.length; _i++) { 2306 params[_i - 1] = arguments[_i]; 2307 } 2308 for (var i = 0; i < loggers.length; i++) { 2309 if (typeof loggers[i][field] === 'function') { 2310 loggers[i][field].apply(loggers[i], params); 2311 } 2312 } 2313 } 2314 Harness.emitLog = emitLog; 2315 var Runnable = /** @class */ (function () { 2316 function Runnable(description, block) { 2317 this.description = description; 2318 this.block = block; 2319 // The error, if any, that occurred when running 'block' 2320 this.error = null; 2321 // Whether or not this object has any failures (including in its descendants) 2322 this.passed = null; 2323 // A list of bugs impacting this object 2324 this.bugs = []; 2325 // A list of all our child Runnables 2326 this.children = []; 2327 } 2328 Runnable.prototype.addChild = function (child) { 2329 this.children.push(child); 2330 }; 2331 /** Call function fn, which may take a done function and may possibly execute 2332 * asynchronously, calling done when finished. Returns true or false depending 2333 * on whether the function was asynchronous or not. 2334 */ 2335 Runnable.prototype.call = function (fn, done) { 2336 var isAsync = true; 2337 try { 2338 if (fn.length === 0) { 2339 // No async. 2340 fn(); 2341 done(); 2342 return false; 2343 } 2344 else { 2345 // Possibly async 2346 Runnable.pushGlobalErrorHandler(done); 2347 fn(function () { 2348 isAsync = false; // If we execute synchronously, this will get called before the return below. 2349 Runnable.popGlobalErrorHandler(); 2350 done(); 2351 }); 2352 return isAsync; 2353 } 2354 } 2355 catch (e) { 2356 done(e); 2357 return false; 2358 } 2359 }; 2360 Runnable.prototype.run = function (done) { }; 2361 Runnable.prototype.runBlock = function (done) { 2362 return this.call(this.block, done); 2363 }; 2364 Runnable.prototype.runChild = function (index, done) { 2365 var _this = this; 2366 return this.call((function (done) { return _this.children[index].run(done); }), done); 2367 }; 2368 Runnable.pushGlobalErrorHandler = function (done) { 2369 errorHandlerStack.push(function (e) { 2370 done(e); 2371 }); 2372 }; 2373 Runnable.popGlobalErrorHandler = function () { 2374 errorHandlerStack.pop(); 2375 }; 2376 Runnable.handleError = function (e) { 2377 if (errorHandlerStack.length === 0) { 2378 IO.printLine('Global error: ' + e); 2379 } 2380 else { 2381 errorHandlerStack[errorHandlerStack.length - 1](e); 2382 } 2383 }; 2384 // The current stack of Runnable objects 2385 Runnable.currentStack = []; 2386 Runnable.errorHandlerStack = []; 2387 return Runnable; 2388 }()); 2389 Harness.Runnable = Runnable; 2390 var TestCase = /** @class */ (function (_super) { 2391 __extends(TestCase, _super); 2392 function TestCase(description, block) { 2393 var _this = _super.call(this, description, block) || this; 2394 _this.description = description; 2395 _this.block = block; 2396 return _this; 2397 } 2398 TestCase.prototype.addChild = function (child) { 2399 throw new Error("Testcases may not be nested inside other testcases"); 2400 }; 2401 /** Run the test case block and fail the test if it raised an error. If no error is raised, the test passes. */ 2402 TestCase.prototype.run = function (done) { 2403 var that = this; 2404 Runnable.currentStack.push(this); 2405 emitLog('testStart', { desc: this.description }); 2406 if (this.block) { 2407 var async = this.runBlock(function (e) { 2408 if (e) { 2409 that.passed = false; 2410 that.error = e; 2411 emitLog('error', { desc: this.description, pass: false }, e); 2412 } 2413 else { 2414 that.passed = true; 2415 emitLog('pass', { desc: this.description, pass: true }); 2416 } 2417 Runnable.currentStack.pop(); 2418 done(); 2419 }); 2420 } 2421 }; 2422 return TestCase; 2423 }(Runnable)); 2424 Harness.TestCase = TestCase; 2425 var Scenario = /** @class */ (function (_super) { 2426 __extends(Scenario, _super); 2427 function Scenario(description, block) { 2428 var _this = _super.call(this, description, block) || this; 2429 _this.description = description; 2430 _this.block = block; 2431 return _this; 2432 } 2433 /** Run the block, and if the block doesn't raise an error, run the children. */ 2434 Scenario.prototype.run = function (done) { 2435 var that = this; 2436 Runnable.currentStack.push(this); 2437 emitLog('scenarioStart', { desc: this.description }); 2438 var async = this.runBlock(function (e) { 2439 Runnable.currentStack.pop(); 2440 if (e) { 2441 that.passed = false; 2442 that.error = e; 2443 var metadata = { id: undefined, desc: this.description, pass: false, bugs: assert.bugIds }; 2444 // Report all bugs affecting this scenario 2445 assert.bugIds.forEach(function (desc) { return emitLog('bug', metadata, desc); }); 2446 emitLog('scenarioEnd', metadata, e); 2447 done(); 2448 } 2449 else { 2450 that.passed = true; // so far so good. 2451 that.runChildren(done); 2452 } 2453 }); 2454 }; 2455 /** Run the children of the scenario (other scenarios and test cases). If any fail, 2456 * set this scenario to failed. Synchronous tests will run synchronously without 2457 * adding stack frames. 2458 */ 2459 Scenario.prototype.runChildren = function (done, index) { 2460 if (index === void 0) { index = 0; } 2461 var that = this; 2462 var async = false; 2463 for (; index < this.children.length; index++) { 2464 async = this.runChild(index, function (e) { 2465 that.passed = that.passed && that.children[index].passed; 2466 if (async) 2467 that.runChildren(done, index + 1); 2468 }); 2469 if (async) 2470 return; 2471 } 2472 var metadata = { id: undefined, desc: this.description, pass: this.passed, bugs: assert.bugIds }; 2473 // Report all bugs affecting this scenario 2474 assert.bugIds.forEach(function (desc) { return emitLog('bug', metadata, desc); }); 2475 emitLog('scenarioEnd', metadata); 2476 done(); 2477 }; 2478 return Scenario; 2479 }(Runnable)); 2480 Harness.Scenario = Scenario; 2481 var Run = /** @class */ (function (_super) { 2482 __extends(Run, _super); 2483 function Run() { 2484 return _super.call(this, 'Test Run', null) || this; 2485 } 2486 Run.prototype.run = function () { 2487 emitLog('start'); 2488 this.runChildren(); 2489 }; 2490 Run.prototype.runChildren = function (index) { 2491 if (index === void 0) { index = 0; } 2492 var async = false; 2493 var that = this; 2494 for (; index < this.children.length; index++) { 2495 // Clear out bug descriptions 2496 assert.bugIds = []; 2497 async = this.runChild(index, function (e) { 2498 if (async) { 2499 that.runChildren(index + 1); 2500 } 2501 }); 2502 if (async) { 2503 return; 2504 } 2505 } 2506 Perf.runBenchmarks(); 2507 emitLog('end'); 2508 }; 2509 return Run; 2510 }(Runnable)); 2511 Harness.Run = Run; 2512 // Performance test 2513 var Perf; 2514 (function (Perf) { 2515 var Clock; 2516 (function (Clock) { 2517 if (typeof WScript !== "undefined" && typeof global['WScript'].InitializeProjection !== "undefined") { 2518 // Running in JSHost. 2519 global['WScript'].InitializeProjection(); 2520 Clock.now = function () { 2521 return TestUtilities.QueryPerformanceCounter(); 2522 }; 2523 Clock.resolution = TestUtilities.QueryPerformanceFrequency(); 2524 } 2525 else { 2526 Clock.now = function () { 2527 return Date.now(); 2528 }; 2529 Clock.resolution = 1000; 2530 } 2531 })(Clock = Perf.Clock || (Perf.Clock = {})); 2532 var Timer = /** @class */ (function () { 2533 function Timer() { 2534 this.time = 0; 2535 } 2536 Timer.prototype.start = function () { 2537 this.time = 0; 2538 this.startTime = Clock.now(); 2539 }; 2540 Timer.prototype.end = function () { 2541 // Set time to MS. 2542 this.time = (Clock.now() - this.startTime) / Clock.resolution * 1000; 2543 }; 2544 return Timer; 2545 }()); 2546 Perf.Timer = Timer; 2547 var Dataset = /** @class */ (function () { 2548 function Dataset() { 2549 this.data = []; 2550 } 2551 Dataset.prototype.add = function (value) { 2552 this.data.push(value); 2553 }; 2554 Dataset.prototype.mean = function () { 2555 var sum = 0; 2556 for (var i = 0; i < this.data.length; i++) { 2557 sum += this.data[i]; 2558 } 2559 return sum / this.data.length; 2560 }; 2561 Dataset.prototype.min = function () { 2562 var min = this.data[0]; 2563 for (var i = 1; i < this.data.length; i++) { 2564 if (this.data[i] < min) { 2565 min = this.data[i]; 2566 } 2567 } 2568 return min; 2569 }; 2570 Dataset.prototype.max = function () { 2571 var max = this.data[0]; 2572 for (var i = 1; i < this.data.length; i++) { 2573 if (this.data[i] > max) { 2574 max = this.data[i]; 2575 } 2576 } 2577 return max; 2578 }; 2579 Dataset.prototype.stdDev = function () { 2580 var sampleMean = this.mean(); 2581 var sumOfSquares = 0; 2582 for (var i = 0; i < this.data.length; i++) { 2583 sumOfSquares += Math.pow(this.data[i] - sampleMean, 2); 2584 } 2585 return Math.sqrt(sumOfSquares / this.data.length); 2586 }; 2587 return Dataset; 2588 }()); 2589 Perf.Dataset = Dataset; 2590 // Base benchmark class with some defaults. 2591 var Benchmark = /** @class */ (function () { 2592 function Benchmark() { 2593 this.iterations = 10; 2594 this.description = ""; 2595 this.results = {}; 2596 } 2597 Benchmark.prototype.bench = function (subBench) { }; 2598 Benchmark.prototype.before = function () { }; 2599 Benchmark.prototype.beforeEach = function () { }; 2600 Benchmark.prototype.after = function () { }; 2601 Benchmark.prototype.afterEach = function () { }; 2602 Benchmark.prototype.addTimingFor = function (name, timing) { 2603 this.results[name] = this.results[name] || new Dataset(); 2604 this.results[name].add(timing); 2605 }; 2606 return Benchmark; 2607 }()); 2608 Perf.Benchmark = Benchmark; 2609 Perf.benchmarks = []; 2610 var timeFunction; 2611 timeFunction = function (benchmark, description, name, f) { 2612 if (description === void 0) { description = benchmark.description; } 2613 if (name === void 0) { name = ''; } 2614 if (f === void 0) { f = benchmark.bench; } 2615 var t = new Timer(); 2616 t.start(); 2617 var subBenchmark = function (name, f) { 2618 timeFunction(benchmark, description, name, f); 2619 }; 2620 f.call(benchmark, subBenchmark); 2621 t.end(); 2622 benchmark.addTimingFor(name, t.time); 2623 }; 2624 function runBenchmarks() { 2625 for (var i = 0; i < Perf.benchmarks.length; i++) { 2626 var b = new Perf.benchmarks[i](); 2627 var t = new Timer(); 2628 b.before(); 2629 for (var j = 0; j < b.iterations; j++) { 2630 b.beforeEach(); 2631 timeFunction(b); 2632 b.afterEach(); 2633 } 2634 b.after(); 2635 for (var prop in b.results) { 2636 var description = b.description + (prop ? ": " + prop : ''); 2637 emitLog('testStart', { desc: description }); 2638 emitLog('pass', { 2639 desc: description, pass: true, perfResults: { 2640 mean: b.results[prop].mean(), 2641 min: b.results[prop].min(), 2642 max: b.results[prop].max(), 2643 stdDev: b.results[prop].stdDev(), 2644 trials: b.results[prop].data 2645 } 2646 }); 2647 } 2648 } 2649 } 2650 Perf.runBenchmarks = runBenchmarks; 2651 // Replace with better type when classes are assignment compatible with 2652 // the below type. 2653 // export function addBenchmark(BenchmarkClass: {new(): Benchmark;}) { 2654 function addBenchmark(BenchmarkClass) { 2655 Perf.benchmarks.push(BenchmarkClass); 2656 } 2657 Perf.addBenchmark = addBenchmark; 2658 })(Perf = Harness.Perf || (Harness.Perf = {})); 2659 /** Functionality for compiling TypeScript code */ 2660 var Compiler; 2661 (function (Compiler) { 2662 /** Aggregate various writes into a single array of lines. Useful for passing to the 2663 * TypeScript compiler to fill with source code or errors. 2664 */ 2665 var WriterAggregator = /** @class */ (function () { 2666 function WriterAggregator() { 2667 this.lines = []; 2668 this.currentLine = ""; 2669 } 2670 WriterAggregator.prototype.Write = function (str) { 2671 this.currentLine += str; 2672 }; 2673 WriterAggregator.prototype.WriteLine = function (str) { 2674 this.lines.push(this.currentLine + str); 2675 this.currentLine = ""; 2676 }; 2677 WriterAggregator.prototype.Close = function () { 2678 if (this.currentLine.length > 0) { 2679 this.lines.push(this.currentLine); 2680 } 2681 this.currentLine = ""; 2682 }; 2683 WriterAggregator.prototype.reset = function () { 2684 this.lines = []; 2685 this.currentLine = ""; 2686 }; 2687 return WriterAggregator; 2688 }()); 2689 Compiler.WriterAggregator = WriterAggregator; 2690 /** Mimics having multiple files, later concatenated to a single file. */ 2691 var EmitterIOHost = /** @class */ (function () { 2692 function EmitterIOHost() { 2693 this.fileCollection = {}; 2694 } 2695 /** create file gets the whole path to create, so this works as expected with the --out parameter */ 2696 EmitterIOHost.prototype.createFile = function (s, useUTF8) { 2697 if (this.fileCollection[s]) { 2698 return this.fileCollection[s]; 2699 } 2700 var writer = new Harness.Compiler.WriterAggregator(); 2701 this.fileCollection[s] = writer; 2702 return writer; 2703 }; 2704 EmitterIOHost.prototype.directoryExists = function (s) { return false; }; 2705 EmitterIOHost.prototype.fileExists = function (s) { return typeof this.fileCollection[s] !== 'undefined'; }; 2706 EmitterIOHost.prototype.resolvePath = function (s) { return s; }; 2707 EmitterIOHost.prototype.reset = function () { this.fileCollection = {}; }; 2708 EmitterIOHost.prototype.toArray = function () { 2709 var result = []; 2710 for (var p in this.fileCollection) { 2711 if (this.fileCollection.hasOwnProperty(p)) { 2712 var current = this.fileCollection[p]; 2713 if (current.lines.length > 0) { 2714 if (p !== '0.js') { 2715 current.lines.unshift('////[' + p + ']'); 2716 } 2717 result.push({ filename: p, file: this.fileCollection[p] }); 2718 } 2719 } 2720 } 2721 return result; 2722 }; 2723 return EmitterIOHost; 2724 }()); 2725 Compiler.EmitterIOHost = EmitterIOHost; 2726 var libFolder = global['WScript'] ? TypeScript.filePath(global['WScript'].ScriptFullName) : (__dirname + '/'); 2727 Compiler.libText = IO ? IO.readFile(libFolder + "lib.d.ts") : ''; 2728 var stdout = new EmitterIOHost(); 2729 var stderr = new WriterAggregator(); 2730 function isDeclareFile(filename) { 2731 return /\.d\.ts$/.test(filename); 2732 } 2733 Compiler.isDeclareFile = isDeclareFile; 2734 function makeDefaultCompilerForTest(c) { 2735 var compiler = c || new TypeScript.TypeScriptCompiler(stderr); 2736 compiler.parser.errorRecovery = true; 2737 compiler.settings.codeGenTarget = TypeScript.CodeGenTarget.ES5; 2738 compiler.settings.controlFlow = true; 2739 compiler.settings.controlFlowUseDef = true; 2740 if (Harness.usePull) { 2741 compiler.settings.usePull = true; 2742 compiler.settings.useFidelity = true; 2743 } 2744 compiler.parseEmitOption(stdout); 2745 TypeScript.moduleGenTarget = TypeScript.ModuleGenTarget.Synchronous; 2746 compiler.addUnit(Harness.Compiler.libText, "lib.d.ts", true); 2747 return compiler; 2748 } 2749 Compiler.makeDefaultCompilerForTest = makeDefaultCompilerForTest; 2750 var compiler; 2751 recreate(); 2752 // pullUpdateUnit is sufficient if an existing unit is updated, if a new unit is added we need to do a full typecheck 2753 var needsFullTypeCheck = true; 2754 function compile(code, filename) { 2755 if (Harness.usePull) { 2756 if (needsFullTypeCheck) { 2757 compiler.pullTypeCheck(true); 2758 needsFullTypeCheck = false; 2759 } 2760 else { 2761 // requires unit to already exist in the compiler 2762 compiler.pullUpdateUnit(new TypeScript.StringSourceText(""), filename, true); 2763 compiler.pullUpdateUnit(new TypeScript.StringSourceText(code), filename, true); 2764 } 2765 } 2766 else { 2767 compiler.reTypeCheck(); 2768 } 2769 } 2770 Compiler.compile = compile; 2771 // Types 2772 var Type = /** @class */ (function () { 2773 function Type(type, code, identifier) { 2774 this.type = type; 2775 this.code = code; 2776 this.identifier = identifier; 2777 } 2778 Type.prototype.normalizeToArray = function (arg) { 2779 if ((Array.isArray && Array.isArray(arg)) || arg instanceof Array) 2780 return arg; 2781 return [arg]; 2782 }; 2783 Type.prototype.compilesOk = function (testCode) { 2784 var errors = null; 2785 compileString(testCode, 'test.ts', function (compilerResult) { 2786 errors = compilerResult.errors; 2787 }); 2788 return errors.length === 0; 2789 }; 2790 Type.prototype.isSubtypeOf = function (other) { 2791 var testCode = 'class __test1__ {\n'; 2792 testCode += ' public test() {\n'; 2793 testCode += ' ' + other.code + ';\n'; 2794 testCode += ' return ' + other.identifier + ';\n'; 2795 testCode += ' }\n'; 2796 testCode += '}\n'; 2797 testCode += 'class __test2__ extends __test1__ {\n'; 2798 testCode += ' public test() {\n'; 2799 testCode += ' ' + this.code + ';\n'; 2800 testCode += ' return ' + other.identifier + ';\n'; 2801 testCode += ' }\n'; 2802 testCode += '}\n'; 2803 return this.compilesOk(testCode); 2804 }; 2805 // TODO: Find an implementation of isIdenticalTo that works. 2806 //public isIdenticalTo(other: Type) { 2807 // var testCode = 'module __test1__ {\n'; 2808 // testCode += ' ' + this.code + ';\n'; 2809 // testCode += ' export var __val__ = ' + this.identifier + ';\n'; 2810 // testCode += '}\n'; 2811 // testCode += 'var __test1__val__ = __test1__.__val__;\n'; 2812 // testCode += 'module __test2__ {\n'; 2813 // testCode += ' ' + other.code + ';\n'; 2814 // testCode += ' export var __val__ = ' + other.identifier + ';\n'; 2815 // testCode += '}\n'; 2816 // testCode += 'var __test2__val__ = __test2__.__val__;\n'; 2817 // testCode += 'function __test__function__() { if(true) { return __test1__val__ }; return __test2__val__; }'; 2818 // return this.compilesOk(testCode); 2819 //} 2820 Type.prototype.assertSubtypeOf = function (others) { 2821 others = this.normalizeToArray(others); 2822 for (var i = 0; i < others.length; i++) { 2823 if (!this.isSubtypeOf(others[i])) { 2824 throw new Error("Expected " + this.type + " to be a subtype of " + others[i].type); 2825 } 2826 } 2827 }; 2828 Type.prototype.assertNotSubtypeOf = function (others) { 2829 others = this.normalizeToArray(others); 2830 for (var i = 0; i < others.length; i++) { 2831 if (this.isSubtypeOf(others[i])) { 2832 throw new Error("Expected " + this.type + " to be a subtype of " + others[i].type); 2833 } 2834 } 2835 }; 2836 //public assertIdenticalTo(other: Type) { 2837 // if (!this.isIdenticalTo(other)) { 2838 // throw new Error("Expected " + this.type + " to be identical to " + other.type); 2839 // } 2840 //} 2841 //public assertNotIdenticalTo(other: Type) { 2842 // if (!this.isIdenticalTo(other)) { 2843 // throw new Error("Expected " + this.type + " to not be identical to " + other.type); 2844 // } 2845 //} 2846 Type.prototype.isAssignmentCompatibleWith = function (other) { 2847 var testCode = 'module __test1__ {\n'; 2848 testCode += ' ' + this.code + ';\n'; 2849 testCode += ' export var __val__ = ' + this.identifier + ';\n'; 2850 testCode += '}\n'; 2851 testCode += 'var __test1__val__ = __test1__.__val__;\n'; 2852 testCode += 'module __test2__ {\n'; 2853 testCode += ' export ' + other.code + ';\n'; 2854 testCode += ' export var __val__ = ' + other.identifier + ';\n'; 2855 testCode += '}\n'; 2856 testCode += 'var __test2__val__ = __test2__.__val__;\n'; 2857 testCode += '__test2__val__ = __test1__val__;'; 2858 return this.compilesOk(testCode); 2859 }; 2860 Type.prototype.assertAssignmentCompatibleWith = function (others) { 2861 others = this.normalizeToArray(others); 2862 for (var i = 0; i < others.length; i++) { 2863 var other = others[i]; 2864 if (!this.isAssignmentCompatibleWith(other)) { 2865 throw new Error("Expected " + this.type + " to be assignment compatible with " + other.type); 2866 } 2867 } 2868 }; 2869 Type.prototype.assertNotAssignmentCompatibleWith = function (others) { 2870 others = this.normalizeToArray(others); 2871 for (var i = 0; i < others.length; i++) { 2872 var other = others[i]; 2873 if (this.isAssignmentCompatibleWith(other)) { 2874 throw new Error("Expected " + this.type + " to not be assignment compatible with " + other.type); 2875 } 2876 } 2877 }; 2878 Type.prototype.assertThisCanBeAssignedTo = function (desc, these, notThese) { 2879 var _this = this; 2880 it(desc + " is assignable to ", function () { 2881 _this.assertAssignmentCompatibleWith(these); 2882 }); 2883 it(desc + " not assignable to ", function () { 2884 _this.assertNotAssignmentCompatibleWith(notThese); 2885 }); 2886 }; 2887 return Type; 2888 }()); 2889 Compiler.Type = Type; 2890 var TypeFactory = /** @class */ (function () { 2891 function TypeFactory() { 2892 this.any = this.get('var x : any', 'x'); 2893 this.number = this.get('var x : number', 'x'); 2894 this.string = this.get('var x : string', 'x'); 2895 this.boolean = this.get('var x : boolean', 'x'); 2896 } 2897 TypeFactory.prototype.get = function (code, target) { 2898 var targetIdentifier = ''; 2899 var targetPosition = -1; 2900 if (typeof target === "string") { 2901 targetIdentifier = target; 2902 } 2903 else if (typeof target === "number") { 2904 targetPosition = target; 2905 } 2906 else { 2907 throw new Error("Expected string or number not " + (typeof target)); 2908 } 2909 var errors = null; 2910 compileString(code, 'test.ts', function (compilerResult) { 2911 errors = compilerResult.errors; 2912 }); 2913 if (errors.length > 0) 2914 throw new Error("Type definition contains errors: " + errors.join(",")); 2915 var matchingIdentifiers = []; 2916 if (!Harness.usePull) { 2917 // This will find the requested identifier in the first script where it's present, a naive search of each member in each script, 2918 // which means this won't play nicely if the same identifier is used in multiple units, but it will enable this to work on multi-file tests. 2919 // m = 1 because the first script will always be lib.d.ts which we don't want to search. 2920 for (var m = 1; m < compiler.scripts.members.length; m++) { 2921 var script = compiler.scripts.members[m]; 2922 var enclosingScopeContext = TypeScript.findEnclosingScopeAt(new TypeScript.NullLogger(), script, new TypeScript.StringSourceText(code), 0, false); 2923 var entries = new TypeScript.ScopeTraversal(compiler).getScopeEntries(enclosingScopeContext); 2924 for (var i = 0; i < entries.length; i++) { 2925 if (entries[i].name === targetIdentifier) { 2926 matchingIdentifiers.push(new Type(entries[i].type, code, targetIdentifier)); 2927 } 2928 } 2929 } 2930 } 2931 else { 2932 for (var m = 0; m < compiler.scripts.members.length; m++) { 2933 var script2 = compiler.scripts.members[m]; 2934 if (script2.locationInfo.filename !== 'lib.d.ts') { 2935 if (targetPosition > -1) { 2936 var tyInfo = compiler.pullGetTypeInfoAtPosition(targetPosition, script2); 2937 var name = this.getTypeInfoName(tyInfo.ast); 2938 var foundValue = new Type(tyInfo.typeInfo, code, name); 2939 if (!matchingIdentifiers.some(function (value) { return (value.identifier === foundValue.identifier) && (value.code === foundValue.code) && (value.type === foundValue.type); })) { 2940 matchingIdentifiers.push(foundValue); 2941 } 2942 } 2943 else { 2944 for (var pos = 0; pos < code.length; pos++) { 2945 var tyInfo = compiler.pullGetTypeInfoAtPosition(pos, script2); 2946 var name = this.getTypeInfoName(tyInfo.ast); 2947 if (name === targetIdentifier) { 2948 var foundValue = new Type(tyInfo.typeInfo, code, targetIdentifier); 2949 if (!matchingIdentifiers.some(function (value) { return (value.identifier === foundValue.identifier) && (value.code === foundValue.code) && (value.type === foundValue.type); })) { 2950 matchingIdentifiers.push(foundValue); 2951 } 2952 } 2953 } 2954 } 2955 } 2956 } 2957 } 2958 if (matchingIdentifiers.length === 0) { 2959 if (targetPosition > -1) { 2960 throw new Error("Could not find an identifier at position " + targetPosition); 2961 } 2962 else { 2963 throw new Error("Could not find an identifier " + targetIdentifier + " in any known scopes"); 2964 } 2965 } 2966 else if (matchingIdentifiers.length > 1) { 2967 throw new Error("Found multiple matching identifiers for " + target); 2968 } 2969 else { 2970 return matchingIdentifiers[0]; 2971 } 2972 }; 2973 TypeFactory.prototype.getTypeInfoName = function (ast) { 2974 var name = ''; 2975 switch (ast.nodeType) { 2976 case TypeScript.NodeType.Name: // Type Name? 2977 case TypeScript.NodeType.Null: 2978 case TypeScript.NodeType.List: 2979 case TypeScript.NodeType.Empty: 2980 case TypeScript.NodeType.EmptyExpr: 2981 case TypeScript.NodeType.Asg: 2982 case TypeScript.NodeType.True: 2983 case TypeScript.NodeType.False: 2984 case TypeScript.NodeType.ArrayLit: 2985 case TypeScript.NodeType.TypeRef: 2986 break; 2987 case TypeScript.NodeType.Super: 2988 name = ast.text; 2989 break; 2990 case TypeScript.NodeType.Regex: 2991 name = ast.text; 2992 break; 2993 case TypeScript.NodeType.QString: 2994 name = ast.text; 2995 break; 2996 case TypeScript.NodeType.NumberLit: 2997 name = ast.text; 2998 break; 2999 case TypeScript.NodeType.Return: 3000 //name = (<TypeScript.ReturnStatement>tyInfo.ast).returnExpression.actualText; // why is this complaining? 3001 break; 3002 case TypeScript.NodeType.InterfaceDeclaration: 3003 name = ast.name.actualText; 3004 break; 3005 case TypeScript.NodeType.ModuleDeclaration: 3006 name = ast.name.actualText; 3007 break; 3008 case TypeScript.NodeType.ClassDeclaration: 3009 name = ast.name.actualText; 3010 break; 3011 case TypeScript.NodeType.FuncDecl: 3012 name = !ast.name ? "" : ast.name.actualText; // name == null for lambdas 3013 break; 3014 default: 3015 // TODO: is there a reason to mess with all the special cases above and not just do this (ie take whatever property is there and works?) 3016 var a = ast; 3017 name = (a.id) ? (a.id.actualText) : (a.name) ? a.name.actualText : (a.text) ? a.text : ''; 3018 break; 3019 } 3020 return name; 3021 }; 3022 TypeFactory.prototype.isOfType = function (expr, expectedType) { 3023 var actualType = this.get('var _v_a_r_ = ' + expr, '_v_a_r_'); 3024 it('Expression "' + expr + '" is of type "' + expectedType + '"', function () { 3025 assert.equal(actualType.type, expectedType); 3026 }); 3027 }; 3028 return TypeFactory; 3029 }()); 3030 Compiler.TypeFactory = TypeFactory; 3031 /** Generates a .d.ts file for the given code 3032 * @param verifyNoDeclFile pass true when the given code should generate no decl file, false otherwise 3033 * @param unitName add the given code under thie name, else use '0.ts' 3034 * @param compilationContext a set of functions to be run before and after compiling this code for doing things like adding dependencies first 3035 * @param references the set of referenced files used by the given code 3036 */ 3037 function generateDeclFile(code, verifyNoDeclFile, unitName, compilationContext, references) { 3038 reset(); 3039 compiler.settings.generateDeclarationFiles = true; 3040 var oldOutputOption = compiler.settings.outputOption; 3041 var oldEmitterIOHost = compiler.emitSettings.ioHost; 3042 try { 3043 if (compilationContext && compilationContext.preCompile) { 3044 compilationContext.preCompile(); 3045 } 3046 addUnit(code, unitName, false, false, references); 3047 compiler.reTypeCheck(); 3048 var outputs = {}; 3049 compiler.settings.outputOption = ""; 3050 compiler.parseEmitOption({ 3051 createFile: function (fn) { 3052 outputs[fn] = new Harness.Compiler.WriterAggregator(); 3053 return outputs[fn]; 3054 }, 3055 directoryExists: function (path) { return true; }, 3056 fileExists: function (path) { return true; }, 3057 resolvePath: function (path) { return path; } 3058 }); 3059 compiler.emitDeclarations(); 3060 var results = null; 3061 for (var fn in outputs) { 3062 if (fn.indexOf('.d.ts') >= 0) { 3063 var writer = outputs[fn]; 3064 writer.Close(); 3065 results = writer.lines.join('\n'); 3066 if (verifyNoDeclFile && results != "") { 3067 throw new Error('Compilation should not produce ' + fn); 3068 } 3069 } 3070 } 3071 if (results) { 3072 return results; 3073 } 3074 if (!verifyNoDeclFile) { 3075 throw new Error('Compilation did not produce .d.ts files'); 3076 } 3077 } 3078 finally { 3079 compiler.settings.generateDeclarationFiles = false; 3080 compiler.settings.outputOption = oldOutputOption; 3081 compiler.parseEmitOption(oldEmitterIOHost); 3082 if (compilationContext && compilationContext.postCompile) { 3083 compilationContext.postCompile(); 3084 } 3085 var uName = unitName || '0.ts'; 3086 updateUnit('', uName); 3087 } 3088 return ''; 3089 } 3090 Compiler.generateDeclFile = generateDeclFile; 3091 /** Contains the code and errors of a compilation and some helper methods to check its status. */ 3092 var CompilerResult = /** @class */ (function () { 3093 /** @param fileResults an array of strings for the filename and an ITextWriter with its code */ 3094 function CompilerResult(fileResults, errorLines, scripts) { 3095 this.fileResults = fileResults; 3096 this.scripts = scripts; 3097 var lines = []; 3098 fileResults.forEach(function (v) { return lines = lines.concat(v.file.lines); }); 3099 this.code = lines.join("\n"); 3100 this.errors = []; 3101 for (var i = 0; i < errorLines.length; i++) { 3102 if (Harness.usePull) { 3103 var err = errorLines[i]; // TypeScript.PullError 3104 this.errors.push(new CompilerError(err.filename, 0, 0, err.message)); 3105 } 3106 else { 3107 var match = errorLines[i].match(/([^\(]*)\((\d+),(\d+)\):\s+((.*[\s\r\n]*.*)+)\s*$/); 3108 if (match) { 3109 this.errors.push(new CompilerError(match[1], parseFloat(match[2]), parseFloat(match[3]), match[4])); 3110 } 3111 else { 3112 WScript.Echo("non-match on: " + errorLines[i]); 3113 } 3114 } 3115 } 3116 } 3117 CompilerResult.prototype.isErrorAt = function (line, column, message) { 3118 for (var i = 0; i < this.errors.length; i++) { 3119 if (this.errors[i].line === line && this.errors[i].column === column && this.errors[i].message === message) 3120 return true; 3121 } 3122 return false; 3123 }; 3124 return CompilerResult; 3125 }()); 3126 Compiler.CompilerResult = CompilerResult; 3127 // Compiler Error. 3128 var CompilerError = /** @class */ (function () { 3129 function CompilerError(file, line, column, message) { 3130 this.file = file; 3131 this.line = line; 3132 this.column = column; 3133 this.message = message; 3134 } 3135 CompilerError.prototype.toString = function () { 3136 return this.file + "(" + this.line + "," + this.column + "): " + this.message; 3137 }; 3138 return CompilerError; 3139 }()); 3140 Compiler.CompilerError = CompilerError; 3141 /** Create a new instance of the compiler with default settings and lib.d.ts, then typecheck */ 3142 function recreate() { 3143 compiler = makeDefaultCompilerForTest(); 3144 if (Harness.usePull) { 3145 compiler.pullTypeCheck(true); 3146 } 3147 else { 3148 compiler.typeCheck(); 3149 } 3150 } 3151 Compiler.recreate = recreate; 3152 function reset() { 3153 stdout.reset(); 3154 stderr.reset(); 3155 var files = compiler.units.map(function (value) { return value.filename; }); 3156 for (var i = 0; i < files.length; i++) { 3157 var fname = files[i]; 3158 if (fname !== 'lib.d.ts') { 3159 updateUnit('', fname); 3160 } 3161 } 3162 compiler.errorReporter.hasErrors = false; 3163 } 3164 Compiler.reset = reset; 3165 function addUnit(code, unitName, isResident, isDeclareFile, references) { 3166 var script = null; 3167 var uName = unitName || '0' + (isDeclareFile ? '.d.ts' : '.ts'); 3168 for (var i = 0; i < compiler.units.length; i++) { 3169 if (compiler.units[i].filename === uName) { 3170 updateUnit(code, uName); 3171 script = compiler.scripts.members[i]; 3172 } 3173 } 3174 if (!script) { 3175 // TODO: make this toggleable, shouldn't be necessary once typecheck bugs are cleaned up 3176 // but without it subsequent tests are treated as edits, making for somewhat useful stress testing 3177 // of persistent typecheck state 3178 //compiler.addUnit("", uName, isResident, references); // equivalent to compiler.deleteUnit(...) 3179 script = compiler.addUnit(code, uName, isResident, references); 3180 needsFullTypeCheck = true; 3181 } 3182 return script; 3183 } 3184 Compiler.addUnit = addUnit; 3185 function updateUnit(code, unitName, setRecovery) { 3186 if (Harness.usePull) { 3187 compiler.pullUpdateUnit(new TypeScript.StringSourceText(code), unitName, setRecovery); 3188 } 3189 else { 3190 compiler.updateUnit(code, unitName, setRecovery); 3191 } 3192 } 3193 Compiler.updateUnit = updateUnit; 3194 function compileFile(path, callback, settingsCallback, context, references) { 3195 path = switchToForwardSlashes(path); 3196 var filename = path.match(/[^\/]*$/)[0]; 3197 var code = readFile(path); 3198 compileUnit(code, filename, callback, settingsCallback, context, references); 3199 } 3200 Compiler.compileFile = compileFile; 3201 function compileUnit(code, filename, callback, settingsCallback, context, references) { 3202 // not recursive 3203 function clone /* <T> */(source, target) { 3204 for (var prop in source) { 3205 target[prop] = source[prop]; 3206 } 3207 } 3208 var oldCompilerSettings = new TypeScript.CompilationSettings(); 3209 clone(compiler.settings, oldCompilerSettings); 3210 var oldEmitSettings = new TypeScript.EmitOptions(compiler.settings); 3211 clone(compiler.emitSettings, oldEmitSettings); 3212 var oldModuleGenTarget = TypeScript.moduleGenTarget; 3213 if (settingsCallback) { 3214 settingsCallback(compiler.settings); 3215 compiler.emitSettings = new TypeScript.EmitOptions(compiler.settings); 3216 } 3217 try { 3218 compileString(code, filename, callback, context, references); 3219 } 3220 finally { 3221 // If settingsCallback exists, assume that it modified the global compiler instance's settings in some way. 3222 // So that a test doesn't have side effects for tests run after it, restore the compiler settings to their previous state. 3223 if (settingsCallback) { 3224 compiler.settings = oldCompilerSettings; 3225 compiler.emitSettings = oldEmitSettings; 3226 TypeScript.moduleGenTarget = oldModuleGenTarget; 3227 } 3228 } 3229 } 3230 Compiler.compileUnit = compileUnit; 3231 function compileUnits(units, callback, settingsCallback) { 3232 var lastUnit = units[units.length - 1]; 3233 var unitName = switchToForwardSlashes(lastUnit.name).match(/[^\/]*$/)[0]; 3234 var dependencies = units.slice(0, units.length - 1); 3235 var compilationContext = Harness.Compiler.defineCompilationContextForTest(unitName, dependencies); 3236 compileUnit(lastUnit.content, unitName, callback, settingsCallback, compilationContext, lastUnit.references); 3237 } 3238 Compiler.compileUnits = compileUnits; 3239 function emitToOutfile(outfile) { 3240 compiler.emitToOutfile(outfile); 3241 } 3242 Compiler.emitToOutfile = emitToOutfile; 3243 function emit(ioHost, usePullEmitter) { 3244 compiler.emit(ioHost, usePullEmitter); 3245 } 3246 Compiler.emit = emit; 3247 function compileString(code, unitName, callback, context, references) { 3248 var scripts = []; 3249 reset(); 3250 if (context) { 3251 context.preCompile(); 3252 } 3253 var isDeclareFile = Harness.Compiler.isDeclareFile(unitName); 3254 // for single file tests just add them as using the old '0.ts' naming scheme 3255 var uName = context ? unitName : ((isDeclareFile) ? '0.d.ts' : '0.ts'); 3256 scripts.push(addUnit(code, uName, false, isDeclareFile, references)); 3257 compile(code, uName); 3258 var errors; 3259 if (Harness.usePull) { 3260 // TODO: no emit support with pull yet 3261 errors = compiler.pullGetErrorsForFile(uName); 3262 emit(stdout, true); 3263 } 3264 else { 3265 errors = stderr.lines; 3266 emit(stdout, false); 3267 //output decl file 3268 compiler.emitDeclarations(); 3269 } 3270 if (context) { 3271 context.postCompile(); 3272 } 3273 callback(new CompilerResult(stdout.toArray(), errors, scripts)); 3274 } 3275 Compiler.compileString = compileString; 3276 /** Returns a set of functions which can be later executed to add and remove given dependencies to the compiler so that 3277 * a file can be successfully compiled. These functions will add/remove named units and code to the compiler for each dependency. 3278 */ 3279 function defineCompilationContextForTest(filename, dependencies) { 3280 // if the given file has no dependencies, there is no context to return, it can be compiled without additional work 3281 if (dependencies.length == 0) { 3282 return null; 3283 } 3284 else { 3285 var addedFiles = []; 3286 var precompile = function () { 3287 // REVIEW: if any dependency has a triple slash reference then does postCompile potentially have to do a recreate since we can't update references with updateUnit? 3288 // easy enough to do if so, prefer to avoid the recreate cost until it proves to be an issue 3289 dependencies.forEach(function (dep) { 3290 addUnit(dep.content, dep.name, false, Harness.Compiler.isDeclareFile(dep.name)); 3291 addedFiles.push(dep.name); 3292 }); 3293 }; 3294 var postcompile = function () { 3295 addedFiles.forEach(function (file) { 3296 updateUnit('', file); 3297 }); 3298 }; 3299 var context = { 3300 filename: filename, 3301 preCompile: precompile, 3302 postCompile: postcompile 3303 }; 3304 return context; 3305 } 3306 } 3307 Compiler.defineCompilationContextForTest = defineCompilationContextForTest; 3308 })(Compiler = Harness.Compiler || (Harness.Compiler = {})); 3309 /** Parses the test cases files 3310 * extracts options and individual files in a multifile test 3311 */ 3312 var TestCaseParser; 3313 (function (TestCaseParser) { 3314 optionRegex = /^[\/]{2}\s*@(\w+):\s*(\S*)/gm; // multiple matches on multiple lines 3315 // List of allowed metadata names 3316 var fileMetadataNames = ["filename", "comments", "declaration", "module", "nolib", "sourcemap", "target", "out"]; 3317 function extractCompilerSettings(content) { 3318 var opts = []; 3319 var match; 3320 while ((match = optionRegex.exec(content)) != null) { 3321 opts.push({ flag: match[1], value: match[2] }); 3322 } 3323 return opts; 3324 } 3325 /** Given a test file containing // @Filename directives, return an array of named units of code to be added to an existing compiler instance */ 3326 function makeUnitsFromTest(code, filename) { 3327 var settings = extractCompilerSettings(code); 3328 // List of all the subfiles we've parsed out 3329 var files = []; 3330 var lines = splitContentByNewlines(code); 3331 // Stuff related to the subfile we're parsing 3332 var currentFileContent = null; 3333 var currentFileOptions = {}; 3334 var currentFileName = null; 3335 var refs = []; 3336 for (var i = 0; i < lines.length; i++) { 3337 var line = lines[i]; 3338 var isTripleSlashReference = /[\/]{3}\s*<reference path/.test(line); 3339 var testMetaData = optionRegex.exec(line); 3340 // Triple slash references need to be tracked as they are added to the compiler as an additional parameter to addUnit 3341 if (isTripleSlashReference) { 3342 var isRef = line.match(/reference\spath='(\w*_?\w*\.?d?\.ts)'/); 3343 if (isRef) { 3344 var ref = { 3345 minChar: 0, 3346 limChar: 0, 3347 startLine: 0, 3348 startCol: 0, 3349 path: isRef[1], 3350 isResident: false 3351 }; 3352 refs.push(ref); 3353 } 3354 } 3355 else if (testMetaData) { 3356 // Comment line, check for global/file @options and record them 3357 optionRegex.lastIndex = 0; 3358 var fileNameIndex = fileMetadataNames.indexOf(testMetaData[1].toLowerCase()); 3359 if (fileNameIndex == -1) { 3360 throw new Error('Unrecognized metadata name "' + testMetaData[1] + '". Available file metadata names are: ' + fileMetadataNames.join(', ')); 3361 } 3362 else if (fileNameIndex == 0) { 3363 currentFileOptions[testMetaData[1]] = testMetaData[2]; 3364 } 3365 else { 3366 continue; 3367 } 3368 // New metadata statement after having collected some code to go with the previous metadata 3369 if (currentFileName) { 3370 // Store result file 3371 var newTestFile = { 3372 content: currentFileContent, 3373 name: currentFileName, 3374 fileOptions: currentFileOptions, 3375 originalFilePath: filename, 3376 references: refs 3377 }; 3378 files.push(newTestFile); 3379 // Reset local data 3380 currentFileContent = null; 3381 currentFileOptions = {}; 3382 currentFileName = testMetaData[2]; 3383 refs = []; 3384 } 3385 else { 3386 // First metadata marker in the file 3387 currentFileName = testMetaData[2]; 3388 } 3389 } 3390 else { 3391 // Subfile content line 3392 // Append to the current subfile content, inserting a newline needed 3393 if (currentFileContent === null) { 3394 currentFileContent = ''; 3395 } 3396 else { 3397 // End-of-line 3398 currentFileContent = currentFileContent + '\n'; 3399 } 3400 currentFileContent = currentFileContent + line; 3401 } 3402 } 3403 // normalize the filename for the single file case 3404 currentFileName = files.length > 0 ? currentFileName : '0.ts'; 3405 // EOF, push whatever remains 3406 var newTestFile = { 3407 content: currentFileContent || '', 3408 name: currentFileName, 3409 fileOptions: currentFileOptions, 3410 originalFilePath: filename, 3411 references: refs 3412 }; 3413 files.push(newTestFile); 3414 return { settings: settings, testUnitData: files }; 3415 } 3416 TestCaseParser.makeUnitsFromTest = makeUnitsFromTest; 3417 })(TestCaseParser = Harness.TestCaseParser || (Harness.TestCaseParser = {})); 3418 var ScriptInfo = /** @class */ (function () { 3419 function ScriptInfo(name, content, isResident, maxScriptVersions) { 3420 this.name = name; 3421 this.content = content; 3422 this.isResident = isResident; 3423 this.maxScriptVersions = maxScriptVersions; 3424 this.editRanges = []; 3425 this.version = 1; 3426 } 3427 ScriptInfo.prototype.updateContent = function (content, isResident) { 3428 this.editRanges = []; 3429 this.content = content; 3430 this.isResident = isResident; 3431 this.version++; 3432 }; 3433 ScriptInfo.prototype.editContent = function (minChar, limChar, newText) { 3434 // Apply edits 3435 var prefix = this.content.substring(0, minChar); 3436 var middle = newText; 3437 var suffix = this.content.substring(limChar); 3438 this.content = prefix + middle + suffix; 3439 // Store edit range + new length of script 3440 this.editRanges.push({ 3441 length: this.content.length, 3442 editRange: new TypeScript.ScriptEditRange(minChar, limChar, (limChar - minChar) + newText.length) 3443 }); 3444 if (this.editRanges.length > this.maxScriptVersions) { 3445 this.editRanges.splice(0, this.maxScriptVersions - this.editRanges.length); 3446 } 3447 // Update version # 3448 this.version++; 3449 }; 3450 ScriptInfo.prototype.getEditRangeSinceVersion = function (version) { 3451 if (this.version == version) { 3452 // No edits! 3453 return null; 3454 } 3455 var initialEditRangeIndex = this.editRanges.length - (this.version - version); 3456 if (initialEditRangeIndex < 0 || initialEditRangeIndex >= this.editRanges.length) { 3457 // Too far away from what we know 3458 return TypeScript.ScriptEditRange.unknown(); 3459 } 3460 var entries = this.editRanges.slice(initialEditRangeIndex); 3461 var minDistFromStart = entries.map(function (x) { return x.editRange.minChar; }).reduce(function (prev, current) { return Math.min(prev, current); }); 3462 var minDistFromEnd = entries.map(function (x) { return x.length - x.editRange.limChar; }).reduce(function (prev, current) { return Math.min(prev, current); }); 3463 var aggDelta = entries.map(function (x) { return x.editRange.delta; }).reduce(function (prev, current) { return prev + current; }); 3464 return new TypeScript.ScriptEditRange(minDistFromStart, entries[0].length - minDistFromEnd, aggDelta); 3465 }; 3466 return ScriptInfo; 3467 }()); 3468 Harness.ScriptInfo = ScriptInfo; 3469 var TypeScriptLS = /** @class */ (function () { 3470 function TypeScriptLS() { 3471 this.ls = null; 3472 this.scripts = []; 3473 this.maxScriptVersions = 100; 3474 } 3475 TypeScriptLS.prototype.addDefaultLibrary = function () { 3476 this.addScript("lib.d.ts", Harness.Compiler.libText, true); 3477 }; 3478 TypeScriptLS.prototype.addFile = function (name, isResident) { 3479 if (isResident === void 0) { isResident = false; } 3480 var code = readFile(name); 3481 this.addScript(name, code, isResident); 3482 }; 3483 TypeScriptLS.prototype.addScript = function (name, content, isResident) { 3484 if (isResident === void 0) { isResident = false; } 3485 var script = new ScriptInfo(name, content, isResident, this.maxScriptVersions); 3486 this.scripts.push(script); 3487 }; 3488 TypeScriptLS.prototype.updateScript = function (name, content, isResident) { 3489 if (isResident === void 0) { isResident = false; } 3490 for (var i = 0; i < this.scripts.length; i++) { 3491 if (this.scripts[i].name == name) { 3492 this.scripts[i].updateContent(content, isResident); 3493 return; 3494 } 3495 } 3496 this.addScript(name, content, isResident); 3497 }; 3498 TypeScriptLS.prototype.editScript = function (name, minChar, limChar, newText) { 3499 for (var i = 0; i < this.scripts.length; i++) { 3500 if (this.scripts[i].name == name) { 3501 this.scripts[i].editContent(minChar, limChar, newText); 3502 return; 3503 } 3504 } 3505 throw new Error("No script with name '" + name + "'"); 3506 }; 3507 TypeScriptLS.prototype.getScriptContent = function (scriptIndex) { 3508 return this.scripts[scriptIndex].content; 3509 }; 3510 ////////////////////////////////////////////////////////////////////// 3511 // ILogger implementation 3512 // 3513 TypeScriptLS.prototype.information = function () { return false; }; 3514 TypeScriptLS.prototype.debug = function () { return true; }; 3515 TypeScriptLS.prototype.warning = function () { return true; }; 3516 TypeScriptLS.prototype.error = function () { return true; }; 3517 TypeScriptLS.prototype.fatal = function () { return true; }; 3518 TypeScriptLS.prototype.log = function (s) { 3519 // For debugging... 3520 //IO.printLine("TypeScriptLS:" + s); 3521 }; 3522 ////////////////////////////////////////////////////////////////////// 3523 // ILanguageServiceShimHost implementation 3524 // 3525 TypeScriptLS.prototype.getCompilationSettings = function () { 3526 return ""; // i.e. default settings 3527 }; 3528 TypeScriptLS.prototype.getScriptCount = function () { 3529 return this.scripts.length; 3530 }; 3531 TypeScriptLS.prototype.getScriptSourceText = function (scriptIndex, start, end) { 3532 return this.scripts[scriptIndex].content.substring(start, end); 3533 }; 3534 TypeScriptLS.prototype.getScriptSourceLength = function (scriptIndex) { 3535 return this.scripts[scriptIndex].content.length; 3536 }; 3537 TypeScriptLS.prototype.getScriptId = function (scriptIndex) { 3538 return this.scripts[scriptIndex].name; 3539 }; 3540 TypeScriptLS.prototype.getScriptIsResident = function (scriptIndex) { 3541 return this.scripts[scriptIndex].isResident; 3542 }; 3543 TypeScriptLS.prototype.getScriptVersion = function (scriptIndex) { 3544 return this.scripts[scriptIndex].version; 3545 }; 3546 TypeScriptLS.prototype.getScriptEditRangeSinceVersion = function (scriptIndex, scriptVersion) { 3547 var range = this.scripts[scriptIndex].getEditRangeSinceVersion(scriptVersion); 3548 var result = (range.minChar + "," + range.limChar + "," + range.delta); 3549 return result; 3550 }; 3551 /** Return a new instance of the language service shim, up-to-date wrt to typecheck. 3552 * To access the non-shim (i.e. actual) language service, use the "ls.languageService" property. 3553 */ 3554 TypeScriptLS.prototype.getLanguageService = function () { 3555 var ls = new Services.TypeScriptServicesFactory().createLanguageServiceShim(this); 3556 ls.refresh(true); 3557 this.ls = ls; 3558 return ls; 3559 }; 3560 /** Parse file given its source text */ 3561 TypeScriptLS.prototype.parseSourceText = function (fileName, sourceText) { 3562 var parser = new TypeScript.Parser(); 3563 parser.setErrorRecovery(null); 3564 parser.errorCallback = function (a, b, c, d) { }; 3565 var script = parser.parse(sourceText, fileName, 0); 3566 return script; 3567 }; 3568 /** Parse a file on disk given its filename */ 3569 TypeScriptLS.prototype.parseFile = function (fileName) { 3570 var sourceText = new TypeScript.StringSourceText(IO.readFile(fileName)); 3571 return this.parseSourceText(fileName, sourceText); 3572 }; 3573 /** 3574 * @param line 1 based index 3575 * @param col 1 based index 3576 */ 3577 TypeScriptLS.prototype.lineColToPosition = function (fileName, line, col) { 3578 var script = this.ls.languageService.getScriptAST(fileName); 3579 assert.notNull(script); 3580 assert.is(line >= 1); 3581 assert.is(col >= 1); 3582 assert.is(line <= script.locationInfo.lineMap.length); 3583 return TypeScript.getPositionFromZeroBasedLineColumn(script, line - 1, col - 1); 3584 }; 3585 /** 3586 * @param line 0 based index 3587 * @param col 0 based index 3588 */ 3589 TypeScriptLS.prototype.positionToZeroBasedLineCol = function (fileName, position) { 3590 var script = this.ls.languageService.getScriptAST(fileName); 3591 assert.notNull(script); 3592 var result = TypeScript.getZeroBasedLineColumnFromPosition(script, position); 3593 assert.is(result.line >= 0); 3594 assert.is(result.col >= 0); 3595 return result; 3596 }; 3597 /** Verify that applying edits to sourceFileName result in the content of the file baselineFileName */ 3598 TypeScriptLS.prototype.checkEdits = function (sourceFileName, baselineFileName, edits) { 3599 var script = readFile(sourceFileName); 3600 var formattedScript = this.applyEdits(script, edits); 3601 var baseline = readFile(baselineFileName); 3602 assert.noDiff(formattedScript, baseline); 3603 assert.equal(formattedScript, baseline); 3604 }; 3605 /** Apply an array of text edits to a string, and return the resulting string. */ 3606 TypeScriptLS.prototype.applyEdits = function (content, edits) { 3607 var result = content; 3608 edits = this.normalizeEdits(edits); 3609 for (var i = edits.length - 1; i >= 0; i--) { 3610 var edit = edits[i]; 3611 var prefix = result.substring(0, edit.minChar); 3612 var middle = edit.text; 3613 var suffix = result.substring(edit.limChar); 3614 result = prefix + middle + suffix; 3615 } 3616 return result; 3617 }; 3618 /** Normalize an array of edits by removing overlapping entries and sorting entries on the minChar position. */ 3619 TypeScriptLS.prototype.normalizeEdits = function (edits) { 3620 var result = []; 3621 function mapEdits(edits) { 3622 var result = []; 3623 for (var i = 0; i < edits.length; i++) { 3624 result.push({ edit: edits[i], index: i }); 3625 } 3626 return result; 3627 } 3628 var temp = mapEdits(edits).sort(function (a, b) { 3629 var result = a.edit.minChar - b.edit.minChar; 3630 if (result == 0) 3631 result = a.index - b.index; 3632 return result; 3633 }); 3634 var current = 0; 3635 var next = 1; 3636 while (current < temp.length) { 3637 var currentEdit = temp[current].edit; 3638 // Last edit 3639 if (next >= temp.length) { 3640 result.push(currentEdit); 3641 current++; 3642 continue; 3643 } 3644 var nextEdit = temp[next].edit; 3645 var gap = nextEdit.minChar - currentEdit.limChar; 3646 // non-overlapping edits 3647 if (gap >= 0) { 3648 result.push(currentEdit); 3649 current = next; 3650 next++; 3651 continue; 3652 } 3653 // overlapping edits: for now, we only support ignoring an next edit 3654 // entirely contained in the current edit. 3655 if (currentEdit.limChar >= nextEdit.limChar) { 3656 next++; 3657 continue; 3658 } 3659 else { 3660 throw new Error("Trying to apply overlapping edits"); 3661 } 3662 } 3663 return result; 3664 }; 3665 TypeScriptLS.prototype.getHostSettings = function () { 3666 return JSON.stringify({ usePullLanguageService: Harness.usePull }); 3667 }; 3668 return TypeScriptLS; 3669 }()); 3670 Harness.TypeScriptLS = TypeScriptLS; 3671 // Describe/it definitions 3672 function describe(description, block) { 3673 var newScenario = new Scenario(description, block); 3674 if (Runnable.currentStack.length === 0) { 3675 Runnable.currentStack.push(currentRun); 3676 } 3677 Runnable.currentStack[Runnable.currentStack.length - 1].addChild(newScenario); 3678 } 3679 Harness.describe = describe; 3680 function it(description, block) { 3681 var testCase = new TestCase(description, block); 3682 Runnable.currentStack[Runnable.currentStack.length - 1].addChild(testCase); 3683 } 3684 Harness.it = it; 3685 function run() { 3686 if (typeof process !== "undefined") { 3687 process.on('uncaughtException', Runnable.handleError); 3688 } 3689 Baseline.reset(); 3690 currentRun.run(); 3691 } 3692 Harness.run = run; 3693 /** Runs TypeScript or Javascript code. */ 3694 var Runner; 3695 (function (Runner) { 3696 function runCollateral(path, callback) { 3697 path = switchToForwardSlashes(path); 3698 runString(readFile(path), path.match(/[^\/]*$/)[0], callback); 3699 } 3700 Runner.runCollateral = runCollateral; 3701 function runJSString(code, callback) { 3702 // List of names that get overriden by various test code we eval 3703 var dangerNames = ['Array']; 3704 var globalBackup = {}; 3705 var n = null; 3706 for (n in dangerNames) { 3707 globalBackup[dangerNames[n]] = global[dangerNames[n]]; 3708 } 3709 try { 3710 var res = eval(code); 3711 for (n in dangerNames) { 3712 global[dangerNames[n]] = globalBackup[dangerNames[n]]; 3713 } 3714 callback(null, res); 3715 } 3716 catch (e) { 3717 for (n in dangerNames) { 3718 global[dangerNames[n]] = globalBackup[dangerNames[n]]; 3719 } 3720 callback(e, null); 3721 } 3722 } 3723 Runner.runJSString = runJSString; 3724 function runString(code, unitName, callback) { 3725 Compiler.compileString(code, unitName, function (res) { 3726 runJSString(res.code, callback); 3727 }); 3728 } 3729 Runner.runString = runString; 3730 })(Runner = Harness.Runner || (Harness.Runner = {})); 3731 /** Support class for baseline files */ 3732 var Baseline; 3733 (function (Baseline) { 3734 var reportFilename = 'baseline-report.html'; 3735 var firstRun = true; 3736 var htmlTrailer = '</body></html>'; 3737 var htmlLeader = '<html><head><title>Baseline Report</title>'; 3738 htmlLeader += ("<style>"); 3739 htmlLeader += '\r\n' + (".code { font: 9pt 'Courier New'; }"); 3740 htmlLeader += '\r\n' + (".old { background-color: #EE1111; }"); 3741 htmlLeader += '\r\n' + (".new { background-color: #FFFF11; }"); 3742 htmlLeader += '\r\n' + (".from { background-color: #EE1111; color: #1111EE; }"); 3743 htmlLeader += '\r\n' + (".to { background-color: #EEEE11; color: #1111EE; }"); 3744 htmlLeader += '\r\n' + ("h2 { margin-bottom: 0px; }"); 3745 htmlLeader += '\r\n' + ("h2 { padding-bottom: 0px; }"); 3746 htmlLeader += '\r\n' + ("h4 { font-weight: normal; }"); 3747 htmlLeader += '\r\n' + ("</style>"); 3748 function localPath(filename) { 3749 if (global.runners[0].testType === 'prototyping') { 3750 return Harness.userSpecifiedroot + 'tests/baselines/prototyping/local/' + filename; 3751 } 3752 else { 3753 return Harness.userSpecifiedroot + 'tests/baselines/local/' + filename; 3754 } 3755 } 3756 function referencePath(filename) { 3757 if (global.runners[0].testType === 'prototyping') { 3758 return Harness.userSpecifiedroot + 'tests/baselines/prototyping/reference/' + filename; 3759 } 3760 else { 3761 return Harness.userSpecifiedroot + 'tests/baselines/reference/' + filename; 3762 } 3763 } 3764 function reset() { 3765 if (IO.fileExists(reportFilename)) { 3766 IO.deleteFile(reportFilename); 3767 } 3768 } 3769 Baseline.reset = reset; 3770 function prepareBaselineReport() { 3771 var reportContent = htmlLeader; 3772 // Delete the baseline-report.html file if needed 3773 if (IO.fileExists(reportFilename)) { 3774 reportContent = IO.readFile(reportFilename); 3775 reportContent = reportContent.replace(htmlTrailer, ''); 3776 } 3777 else { 3778 reportContent = htmlLeader; 3779 } 3780 return reportContent; 3781 } 3782 function generateActual(actualFilename, generateContent) { 3783 // Create folders if needed 3784 IO.createDirectory(IO.dirName(IO.dirName(actualFilename))); 3785 IO.createDirectory(IO.dirName(actualFilename)); 3786 // Delete the actual file in case it fails 3787 if (IO.fileExists(actualFilename)) { 3788 IO.deleteFile(actualFilename); 3789 } 3790 var actual = generateContent(); 3791 if (actual === undefined) { 3792 throw new Error('The generated content was "undefined". Return "null" if no baselining is required."'); 3793 } 3794 // Store the content in the 'local' folder so we 3795 // can accept it later (manually) 3796 if (actual !== null) { 3797 IO.writeFile(actualFilename, actual); 3798 } 3799 return actual; 3800 } 3801 function compareToBaseline(actual, relativeFilename, opts) { 3802 // actual is now either undefined (the generator had an error), null (no file requested), 3803 // or some real output of the function 3804 if (actual === undefined) { 3805 // Nothing to do 3806 return; 3807 } 3808 var refFilename = referencePath(relativeFilename); 3809 if (actual === null) { 3810 actual = '<no content>'; 3811 } 3812 var expected = '<no content>'; 3813 if (IO.fileExists(refFilename)) { 3814 expected = IO.readFile(refFilename); 3815 } 3816 var lineEndingSensitive = opts && opts.LineEndingSensitive; 3817 if (!lineEndingSensitive) { 3818 expected = expected.replace(/\r\n?/g, '\n'); 3819 actual = actual.replace(/\r\n?/g, '\n'); 3820 } 3821 return { expected: expected, actual: actual }; 3822 } 3823 function writeComparison(expected, actual, relativeFilename, actualFilename, descriptionForDescribe) { 3824 if (expected != actual) { 3825 // Overwrite & issue error 3826 var errMsg = 'The baseline file ' + relativeFilename + ' has changed. Please refer to baseline-report.html and '; 3827 errMsg += 'either fix the regression (if unintended) or run nmake baseline-accept (if intended).'; 3828 var refFilename = referencePath(relativeFilename); 3829 // Append diff to the report 3830 var diff = new Diff.StringDiff(expected, actual); 3831 var header = '<h2>' + descriptionForDescribe + '</h2>'; 3832 header += '<h4>Left file: ' + actualFilename + '; Right file: ' + refFilename + '</h4>'; 3833 var trailer = '<hr>'; 3834 var reportContentSoFar = prepareBaselineReport(); 3835 reportContentSoFar = reportContentSoFar + header + '<div class="code">' + diff.mergedHtml + '</div>' + trailer + htmlTrailer; 3836 IO.writeFile(reportFilename, reportContentSoFar); 3837 throw new Error(errMsg); 3838 } 3839 } 3840 function runBaseline(descriptionForDescribe, relativeFilename, generateContent, runImmediately, opts) { 3841 if (runImmediately === void 0) { runImmediately = false; } 3842 var actual = undefined; 3843 var actualFilename = localPath(relativeFilename); 3844 if (runImmediately) { 3845 var actual = generateActual(actualFilename, generateContent); 3846 var comparison = compareToBaseline(actual, relativeFilename, opts); 3847 writeComparison(comparison.expected, comparison.actual, relativeFilename, actualFilename, descriptionForDescribe); 3848 } 3849 else { 3850 describe(descriptionForDescribe, function () { 3851 var actual; 3852 it('Can generate the content without error', function () { 3853 actual = generateActual(actualFilename, generateContent); 3854 }); 3855 it('Matches the baseline file', function () { 3856 var comparison = compareToBaseline(actual, relativeFilename, opts); 3857 writeComparison(comparison.expected, comparison.actual, relativeFilename, actualFilename, descriptionForDescribe); 3858 }); 3859 }); 3860 } 3861 } 3862 Baseline.runBaseline = runBaseline; 3863 })(Baseline = Harness.Baseline || (Harness.Baseline = {})); 3864 var currentRun = new Run(); 3865 global.describe = describe; 3866 global.run = run; 3867 global.it = it; 3868 global.assert = Harness.Assert; 3869})(Harness || (Harness = {})); 3870