merge with origin/master

This commit is contained in:
Vladimir Matveev
2016-09-27 10:22:41 -07:00
4908 changed files with 165378 additions and 93228 deletions
+2 -4
View File
@@ -136,9 +136,7 @@ class CompilerBaselineRunner extends RunnerBase {
// check errors
it("Correct errors for " + fileName, () => {
if (this.errors) {
Harness.Compiler.doErrorBaseline(justName, toBeCompiled.concat(otherFiles), result.errors);
}
Harness.Compiler.doErrorBaseline(justName, toBeCompiled.concat(otherFiles), result.errors);
});
it (`Correct module resolution tracing for ${fileName}`, () => {
@@ -154,7 +152,7 @@ class CompilerBaselineRunner extends RunnerBase {
if (options.sourceMap || options.inlineSourceMap) {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
const record = result.getSourceMapRecord();
if (options.noEmitOnError && result.errors.length !== 0 && record === undefined) {
if ((options.noEmitOnError && result.errors.length !== 0) || record === undefined) {
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
/* tslint:disable:no-null-keyword */
return null;
+274 -106
View File
@@ -88,6 +88,10 @@ namespace FourSlash {
marker?: Marker;
}
interface ImplementationLocationInformation extends ts.ImplementationLocation {
matched?: boolean;
}
export interface TextSpan {
start: number;
end: number;
@@ -347,23 +351,25 @@ namespace FourSlash {
}
this.formatCodeSettings = {
baseIndentSize: 0,
indentSize: 4,
tabSize: 4,
newLineCharacter: Harness.IO.newLine(),
convertTabsToSpaces: true,
indentStyle: ts.IndentStyle.Smart,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
BaseIndentSize: 0,
IndentSize: 4,
TabSize: 4,
NewLineCharacter: Harness.IO.newLine(),
ConvertTabsToSpaces: true,
IndentStyle: ts.IndentStyle.Smart,
InsertSpaceAfterCommaDelimiter: true,
InsertSpaceAfterSemicolonInForStatements: true,
InsertSpaceBeforeAndAfterBinaryOperators: true,
InsertSpaceAfterKeywordsInControlFlowStatements: true,
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
InsertSpaceAfterTypeAssertion: false,
PlaceOpenBraceOnNewLineForFunctions: false,
PlaceOpenBraceOnNewLineForControlBlocks: false,
};
// Open the first file by default
@@ -543,6 +549,67 @@ namespace FourSlash {
}
}
public verifyGoToDefinitionIs(endMarker: string | string[]) {
this.verifyGoToDefinitionWorker(endMarker instanceof Array ? endMarker : [endMarker]);
}
public verifyGoToDefinition(arg0: any, endMarkerNames?: string | string[]) {
if (endMarkerNames) {
this.verifyGoToDefinitionPlain(arg0, endMarkerNames);
}
else if (arg0 instanceof Array) {
const pairs: [string | string[], string | string[]][] = arg0;
for (const [start, end] of pairs) {
this.verifyGoToDefinitionPlain(start, end);
}
}
else {
const obj: { [startMarkerName: string]: string | string[] } = arg0;
for (const startMarkerName in obj) {
if (ts.hasProperty(obj, startMarkerName)) {
this.verifyGoToDefinitionPlain(startMarkerName, obj[startMarkerName]);
}
}
}
}
private verifyGoToDefinitionPlain(startMarkerNames: string | string[], endMarkerNames: string | string[]) {
if (startMarkerNames instanceof Array) {
for (const start of startMarkerNames) {
this.verifyGoToDefinitionSingle(start, endMarkerNames);
}
}
else {
this.verifyGoToDefinitionSingle(startMarkerNames, endMarkerNames);
}
}
public verifyGoToDefinitionForMarkers(markerNames: string[]) {
for (const markerName of markerNames) {
this.verifyGoToDefinitionSingle(`${markerName}Reference`, `${markerName}Definition`);
}
}
private verifyGoToDefinitionSingle(startMarkerName: string, endMarkerNames: string | string[]) {
this.goToMarker(startMarkerName);
this.verifyGoToDefinitionWorker(endMarkerNames instanceof Array ? endMarkerNames : [endMarkerNames]);
}
private verifyGoToDefinitionWorker(endMarkers: string[]) {
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition) || [];
if (endMarkers.length !== definitions.length) {
this.raiseError(`goToDefinitions failed - expected to find ${endMarkers.length} definitions but got ${definitions.length}`);
}
for (let i = 0; i < endMarkers.length; i++) {
const marker = this.getMarkerByName(endMarkers[i]), definition = definitions[i];
if (marker.fileName !== definition.fileName || marker.position !== definition.textSpan.start) {
this.raiseError(`goToDefinition failed for definition ${i}: expected ${marker.fileName} at ${marker.position}, got ${definition.fileName} at ${definition.textSpan.start}`);
}
}
}
public verifyGetEmitOutputForCurrentFile(expected: string): void {
const emit = this.languageService.getEmitOutput(this.activeFile.fileName);
if (emit.outputFiles.length !== 1) {
@@ -892,29 +959,35 @@ namespace FourSlash {
assert.equal(actual, expected);
}
public verifyQuickInfoString(negative: boolean, expectedText?: string, expectedDocumentation?: string) {
public verifyQuickInfoAt(markerName: string, expectedText: string, expectedDocumentation?: string) {
this.goToMarker(markerName);
this.verifyQuickInfoString(expectedText, expectedDocumentation);
}
public verifyQuickInfos(namesAndTexts: { [name: string]: string | [string, string] }) {
ts.forEachProperty(ts.createMap(namesAndTexts), (text, name) => {
if (text instanceof Array) {
assert(text.length === 2);
const [expectedText, expectedDocumentation] = text;
this.verifyQuickInfoAt(name, expectedText, expectedDocumentation);
}
else {
this.verifyQuickInfoAt(name, text);
}
});
}
public verifyQuickInfoString(expectedText: string, expectedDocumentation?: string) {
if (expectedDocumentation === "") {
throw new Error("Use 'undefined' instead");
}
const actualQuickInfo = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualQuickInfoText = actualQuickInfo ? ts.displayPartsToString(actualQuickInfo.displayParts) : "";
const actualQuickInfoDocumentation = actualQuickInfo ? ts.displayPartsToString(actualQuickInfo.documentation) : "";
if (negative) {
if (expectedText !== undefined) {
assert.notEqual(actualQuickInfoText, expectedText, this.messageAtLastKnownMarker("quick info text"));
}
// TODO: should be '==='?
if (expectedDocumentation != undefined) {
assert.notEqual(actualQuickInfoDocumentation, expectedDocumentation, this.messageAtLastKnownMarker("quick info doc comment"));
}
}
else {
if (expectedText !== undefined) {
assert.equal(actualQuickInfoText, expectedText, this.messageAtLastKnownMarker("quick info text"));
}
// TODO: should be '==='?
if (expectedDocumentation != undefined) {
assert.equal(actualQuickInfoDocumentation, expectedDocumentation, this.assertionMessageAtLastKnownMarker("quick info doc"));
}
}
assert.equal(actualQuickInfoText, expectedText, this.messageAtLastKnownMarker("quick info text"));
assert.equal(actualQuickInfoDocumentation, expectedDocumentation || "", this.assertionMessageAtLastKnownMarker("quick info doc"));
}
public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: { start: number; length: number; },
@@ -1232,6 +1305,22 @@ namespace FourSlash {
});
}
public baselineQuickInfo() {
let baselineFile = this.testData.globalOptions[metadataOptionNames.baselineFile];
if (!baselineFile) {
baselineFile = ts.getBaseFileName(this.activeFile.fileName).replace(".ts", ".baseline");
}
Harness.Baseline.runBaseline(
baselineFile,
() => stringify(
this.testData.markers.map(marker => ({
marker,
quickInfo: this.languageService.getQuickInfoAtPosition(marker.fileName, marker.position)
}))
));
}
public printBreakpointLocation(pos: number) {
Harness.IO.log("\n**Pos: " + pos + " " + this.spanInfoToString(pos, this.getBreakpointStatementLocation(pos), " "));
}
@@ -1590,25 +1679,10 @@ namespace FourSlash {
this.goToPosition(len);
}
public goToDefinition(definitionIndex: number) {
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!definitions || !definitions.length) {
this.raiseError("goToDefinition failed - expected to at least one definition location but got 0");
}
if (definitionIndex >= definitions.length) {
this.raiseError(`goToDefinition failed - definitionIndex value (${definitionIndex}) exceeds definition list size (${definitions.length})`);
}
const definition = definitions[definitionIndex];
this.openFile(definition.fileName);
this.currentCaretPosition = definition.textSpan.start;
}
public goToTypeDefinition(definitionIndex: number) {
const definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!definitions || !definitions.length) {
this.raiseError("goToTypeDefinition failed - expected to at least one definition location but got 0");
this.raiseError("goToTypeDefinition failed - expected to find at least one definition location but got 0");
}
if (definitionIndex >= definitions.length) {
@@ -1620,28 +1694,6 @@ namespace FourSlash {
this.currentCaretPosition = definition.textSpan.start;
}
public verifyDefinitionLocationExists(negative: boolean) {
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const foundDefinitions = definitions && definitions.length;
if (foundDefinitions && negative) {
this.raiseError(`goToDefinition - expected to 0 definition locations but got ${definitions.length}`);
}
else if (!foundDefinitions && !negative) {
this.raiseError("goToDefinition - expected to at least one definition location but got 0");
}
}
public verifyDefinitionsCount(negative: boolean, expectedCount: number) {
const assertFn = negative ? assert.notEqual : assert.equal;
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualCount = definitions && definitions.length || 0;
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Definitions Count"));
}
public verifyTypeDefinitionsCount(negative: boolean, expectedCount: number) {
const assertFn = negative ? assert.notEqual : assert.equal;
@@ -1651,17 +1703,98 @@ namespace FourSlash {
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count"));
}
public verifyDefinitionsName(negative: boolean, expectedName: string, expectedContainerName: string) {
public verifyImplementationListIsEmpty(negative: boolean) {
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (negative) {
assert.isTrue(implementations && implementations.length > 0, "Expected at least one implementation but got 0");
}
else {
assert.isUndefined(implementations, "Expected implementation list to be empty but implementations returned");
}
}
public verifyGoToDefinitionName(expectedName: string, expectedContainerName: string) {
const definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
const actualDefinitionName = definitions && definitions.length ? definitions[0].name : "";
const actualDefinitionContainerName = definitions && definitions.length ? definitions[0].containerName : "";
if (negative) {
assert.notEqual(actualDefinitionName, expectedName, this.messageAtLastKnownMarker("Definition Info Name"));
assert.notEqual(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name"));
assert.equal(actualDefinitionName, expectedName, this.messageAtLastKnownMarker("Definition Info Name"));
assert.equal(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name"));
}
public goToImplementation() {
const implementations = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!implementations || !implementations.length) {
this.raiseError("goToImplementation failed - expected to find at least one implementation location but got 0");
}
else {
assert.equal(actualDefinitionName, expectedName, this.messageAtLastKnownMarker("Definition Info Name"));
assert.equal(actualDefinitionContainerName, expectedContainerName, this.messageAtLastKnownMarker("Definition Info Container Name"));
if (implementations.length > 1) {
this.raiseError(`goToImplementation failed - more than 1 implementation returned (${implementations.length})`);
}
const implementation = implementations[0];
this.openFile(implementation.fileName);
this.currentCaretPosition = implementation.textSpan.start;
}
public verifyRangesInImplementationList(markerName: string) {
this.goToMarker(markerName);
const implementations: ImplementationLocationInformation[] = this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!implementations || !implementations.length) {
this.raiseError("verifyRangesInImplementationList failed - expected to find at least one implementation location but got 0");
}
for (let i = 0; i < implementations.length; i++) {
for (let j = 0; j < implementations.length; j++) {
if (i !== j && implementationsAreEqual(implementations[i], implementations[j])) {
const { textSpan, fileName } = implementations[i];
const end = textSpan.start + textSpan.length;
this.raiseError(`Duplicate implementations returned for range (${textSpan.start}, ${end}) in ${fileName}`);
}
}
}
const ranges = this.getRanges();
if (!ranges || !ranges.length) {
this.raiseError("verifyRangesInImplementationList failed - expected to find at least one range in test source");
}
const unsatisfiedRanges: Range[] = [];
for (const range of ranges) {
const length = range.end - range.start;
const matchingImpl = ts.find(implementations, impl =>
range.fileName === impl.fileName && range.start === impl.textSpan.start && length === impl.textSpan.length);
if (matchingImpl) {
matchingImpl.matched = true;
}
else {
unsatisfiedRanges.push(range);
}
}
const unmatchedImplementations = implementations.filter(impl => !impl.matched);
if (unmatchedImplementations.length || unsatisfiedRanges.length) {
let error = "Not all ranges or implementations are satisfied";
if (unsatisfiedRanges.length) {
error += "\nUnsatisfied ranges:";
for (const range of unsatisfiedRanges) {
error += `\n (${range.start}, ${range.end}) in ${range.fileName}: ${this.rangeText(range)}`;
}
}
if (unmatchedImplementations.length) {
error += "\nUnmatched implementations:";
for (const impl of unmatchedImplementations) {
const end = impl.textSpan.start + impl.textSpan.length;
error += `\n (${impl.textSpan.start}, ${end}) in ${impl.fileName}: ${this.getFileContent(impl.fileName).slice(impl.textSpan.start, end)}`;
}
}
this.raiseError(error);
}
function implementationsAreEqual(a: ImplementationLocationInformation, b: ImplementationLocationInformation) {
return a.fileName === b.fileName && TestState.textSpansEqual(a.textSpan, b.textSpan);
}
}
@@ -1670,6 +1803,10 @@ namespace FourSlash {
return this.testData.markers.slice(0);
}
public getMarkerNames(): string[] {
return Object.keys(this.testData.markerPositions);
}
public getRanges(): Range[] {
return this.testData.ranges;
}
@@ -1678,8 +1815,7 @@ namespace FourSlash {
const result = ts.createMap<Range[]>();
for (const range of this.getRanges()) {
const text = this.rangeText(range);
const ranges = result[text] || (result[text] = []);
ranges.push(range);
ts.multiMapAdd(result, text, range);
}
return result;
}
@@ -1991,11 +2127,12 @@ namespace FourSlash {
}
/*
Check number of navigationItems which match both searchValue and matchKind.
Check number of navigationItems which match both searchValue and matchKind,
if a filename is passed in, limit the results to that file.
Report an error if expected value and actual value do not match.
*/
public verifyNavigationItemsCount(expected: number, searchValue: string, matchKind?: string) {
const items = this.languageService.getNavigateToItems(searchValue);
public verifyNavigationItemsCount(expected: number, searchValue: string, matchKind?: string, fileName?: string) {
const items = this.languageService.getNavigateToItems(searchValue, /*maxResultCount*/ undefined, fileName);
let actual = 0;
let item: ts.NavigateToItem;
@@ -2794,6 +2931,10 @@ namespace FourSlashInterface {
return this.state.getMarkers();
}
public markerNames(): string[] {
return this.state.getMarkerNames();
}
public marker(name?: string): FourSlash.Marker {
return this.state.getMarkerByName(name);
}
@@ -2829,14 +2970,14 @@ namespace FourSlashInterface {
this.state.goToEOF();
}
public definition(definitionIndex = 0) {
this.state.goToDefinition(definitionIndex);
}
public type(definitionIndex = 0) {
this.state.goToTypeDefinition(definitionIndex);
}
public implementation() {
this.state.goToImplementation();
}
public position(position: number, fileIndex?: number): void;
public position(position: number, fileName?: string): void;
public position(position: number, fileNameOrIndex?: any): void {
@@ -2929,28 +3070,16 @@ namespace FourSlashInterface {
this.state.verifyErrorExistsAfterMarker(markerName, !this.negative, /*after*/ false);
}
public quickInfoIs(expectedText?: string, expectedDocumentation?: string) {
this.state.verifyQuickInfoString(this.negative, expectedText, expectedDocumentation);
}
public quickInfoExists() {
this.state.verifyQuickInfoExists(this.negative);
}
public definitionCountIs(expectedCount: number) {
this.state.verifyDefinitionsCount(this.negative, expectedCount);
}
public typeDefinitionCountIs(expectedCount: number) {
this.state.verifyTypeDefinitionsCount(this.negative, expectedCount);
}
public definitionLocationExists() {
this.state.verifyDefinitionLocationExists(this.negative);
}
public verifyDefinitionsName(name: string, containerName: string) {
this.state.verifyDefinitionsName(this.negative, name, containerName);
public implementationListIsEmpty() {
this.state.verifyImplementationListIsEmpty(this.negative);
}
public isValidBraceCompletionAtPosition(openingBrace: string) {
@@ -2963,6 +3092,18 @@ namespace FourSlashInterface {
super(state);
}
public quickInfoIs(expectedText: string, expectedDocumentation?: string) {
this.state.verifyQuickInfoString(expectedText, expectedDocumentation);
}
public quickInfoAt(markerName: string, expectedText?: string, expectedDocumentation?: string) {
this.state.verifyQuickInfoAt(markerName, expectedText, expectedDocumentation);
}
public quickInfos(namesAndTexts: { [name: string]: string }) {
this.state.verifyQuickInfos(namesAndTexts);
}
public caretAtMarker(markerName?: string) {
this.state.verifyCaretAtMarker(markerName);
}
@@ -2996,6 +3137,25 @@ namespace FourSlashInterface {
this.state.verifyCurrentFileContent(text);
}
public goToDefinitionIs(endMarkers: string | string[]) {
this.state.verifyGoToDefinitionIs(endMarkers);
}
public goToDefinition(startMarkerName: string | string[], endMarkerName: string | string[]): void;
public goToDefinition(startsAndEnds: [string | string[], string | string[]][]): void;
public goToDefinition(startsAndEnds: { [startMarkerName: string]: string | string[] }): void;
public goToDefinition(arg0: any, endMarkerName?: string | string[]) {
this.state.verifyGoToDefinition(arg0, endMarkerName);
}
public goToDefinitionForMarkers(...markerNames: string[]) {
this.state.verifyGoToDefinitionForMarkers(markerNames);
}
public goToDefinitionName(name: string, containerName: string) {
this.state.verifyGoToDefinitionName(name, containerName);
}
public verifyGetEmitOutputForCurrentFile(expected: string): void {
this.state.verifyGetEmitOutputForCurrentFile(expected);
}
@@ -3072,6 +3232,10 @@ namespace FourSlashInterface {
this.state.baselineGetEmitOutput();
}
public baselineQuickInfo() {
this.state.baselineQuickInfo();
}
public nameOrDottedNameSpanTextIs(text: string) {
this.state.verifyCurrentNameOrDottedNameSpanText(text);
}
@@ -3104,8 +3268,8 @@ namespace FourSlashInterface {
this.state.verifyNavigationBar(json);
}
public navigationItemsListCount(count: number, searchValue: string, matchKind?: string) {
this.state.verifyNavigationItemsCount(count, searchValue, matchKind);
public navigationItemsListCount(count: number, searchValue: string, matchKind?: string, fileName?: string) {
this.state.verifyNavigationItemsCount(count, searchValue, matchKind, fileName);
}
public navigationItemsListContains(
@@ -3186,6 +3350,10 @@ namespace FourSlashInterface {
public ProjectInfo(expected: string[]) {
this.state.verifyProjectInfo(expected);
}
public allRangesAppearInImplementationList(markerName: string) {
this.state.verifyRangesInImplementationList(markerName);
}
}
export class Edit {
+82 -9
View File
@@ -416,6 +416,60 @@ namespace Utils {
throw new Error("Could not find child in parent");
}
const maxHarnessFrames = 1;
export function filterStack(error: Error, stackTraceLimit: number = Infinity) {
const stack = <string>(<any>error).stack;
if (stack) {
const lines = stack.split(/\r\n?|\n/g);
const filtered: string[] = [];
let frameCount = 0;
let harnessFrameCount = 0;
for (let line of lines) {
if (isStackFrame(line)) {
if (frameCount >= stackTraceLimit
|| isMocha(line)
|| isNode(line)) {
continue;
}
if (isHarness(line)) {
if (harnessFrameCount >= maxHarnessFrames) {
continue;
}
harnessFrameCount++;
}
line = line.replace(/\bfile:\/\/\/(.*?)(?=(:\d+)*($|\)))/, (_, path) => ts.sys.resolvePath(path));
frameCount++;
}
filtered.push(line);
}
(<any>error).stack = filtered.join(Harness.IO.newLine());
}
return error;
}
function isStackFrame(line: string) {
return /^\s+at\s/.test(line);
}
function isMocha(line: string) {
return /[\\/](node_modules|components)[\\/]mocha(js)?[\\/]|[\\/]mocha\.js/.test(line);
}
function isNode(line: string) {
return /\((timers|events|node|module)\.js:/.test(line);
}
function isHarness(line: string) {
return /[\\/]src[\\/]harness[\\/]|[\\/]run\.js/.test(line);
}
}
namespace Harness.Path {
@@ -452,6 +506,8 @@ namespace Harness {
getExecutingFilePath(): string;
exit(exitCode?: number): void;
readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]): string[];
tryEnableSourceMapsForHost?(): void;
getEnvironmentVariable?(name: string): string;
}
export var IO: IO;
@@ -493,6 +549,7 @@ namespace Harness {
export const directoryExists: typeof IO.directoryExists = fso.FolderExists;
export const fileExists: typeof IO.fileExists = fso.FileExists;
export const log: typeof IO.log = global.WScript && global.WScript.StdOut.WriteLine;
export const getEnvironmentVariable: typeof IO.getEnvironmentVariable = name => ts.sys.getEnvironmentVariable(name);
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include) => ts.sys.readDirectory(path, extension, exclude, include);
export function createDirectory(path: string) {
@@ -562,7 +619,13 @@ namespace Harness {
export const writeFile: typeof IO.writeFile = (path, content) => ts.sys.writeFile(path, content);
export const fileExists: typeof IO.fileExists = fs.existsSync;
export const log: typeof IO.log = s => console.log(s);
export const getEnvironmentVariable: typeof IO.getEnvironmentVariable = name => ts.sys.getEnvironmentVariable(name);
export function tryEnableSourceMapsForHost() {
if (ts.sys.tryEnableSourceMapsForHost) {
ts.sys.tryEnableSourceMapsForHost();
}
}
export const readDirectory: typeof IO.readDirectory = (path, extension, exclude, include) => ts.sys.readDirectory(path, extension, exclude, include);
export function createDirectory(path: string) {
@@ -713,7 +776,16 @@ namespace Harness {
return dirPath;
}
export let directoryName: typeof IO.directoryName = Utils.memoize(directoryNameImpl);
export const resolvePath = (path: string) => directoryName(path);
export function resolvePath(path: string) {
const response = Http.getFileFromServerSync(serverRoot + path + "?resolve=true");
if (response.status === 200) {
return response.responseText;
}
else {
return undefined;
}
}
export function fileExists(path: string): boolean {
const response = Http.getFileFromServerSync(serverRoot + path);
@@ -787,7 +859,9 @@ namespace Harness {
namespace Harness {
export const libFolder = "built/local/";
const tcServicesFileName = ts.combinePaths(libFolder, Utils.getExecutionEnvironment() === Utils.ExecutionEnvironment.Browser ? "typescriptServicesInBrowserTest.js" : "typescriptServices.js");
export const tcServicesFile = IO.readFile(tcServicesFileName);
export const tcServicesFile = IO.readFile(tcServicesFileName) + (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser
? IO.newLine() + `//# sourceURL=${IO.resolvePath(tcServicesFileName)}`
: "");
export interface SourceMapEmitterCallback {
(emittedFile: string, emittedLine: number, emittedColumn: number, sourceFile: string, sourceLine: number, sourceColumn: number, sourceName: string): void;
@@ -1333,7 +1407,7 @@ namespace Harness {
export function doErrorBaseline(baselinePath: string, inputFiles: TestFile[], errors: ts.Diagnostic[]) {
Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?$/, ".errors.txt"), (): string => {
if (errors.length === 0) {
if (!errors || (errors.length === 0)) {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
@@ -1484,7 +1558,7 @@ namespace Harness {
}
Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ".js.map"), () => {
if (options.noEmitOnError && result.errors.length !== 0 && result.sourceMaps.length === 0) {
if ((options.noEmitOnError && result.errors.length !== 0) || result.sourceMaps.length === 0) {
// We need to return null here or the runBaseLine will actually create a empty file.
// Baselining isn't required here because there is no output.
/* tslint:disable:no-null-keyword */
@@ -1651,7 +1725,7 @@ namespace Harness {
}
public getSourceMapRecord() {
if (this.sourceMapData) {
if (this.sourceMapData && this.sourceMapData.length > 0) {
return Harness.SourceMapRecorder.getSourceMapRecord(this.sourceMapData, this.program, this.files);
}
}
@@ -1770,7 +1844,8 @@ namespace Harness {
const parseConfigHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: false,
readDirectory: (name) => [],
fileExists: (name) => true
fileExists: (name) => true,
readFile: (name) => ts.forEach(testUnitData, data => data.name.toLowerCase() === name.toLowerCase() ? data.content : undefined)
};
// check if project has tsconfig.json in the list of files
@@ -1788,7 +1863,7 @@ namespace Harness {
tsConfig.options.configFilePath = data.name;
// delete entry from the list
testUnitData.splice(i, 1);
ts.orderedRemoveItemAt(testUnitData, i);
break;
}
@@ -1908,9 +1983,7 @@ namespace Harness {
}
export function runBaseline(relativeFileName: string, generateContent: () => string, opts?: BaselineOptions): void {
const actualFileName = localPath(relativeFileName, opts && opts.Baselinefolder, opts && opts.Subfolder);
const actual = generateActual(generateContent);
const comparison = compareToBaseline(actual, relativeFileName, opts);
writeComparison(comparison.expected, comparison.actual, relativeFileName, actualFileName);
+13
View File
@@ -411,6 +411,9 @@ namespace Harness.LanguageService {
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
}
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): ts.Symbol {
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");
}
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo {
return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position));
}
@@ -435,6 +438,9 @@ namespace Harness.LanguageService {
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position));
}
getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] {
return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position));
}
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
}
@@ -489,6 +495,9 @@ namespace Harness.LanguageService {
getNonBoundSourceFile(fileName: string): ts.SourceFile {
throw new Error("SourceFile can not be marshaled across the shim layer.");
}
getSourceFile(fileName: string): ts.SourceFile {
throw new Error("SourceFile can not be marshaled across the shim layer.");
}
dispose(): void { this.shim.dispose({}); }
}
@@ -643,6 +652,10 @@ namespace Harness.LanguageService {
return [];
}
getEnvironmentVariable(name: string): string {
return ts.sys.getEnvironmentVariable(name);
}
readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]): string[] {
throw new Error("Not implemented Yet.");
}
+2
View File
@@ -1,6 +1,8 @@
/// <reference path="..\..\src\compiler\sys.ts" />
/// <reference path="..\..\src\harness\harness.ts" />
/// <reference path="..\..\src\harness\harnessLanguageService.ts" />
/// <reference path="..\..\src\harness\runnerbase.ts" />
/// <reference path="..\..\src\harness\typeWriter.ts" />
interface FileInformation {
contents: string;
+13 -11
View File
@@ -222,6 +222,7 @@ class ProjectRunner extends RunnerBase {
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
fileExists,
readDirectory,
readFile
};
const configParseResult = ts.parseJsonConfigFileContent(configObject, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions);
if (configParseResult.errors.length > 0) {
@@ -292,6 +293,10 @@ class ProjectRunner extends RunnerBase {
return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName));
}
function readFile(fileName: string): string {
return Harness.IO.readFile(getFileNameInTheProjectTest(fileName));
}
function getSourceFileText(fileName: string): string {
let text: string = undefined;
try {
@@ -473,7 +478,6 @@ class ProjectRunner extends RunnerBase {
}
});
it("Baseline of emitted result (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
if (testCase.baselineCheck) {
const errs: Error[] = [];
@@ -500,18 +504,16 @@ class ProjectRunner extends RunnerBase {
}
});
it("SourceMapRecord for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
if (compilerResult.sourceMapData) {
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => {
return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program,
ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName)));
});
}
});
// it("SourceMapRecord for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
// if (compilerResult.sourceMapData) {
// Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => {
// return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program,
// ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName)));
// });
// }
// });
// Verify that all the generated .d.ts files compile
it("Errors in generated Dts files for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
if (!compilerResult.errors.length && testCase.declaration) {
const dTsCompileResult = compileCompileDTsFiles(compilerResult);
+8
View File
@@ -82,6 +82,7 @@ interface TestConfig {
light?: boolean;
taskConfigsFolder?: string;
workerCount?: number;
stackTraceLimit?: number | "full";
tasks?: TaskSet[];
test?: string[];
runUnitTests?: boolean;
@@ -116,6 +117,13 @@ if (testConfigContent !== "") {
}
}
if (testConfig.stackTraceLimit === "full") {
(<any>Error).stackTraceLimit = Infinity;
}
else if ((+testConfig.stackTraceLimit | 0) > 0) {
(<any>Error).stackTraceLimit = testConfig.stackTraceLimit;
}
if (testConfig.test && testConfig.test.length > 0) {
for (const option of testConfig.test) {
if (!option) {
+1
View File
@@ -79,6 +79,7 @@ namespace RWC {
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
fileExists: Harness.IO.fileExists,
readDirectory: Harness.IO.readDirectory,
readFile: Harness.IO.readFile
};
const configParseResult = ts.parseJsonConfigFileContent(parsedTsconfigFileContents.config, configParseHost, ts.getDirectoryPath(tsconfigFile.path));
fileNames = configParseResult.fileNames;
+14
View File
@@ -22,6 +22,19 @@
"../compiler/utilities.ts",
"../compiler/binder.ts",
"../compiler/checker.ts",
"../compiler/factory.ts",
"../compiler/visitor.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/jsx.ts",
"../compiler/transformers/es7.ts",
"../compiler/transformers/es6.ts",
"../compiler/transformers/generators.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/module/module.ts",
"../compiler/transformers/module/system.ts",
"../compiler/transformers/module/es6.ts",
"../compiler/transformer.ts",
"../compiler/comments.ts",
"../compiler/sourcemap.ts",
"../compiler/declarationEmitter.ts",
"../compiler/emitter.ts",
@@ -86,6 +99,7 @@
"./unittests/moduleResolution.ts",
"./unittests/tsconfigParsing.ts",
"./unittests/commandLineParsing.ts",
"./unittests/configurationExtension.ts",
"./unittests/convertCompilerOptionsFromJson.ts",
"./unittests/convertTypingOptionsFromJson.ts",
"./unittests/tsserverProjectSystem.ts",
+1 -1
View File
@@ -29,7 +29,7 @@ class TypeWriterWalker {
}
private visitNode(node: ts.Node): void {
if (ts.isExpression(node) || node.kind === ts.SyntaxKind.Identifier) {
if (ts.isPartOfExpression(node) || node.kind === ts.SyntaxKind.Identifier) {
this.logTypeAndSymbol(node);
}
@@ -47,6 +47,7 @@ namespace ts {
return "";
},
getDirectories: (path: string) => [],
getEnvironmentVariable: (name: string) => "",
readDirectory: (path: string, extension?: string[], exclude?: string[], include?: string[]): string[] => {
throw new Error("NYI");
},
@@ -0,0 +1,187 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\virtualFileSystem.ts" />
namespace ts {
const testContents = {
"/dev/tsconfig.json": `{
"extends": "./configs/base",
"files": [
"main.ts",
"supplemental.ts"
]
}`,
"/dev/tsconfig.nostrictnull.json": `{
"extends": "./tsconfig",
"compilerOptions": {
"strictNullChecks": false
}
}`,
"/dev/configs/base.json": `{
"compilerOptions": {
"allowJs": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}`,
"/dev/configs/tests.json": `{
"compilerOptions": {
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true
},
"exclude": [
"../tests/baselines",
"../tests/scenarios"
],
"include": [
"../tests/**/*.ts"
]
}`,
"/dev/circular.json": `{
"extends": "./circular2",
"compilerOptions": {
"module": "amd"
}
}`,
"/dev/circular2.json": `{
"extends": "./circular",
"compilerOptions": {
"module": "commonjs"
}
}`,
"/dev/missing.json": `{
"extends": "./missing2",
"compilerOptions": {
"types": []
}
}`,
"/dev/failure.json": `{
"extends": "./failure2.json",
"compilerOptions": {
"typeRoots": []
}
}`,
"/dev/failure2.json": `{
"excludes": ["*.js"]
}`,
"/dev/configs/first.json": `{
"extends": "./base",
"compilerOptions": {
"module": "commonjs"
},
"files": ["../main.ts"]
}`,
"/dev/configs/second.json": `{
"extends": "./base",
"compilerOptions": {
"module": "amd"
},
"include": ["../supplemental.*"]
}`,
"/dev/extends.json": `{ "extends": 42 }`,
"/dev/extends2.json": `{ "extends": "configs/base" }`,
"/dev/main.ts": "",
"/dev/supplemental.ts": "",
"/dev/tests/unit/spec.ts": "",
"/dev/tests/utils.ts": "",
"/dev/tests/scenarios/first.json": "",
"/dev/tests/baselines/first/output.ts": ""
};
const caseInsensitiveBasePath = "c:/dev/";
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapObject(testContents, (key, content) => [`c:${key}`, content]));
const caseSensitiveBasePath = "/dev/";
const caseSensitiveHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, testContents);
function verifyDiagnostics(actual: Diagnostic[], expected: {code: number, category: DiagnosticCategory, messageText: string}[]) {
assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`);
for (let i = 0; i < actual.length; i++) {
const actualError = actual[i];
const expectedError = expected[i];
assert.equal(actualError.code, expectedError.code, "Error code mismatch");
assert.equal(actualError.category, expectedError.category, "Category mismatch");
assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText);
}
}
describe("Configuration Extension", () => {
forEach<[string, string, Utils.MockParseConfigHost], void>([
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
], ([testName, basePath, host]) => {
function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) {
it(name, () => {
const {config, error} = ts.readConfigFile(entry, name => host.readFile(name));
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry);
assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n"));
expected.configFilePath = entry;
assert.deepEqual(parsed.options, expected);
assert.deepEqual(parsed.fileNames, expectedFiles);
});
}
function testFailure(name: string, entry: string, expectedDiagnostics: {code: number, category: DiagnosticCategory, messageText: string}[]) {
it(name, () => {
const {config, error} = ts.readConfigFile(entry, name => host.readFile(name));
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry);
verifyDiagnostics(parsed.errors, expectedDiagnostics);
});
}
describe(testName, () => {
testSuccess("can resolve an extension with a base extension", "tsconfig.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true,
}, [
combinePaths(basePath, "main.ts"),
combinePaths(basePath, "supplemental.ts"),
]);
testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: false,
}, [
combinePaths(basePath, "main.ts"),
combinePaths(basePath, "supplemental.ts"),
]);
testFailure("can report errors on circular imports", "circular.json", [
{
code: 18000,
category: DiagnosticCategory.Error,
messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}`
}
]);
testFailure("can report missing configurations", "missing.json", [{
code: 6096,
category: DiagnosticCategory.Message,
messageText: `File './missing2' does not exist.`
}]);
testFailure("can report errors in extended configs", "failure.json", [{
code: 6114,
category: DiagnosticCategory.Error,
messageText: `Unknown option 'excludes'. Did you mean 'exclude'?`
}]);
testFailure("can error when 'extends' is not a string", "extends.json", [{
code: 5024,
category: DiagnosticCategory.Error,
messageText: `Compiler option 'extends' requires a value of type string.`
}]);
testFailure("can error when 'extends' is neither relative nor rooted.", "extends2.json", [{
code: 18001,
category: DiagnosticCategory.Error,
messageText: `The path in an 'extends' options must be relative or rooted.`
}]);
});
});
});
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -18,6 +18,7 @@ namespace ts.server {
createDirectory(): void {},
getExecutingFilePath(): string { return void 0; },
getCurrentDirectory(): string { return void 0; },
getEnvironmentVariable(name: string): string { return ""; },
readDirectory(): string[] { return []; },
exit(): void { },
setTimeout(callback, ms, ...args) { return 0; },
-1
View File
@@ -101,7 +101,6 @@ namespace ts {
});
});
if (canUseOldTranspile) {
it("Correct output (old transpile) for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), () => {
+2 -2
View File
@@ -151,7 +151,7 @@ namespace ts {
);
});
it("always exclude outDir", () => {
it("exclude outDir unless overridden", () => {
const tsconfigWithoutExclude =
`{
"compilerOptions": {
@@ -169,7 +169,7 @@ namespace ts {
const allFiles = ["/bin/a.ts", "/b.ts"];
const expectedFiles = ["/b.ts"];
assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles);
assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, expectedFiles);
assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles);
});
it("implicitly exclude common package folders", () => {
+5 -25
View File
@@ -419,22 +419,12 @@ namespace ts.projectSystem {
watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher {
const path = this.toPath(directoryName);
const callbacks = this.watchedDirectories[path] || (this.watchedDirectories[path] = []);
callbacks.push({ cb: callback, recursive });
const cbWithRecursive = { cb: callback, recursive };
multiMapAdd(this.watchedDirectories, path, cbWithRecursive);
return {
referenceCount: 0,
directoryName,
close: () => {
for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i].cb === callback) {
callbacks.splice(i, 1);
break;
}
}
if (!callbacks.length) {
delete this.watchedDirectories[path];
}
}
close: () => multiMapRemove(this.watchedDirectories, path, cbWithRecursive)
};
}
@@ -460,17 +450,8 @@ namespace ts.projectSystem {
watchFile(fileName: string, callback: FileWatcherCallback) {
const path = this.toPath(fileName);
const callbacks = this.watchedFiles[path] || (this.watchedFiles[path] = []);
callbacks.push(callback);
return {
close: () => {
const i = callbacks.indexOf(callback);
callbacks.splice(i, 1);
if (!callbacks.length) {
delete this.watchedFiles[path];
}
}
};
multiMapAdd(this.watchedFiles, path, callback);
return { close: () => multiMapRemove(this.watchedFiles, path, callback) };
}
// TOOD: record and invoke callbacks to simulate timer events
@@ -527,7 +508,6 @@ namespace ts.projectSystem {
readonly resolvePath = (s: string) => s;
readonly getExecutingFilePath = () => this.executingFilePath;
readonly getCurrentDirectory = () => this.currentDirectory;
readonly writeCompressedData = () => notImplemented();
readonly write = (s: string) => notImplemented();
readonly exit = () => notImplemented();
}
+16 -9
View File
@@ -10,9 +10,9 @@ namespace Utils {
this.name = name;
}
isDirectory() { return false; }
isFile() { return false; }
isFileSystem() { return false; }
isDirectory(): this is VirtualDirectory { return false; }
isFile(): this is VirtualFile { return false; }
isFileSystem(): this is VirtualFileSystem { return false; }
}
export class VirtualFile extends VirtualFileSystemEntry {
@@ -82,9 +82,8 @@ namespace Utils {
return file;
}
else if (entry.isFile()) {
const file = <VirtualFile>entry;
file.content = content;
return file;
entry.content = content;
return entry;
}
else {
return undefined;
@@ -196,10 +195,18 @@ namespace Utils {
}
export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost {
constructor(currentDirectory: string, ignoreCase: boolean, files: string[]) {
constructor(currentDirectory: string, ignoreCase: boolean, files: ts.MapLike<string> | string[]) {
super(currentDirectory, ignoreCase);
for (const file of files) {
this.addFile(file);
const fileNames = (files instanceof Array) ? files : ts.getOwnKeys(files);
for (const file of fileNames) {
this.addFile(file, new Harness.LanguageService.ScriptInfo(file, (files as ts.MapLike<string>)[file], /*isRootFile*/false));
}
}
readFile(path: string): string {
const value = this.traversePath(path);
if (value && value.isFile()) {
return value.content.content;
}
}