Skip to content

Commit 4685646

Browse files
author
Kanchalai Tanglertsampan
committed
2 parents 9b7d8c7 + bef6a66 commit 4685646

File tree

71 files changed

+1829
-131
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1829
-131
lines changed

src/compiler/checker.ts

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
/* @internal */
44
namespace ts {
5+
const ambientModuleSymbolRegex = /^".+"$/;
6+
57
let nextSymbolId = 1;
68
let nextNodeId = 1;
79
let nextMergeId = 1;
@@ -100,6 +102,7 @@ namespace ts {
100102
getAliasedSymbol: resolveAlias,
101103
getEmitResolver,
102104
getExportsOfModule: getExportsOfModuleAsArray,
105+
getAmbientModules,
103106

104107
getJsxElementAttributesType,
105108
getJsxIntrinsicTagNames,
@@ -20195,5 +20198,15 @@ namespace ts {
2019520198
return true;
2019620199
}
2019720200
}
20201+
20202+
function getAmbientModules(): Symbol[] {
20203+
const result: Symbol[] = [];
20204+
for (const sym in globals) {
20205+
if (ambientModuleSymbolRegex.test(sym)) {
20206+
result.push(globals[sym]);
20207+
}
20208+
}
20209+
return result;
20210+
}
2019820211
}
2019920212
}

src/compiler/program.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace ts {
88

99
const emptyArray: any[] = [];
1010

11-
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string {
11+
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string {
1212
while (true) {
13-
const fileName = combinePaths(searchPath, "tsconfig.json");
13+
const fileName = combinePaths(searchPath, configName);
1414
if (fileExists(fileName)) {
1515
return fileName;
1616
}
@@ -166,7 +166,7 @@ namespace ts {
166166

167167
const typeReferenceExtensions = [".d.ts"];
168168

169-
function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost): string[] | undefined {
169+
export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined {
170170
if (options.typeRoots) {
171171
return options.typeRoots;
172172
}
@@ -186,7 +186,7 @@ namespace ts {
186186
* Returns the path to every node_modules/@types directory from some ancestor directory.
187187
* Returns undefined if there are none.
188188
*/
189-
function getDefaultTypeRoots(currentDirectory: string, host: ModuleResolutionHost): string[] | undefined {
189+
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
190190
if (!host.directoryExists) {
191191
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
192192
// And if it doesn't exist, tough.

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,7 @@ namespace ts {
19651965
getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
19661966
getJsxIntrinsicTagNames(): Symbol[];
19671967
isOptionalParameter(node: ParameterDeclaration): boolean;
1968+
getAmbientModules(): Symbol[];
19681969

19691970
// Should not be called directly. Should only be accessed through the Program instance.
19701971
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];

src/harness/fourslash.ts

+81-27
Original file line numberDiff line numberDiff line change
@@ -263,22 +263,31 @@ namespace FourSlash {
263263
constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) {
264264
// Create a new Services Adapter
265265
this.cancellationToken = new TestCancellationToken();
266-
const compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
267-
if (compilationOptions.typeRoots) {
268-
compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath));
269-
}
266+
let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
270267
compilationOptions.skipDefaultLibCheck = true;
271268

272-
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
273-
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
274-
this.languageService = languageServiceAdapter.getLanguageService();
275-
276269
// Initialize the language service with all the scripts
277270
let startResolveFileRef: FourSlashFile;
278271

279272
ts.forEach(testData.files, file => {
280273
// Create map between fileName and its content for easily looking up when resolveReference flag is specified
281274
this.inputFiles[file.fileName] = file.content;
275+
276+
if (ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json") {
277+
const configJson = ts.parseConfigFileTextToJson(file.fileName, file.content);
278+
assert.isTrue(configJson.config !== undefined);
279+
280+
// Extend our existing compiler options so that we can also support tsconfig only options
281+
if (configJson.config.compilerOptions) {
282+
const baseDirectory = ts.normalizePath(ts.getDirectoryPath(file.fileName));
283+
const tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDirectory, file.fileName);
284+
285+
if (!tsConfig.errors || !tsConfig.errors.length) {
286+
compilationOptions = ts.extend(compilationOptions, tsConfig.options);
287+
}
288+
}
289+
}
290+
282291
if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") {
283292
startResolveFileRef = file;
284293
}
@@ -288,6 +297,15 @@ namespace FourSlash {
288297
}
289298
});
290299

300+
301+
if (compilationOptions.typeRoots) {
302+
compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath));
303+
}
304+
305+
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
306+
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
307+
this.languageService = languageServiceAdapter.getLanguageService();
308+
291309
if (startResolveFileRef) {
292310
// Add the entry-point file itself into the languageServiceShimHost
293311
this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content, /*isRootFile*/ true);
@@ -342,6 +360,7 @@ namespace FourSlash {
342360
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
343361
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
344362
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
363+
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
345364
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
346365
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
347366
PlaceOpenBraceOnNewLineForFunctions: false,
@@ -730,10 +749,10 @@ namespace FourSlash {
730749
}
731750
}
732751

733-
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
752+
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
734753
const completions = this.getCompletionListAtCaret();
735754
if (completions) {
736-
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind);
755+
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex);
737756
}
738757
else {
739758
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
@@ -749,25 +768,32 @@ namespace FourSlash {
749768
* @param expectedText the text associated with the symbol
750769
* @param expectedDocumentation the documentation text associated with the symbol
751770
* @param expectedKind the kind of symbol (see ScriptElementKind)
771+
* @param spanIndex the index of the range that the completion item's replacement text span should match
752772
*/
753-
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string) {
773+
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
754774
const that = this;
775+
let replacementSpan: ts.TextSpan;
776+
if (spanIndex !== undefined) {
777+
replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex);
778+
}
779+
755780
function filterByTextOrDocumentation(entry: ts.CompletionEntry) {
756781
const details = that.getCompletionEntryDetails(entry.name);
757782
const documentation = ts.displayPartsToString(details.documentation);
758783
const text = ts.displayPartsToString(details.displayParts);
759-
if (expectedText && expectedDocumentation) {
760-
return (documentation === expectedDocumentation && text === expectedText) ? true : false;
784+
785+
// If any of the expected values are undefined, assume that users don't
786+
// care about them.
787+
if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) {
788+
return false;
761789
}
762-
else if (expectedText && !expectedDocumentation) {
763-
return text === expectedText ? true : false;
790+
else if (expectedText && text !== expectedText) {
791+
return false;
764792
}
765-
else if (expectedDocumentation && !expectedText) {
766-
return documentation === expectedDocumentation ? true : false;
793+
else if (expectedDocumentation && documentation !== expectedDocumentation) {
794+
return false;
767795
}
768-
// Because expectedText and expectedDocumentation are undefined, we assume that
769-
// users don"t care to compare them so we will treat that entry as if the entry has matching text and documentation
770-
// and keep it in the list of filtered entry.
796+
771797
return true;
772798
}
773799

@@ -791,6 +817,10 @@ namespace FourSlash {
791817
if (expectedKind) {
792818
error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions[0].kind + ".";
793819
}
820+
if (replacementSpan) {
821+
const spanText = filterCompletions[0].replacementSpan ? stringify(filterCompletions[0].replacementSpan) : undefined;
822+
error += "Expected replacement span: " + stringify(replacementSpan) + " to equal: " + spanText + ".";
823+
}
794824
this.raiseError(error);
795825
}
796826
}
@@ -2188,7 +2218,7 @@ namespace FourSlash {
21882218
return text.substring(startPos, endPos);
21892219
}
21902220

2191-
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string) {
2221+
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
21922222
for (let i = 0; i < items.length; i++) {
21932223
const item = items[i];
21942224
if (item.name === name) {
@@ -2207,6 +2237,11 @@ namespace FourSlash {
22072237
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name));
22082238
}
22092239

2240+
if (spanIndex !== undefined) {
2241+
const span = this.getTextSpanForRangeAtIndex(spanIndex);
2242+
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name));
2243+
}
2244+
22102245
return;
22112246
}
22122247
}
@@ -2263,6 +2298,17 @@ namespace FourSlash {
22632298
return `line ${(pos.line + 1)}, col ${pos.character}`;
22642299
}
22652300

