mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
merge with origin/master
This commit is contained in:
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.`
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
+192
-2268
File diff suppressed because it is too large
Load Diff
@@ -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; },
|
||||
|
||||
@@ -101,7 +101,6 @@ namespace ts {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (canUseOldTranspile) {
|
||||
it("Correct output (old transpile) for " + justName, () => {
|
||||
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), () => {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user