2301+
private getTextSpanForRangeAtIndex(index: number): ts.TextSpan {
2302+
const ranges = this.getRanges();
2303+
if (ranges && ranges.length > index) {
2304+
const range = ranges[index];
2305+
return { start: range.start, length: range.end - range.start };
2306+
}
2307+
else {
2308+
this.raiseError("Supplied span index: " + index + " does not exist in range list of size: " + (ranges ? 0 : ranges.length));
2309+
}
2310+
}
2311+
22662312
public getMarkerByName(markerName: string) {
22672313
const markerPos = this.testData.markerPositions[markerName];
22682314
if (markerPos === undefined) {
@@ -2286,6 +2332,10 @@ namespace FourSlash {
22862332
public resetCancelled(): void {
22872333
this.cancellationToken.resetCancelled();
22882334
}
2335+
2336+
private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) {
2337+
return a && b && a.start === b.start && a.length === b.length;
2338+
}
22892339
}
22902340

22912341
export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
@@ -2294,12 +2344,16 @@ namespace FourSlash {
22942344
}
22952345

22962346
export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void {
2347+
// Give file paths an absolute path for the virtual file system
2348+
const absoluteBasePath = ts.combinePaths(Harness.virtualFileSystemRoot, basePath);
2349+
const absoluteFileName = ts.combinePaths(Harness.virtualFileSystemRoot, fileName);
2350+
22972351
// Parse out the files and their metadata
2298-
const testData = parseTestData(basePath, content, fileName);
2299-
const state = new TestState(basePath, testType, testData);
2352+
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
2353+
const state = new TestState(absoluteBasePath, testType, testData);
23002354
const output = ts.transpileModule(content, { reportDiagnostics: true });
23012355
if (output.diagnostics.length > 0) {
2302-
throw new Error(`Syntax error in ${basePath}: ${output.diagnostics[0].messageText}`);
2356+
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics[0].messageText}`);
23032357
}
23042358
runCode(output.outputText, state);
23052359
}
@@ -2852,12 +2906,12 @@ namespace FourSlashInterface {
28522906

28532907
// Verifies the completion list contains the specified symbol. The
28542908
// completion list is brought up if necessary
2855-
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
2909+
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
28562910
if (this.negative) {
2857-
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind);
2911+
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex);
28582912
}
28592913
else {
2860-
this.state.verifyCompletionListContains(symbol, text, documentation, kind);
2914+
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex);
28612915
}
28622916
}
28632917

src/harness/harness.ts

+3
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,9 @@ namespace Harness {
514514
// harness always uses one kind of new line
515515
const harnessNewLine = "\r\n";
516516

517+
// Root for file paths that are stored in a virtual file system
518+
export const virtualFileSystemRoot = "/";
519+
517520
namespace IOImpl {
518521
declare class Enumerator {
519522
public atEnd(): boolean;

0 commit comments

Comments
 (0)