diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7b13027af4..da0b09cc78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2,6 +2,8 @@ /* @internal */ namespace ts { + const ambientModuleSymbolRegex = /^".+"$/; + let nextSymbolId = 1; let nextNodeId = 1; let nextMergeId = 1; @@ -100,6 +102,7 @@ namespace ts { getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, + getAmbientModules, getJsxElementAttributesType, getJsxIntrinsicTagNames, @@ -20195,5 +20198,15 @@ namespace ts { return true; } } + + function getAmbientModules(): Symbol[] { + const result: Symbol[] = []; + for (const sym in globals) { + if (ambientModuleSymbolRegex.test(sym)) { + result.push(globals[sym]); + } + } + return result; + } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index abfbddce1f..7c010285b7 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -8,9 +8,9 @@ namespace ts { const emptyArray: any[] = []; - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string { + export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string { while (true) { - const fileName = combinePaths(searchPath, "tsconfig.json"); + const fileName = combinePaths(searchPath, configName); if (fileExists(fileName)) { return fileName; } @@ -166,7 +166,7 @@ namespace ts { const typeReferenceExtensions = [".d.ts"]; - function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost): string[] | undefined { + export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined { if (options.typeRoots) { return options.typeRoots; } @@ -186,7 +186,7 @@ namespace ts { * Returns the path to every node_modules/@types directory from some ancestor directory. * Returns undefined if there are none. */ - function getDefaultTypeRoots(currentDirectory: string, host: ModuleResolutionHost): string[] | undefined { + function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined { if (!host.directoryExists) { return [combinePaths(currentDirectory, nodeModulesAtTypes)]; // And if it doesn't exist, tough. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 94aae9a2cd..f7b92b643c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1965,6 +1965,7 @@ namespace ts { getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type; getJsxIntrinsicTagNames(): Symbol[]; isOptionalParameter(node: ParameterDeclaration): boolean; + getAmbientModules(): Symbol[]; // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index d5c1e179c6..b4e40fbc44 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -263,22 +263,31 @@ namespace FourSlash { constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { // Create a new Services Adapter this.cancellationToken = new TestCancellationToken(); - const compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); - if (compilationOptions.typeRoots) { - compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); - } + let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); compilationOptions.skipDefaultLibCheck = true; - const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); - this.languageServiceAdapterHost = languageServiceAdapter.getHost(); - this.languageService = languageServiceAdapter.getLanguageService(); - // Initialize the language service with all the scripts let startResolveFileRef: FourSlashFile; ts.forEach(testData.files, file => { // Create map between fileName and its content for easily looking up when resolveReference flag is specified this.inputFiles[file.fileName] = file.content; + + if (ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json") { + const configJson = ts.parseConfigFileTextToJson(file.fileName, file.content); + assert.isTrue(configJson.config !== undefined); + + // Extend our existing compiler options so that we can also support tsconfig only options + if (configJson.config.compilerOptions) { + const baseDirectory = ts.normalizePath(ts.getDirectoryPath(file.fileName)); + const tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDirectory, file.fileName); + + if (!tsConfig.errors || !tsConfig.errors.length) { + compilationOptions = ts.extend(compilationOptions, tsConfig.options); + } + } + } + if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") { startResolveFileRef = file; } @@ -288,6 +297,15 @@ namespace FourSlash { } }); + + if (compilationOptions.typeRoots) { + compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); + } + + const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); + this.languageServiceAdapterHost = languageServiceAdapter.getHost(); + this.languageService = languageServiceAdapter.getLanguageService(); + if (startResolveFileRef) { // Add the entry-point file itself into the languageServiceShimHost this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content, /*isRootFile*/ true); @@ -342,6 +360,7 @@ namespace FourSlash { InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, PlaceOpenBraceOnNewLineForFunctions: false, @@ -730,10 +749,10 @@ namespace FourSlash { } } - public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string) { + public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { const completions = this.getCompletionListAtCaret(); if (completions) { - this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind); + this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex); } else { this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`); @@ -749,25 +768,32 @@ namespace FourSlash { * @param expectedText the text associated with the symbol * @param expectedDocumentation the documentation text associated with the symbol * @param expectedKind the kind of symbol (see ScriptElementKind) + * @param spanIndex the index of the range that the completion item's replacement text span should match */ - public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string) { + public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) { const that = this; + let replacementSpan: ts.TextSpan; + if (spanIndex !== undefined) { + replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex); + } + function filterByTextOrDocumentation(entry: ts.CompletionEntry) { const details = that.getCompletionEntryDetails(entry.name); const documentation = ts.displayPartsToString(details.documentation); const text = ts.displayPartsToString(details.displayParts); - if (expectedText && expectedDocumentation) { - return (documentation === expectedDocumentation && text === expectedText) ? true : false; + + // If any of the expected values are undefined, assume that users don't + // care about them. + if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) { + return false; } - else if (expectedText && !expectedDocumentation) { - return text === expectedText ? true : false; + else if (expectedText && text !== expectedText) { + return false; } - else if (expectedDocumentation && !expectedText) { - return documentation === expectedDocumentation ? true : false; + else if (expectedDocumentation && documentation !== expectedDocumentation) { + return false; } - // Because expectedText and expectedDocumentation are undefined, we assume that - // users don"t care to compare them so we will treat that entry as if the entry has matching text and documentation - // and keep it in the list of filtered entry. + return true; } @@ -791,6 +817,10 @@ namespace FourSlash { if (expectedKind) { error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions[0].kind + "."; } + if (replacementSpan) { + const spanText = filterCompletions[0].replacementSpan ? stringify(filterCompletions[0].replacementSpan) : undefined; + error += "Expected replacement span: " + stringify(replacementSpan) + " to equal: " + spanText + "."; + } this.raiseError(error); } } @@ -2188,7 +2218,7 @@ namespace FourSlash { return text.substring(startPos, endPos); } - private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string) { + private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.name === name) { @@ -2207,6 +2237,11 @@ namespace FourSlash { assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name)); } + if (spanIndex !== undefined) { + const span = this.getTextSpanForRangeAtIndex(spanIndex); + assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name)); + } + return; } } @@ -2263,6 +2298,17 @@ namespace FourSlash { return `line ${(pos.line + 1)}, col ${pos.character}`; } + private getTextSpanForRangeAtIndex(index: number): ts.TextSpan { + const ranges = this.getRanges(); + if (ranges && ranges.length > index) { + const range = ranges[index]; + return { start: range.start, length: range.end - range.start }; + } + else { + this.raiseError("Supplied span index: " + index + " does not exist in range list of size: " + (ranges ? 0 : ranges.length)); + } + } + public getMarkerByName(markerName: string) { const markerPos = this.testData.markerPositions[markerName]; if (markerPos === undefined) { @@ -2286,6 +2332,10 @@ namespace FourSlash { public resetCancelled(): void { this.cancellationToken.resetCancelled(); } + + private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) { + return a && b && a.start === b.start && a.length === b.length; + } } export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) { @@ -2294,12 +2344,16 @@ namespace FourSlash { } export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void { + // Give file paths an absolute path for the virtual file system + const absoluteBasePath = ts.combinePaths(Harness.virtualFileSystemRoot, basePath); + const absoluteFileName = ts.combinePaths(Harness.virtualFileSystemRoot, fileName); + // Parse out the files and their metadata - const testData = parseTestData(basePath, content, fileName); - const state = new TestState(basePath, testType, testData); + const testData = parseTestData(absoluteBasePath, content, absoluteFileName); + const state = new TestState(absoluteBasePath, testType, testData); const output = ts.transpileModule(content, { reportDiagnostics: true }); if (output.diagnostics.length > 0) { - throw new Error(`Syntax error in ${basePath}: ${output.diagnostics[0].messageText}`); + throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics[0].messageText}`); } runCode(output.outputText, state); } @@ -2852,12 +2906,12 @@ namespace FourSlashInterface { // Verifies the completion list contains the specified symbol. The // completion list is brought up if necessary - public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string) { + public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) { if (this.negative) { - this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind); + this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex); } else { - this.state.verifyCompletionListContains(symbol, text, documentation, kind); + this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex); } } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 8eac4f3c34..c5816deb92 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -514,6 +514,9 @@ namespace Harness { // harness always uses one kind of new line const harnessNewLine = "\r\n"; + // Root for file paths that are stored in a virtual file system + export const virtualFileSystemRoot = "/"; + namespace IOImpl { declare class Enumerator { public atEnd(): boolean; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 9421cbb3c8..31f8a37aa3 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript = ts.createMap(); + protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -135,22 +135,24 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEachProperty(this.fileNameToScript, (scriptInfo) => { + for (const virtualEntry of this.virtualFileSystem.getAllFileEntries()){ + const scriptInfo = virtualEntry.content; if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. fileNames.push(scriptInfo.fileName); } - }); + } return fileNames; } public getScriptInfo(fileName: string): ScriptInfo { - return this.fileNameToScript[fileName]; + const fileEntry = this.virtualFileSystem.traversePath(fileName); + return fileEntry && fileEntry.isFile() ? (fileEntry).content : undefined; } public addScript(fileName: string, content: string, isRootFile: boolean): void { - this.fileNameToScript[fileName] = new ScriptInfo(fileName, content, isRootFile); + this.virtualFileSystem.addFile(fileName, new ScriptInfo(fileName, content, isRootFile)); } public editScript(fileName: string, start: number, end: number, newText: string) { @@ -171,7 +173,7 @@ namespace Harness.LanguageService { * @param col 0 based index */ public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { - const script: ScriptInfo = this.fileNameToScript[fileName]; + const script: ScriptInfo = this.getScriptInfo(fileName); assert.isOk(script); return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); @@ -182,8 +184,14 @@ namespace Harness.LanguageService { class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost { getCompilationSettings() { return this.settings; } getCancellationToken() { return this.cancellationToken; } - getDirectories(path: string): string[] { return []; } - getCurrentDirectory(): string { return ""; } + getDirectories(path: string): string[] { + const dir = this.virtualFileSystem.traversePath(path); + if (dir && dir.isDirectory()) { + return ts.map((dir).getDirectories(), (d) => ts.combinePaths(path, d.name)); + } + return []; + } + getCurrentDirectory(): string { return virtualFileSystemRoot; } getDefaultLibFileName(): string { return Harness.Compiler.defaultLibFileName; } getScriptFileNames(): string[] { return this.getFilenames(); } getScriptSnapshot(fileName: string): ts.IScriptSnapshot { @@ -196,6 +204,22 @@ namespace Harness.LanguageService { return script ? script.version.toString() : undefined; } + fileExists(fileName: string): boolean { + const script = this.getScriptSnapshot(fileName); + return script !== undefined; + } + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + return ts.matchFiles(path, extensions, exclude, include, + /*useCaseSensitiveFileNames*/false, + this.getCurrentDirectory(), + (p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p)); + } + readFile(path: string, encoding?: string): string { + const snapshot = this.getScriptSnapshot(path); + return snapshot.getText(0, snapshot.getLength()); + } + + log(s: string): void { } trace(s: string): void { } error(s: string): void { } diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index 30192b8b8e..b6d234acd5 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -16,7 +16,7 @@ namespace Utils { } export class VirtualFile extends VirtualFileSystemEntry { - content: string; + content?: Harness.LanguageService.ScriptInfo; isFile() { return true; } } @@ -73,7 +73,7 @@ namespace Utils { } } - addFile(name: string, content?: string): VirtualFile { + addFile(name: string, content?: Harness.LanguageService.ScriptInfo): VirtualFile { const entry = this.getFileSystemEntry(name); if (entry === undefined) { const file = new VirtualFile(this.fileSystem, name); @@ -111,6 +111,7 @@ namespace Utils { getFileSystemEntries() { return this.root.getFileSystemEntries(); } addDirectory(path: string) { + path = ts.normalizePath(path); const components = ts.getNormalizedPathComponents(path, this.currentDirectory); let directory: VirtualDirectory = this.root; for (const component of components) { @@ -123,8 +124,8 @@ namespace Utils { return directory; } - addFile(path: string, content?: string) { - const absolutePath = ts.getNormalizedAbsolutePath(path, this.currentDirectory); + addFile(path: string, content?: Harness.LanguageService.ScriptInfo) { + const absolutePath = ts.normalizePath(ts.getNormalizedAbsolutePath(path, this.currentDirectory)); const fileName = ts.getBaseFileName(path); const directoryPath = ts.getDirectoryPath(absolutePath); const directory = this.addDirectory(directoryPath); @@ -141,6 +142,7 @@ namespace Utils { } traversePath(path: string) { + path = ts.normalizePath(path); let directory: VirtualDirectory = this.root; for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) { const entry = directory.getFileSystemEntry(component); @@ -157,6 +159,40 @@ namespace Utils { return directory; } + + /** + * Reads the directory at the given path and retrieves a list of file names and a list + * of directory names within it. Suitable for use with ts.matchFiles() + * @param path The path to the directory to be read + */ + getAccessibleFileSystemEntries(path: string) { + const entry = this.traversePath(path); + if (entry && entry.isDirectory()) { + const directory = entry; + return { + files: ts.map(directory.getFiles(), f => f.name), + directories: ts.map(directory.getDirectories(), d => d.name) + }; + } + return { files: [], directories: [] }; + } + + getAllFileEntries() { + const fileEntries: VirtualFile[] = []; + getFilesRecursive(this.root, fileEntries); + return fileEntries; + + function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) { + const files = dir.getFiles(); + const dirs = dir.getDirectories(); + for (const file of files) { + result.push(file); + } + for (const subDir of dirs) { + getFilesRecursive(subDir, result); + } + } + } } export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost { @@ -170,17 +206,5 @@ namespace Utils { readDirectory(path: string, extensions: string[], excludes: string[], includes: string[]) { return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, (path: string) => this.getAccessibleFileSystemEntries(path)); } - - getAccessibleFileSystemEntries(path: string) { - const entry = this.traversePath(path); - if (entry && entry.isDirectory()) { - const directory = entry; - return { - files: ts.map(directory.getFiles(), f => f.name), - directories: ts.map(directory.getDirectories(), d => d.name) - }; - } - return { files: [], directories: [] }; - } } } \ No newline at end of file diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index e4cbe323c6..a91ad4b126 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1060,6 +1060,12 @@ interface ReadonlyArray { * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ map(callbackfn: (value: T, index: number, array: ReadonlyArray) => U, thisArg?: any): U[]; + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: T, index: number, array: ReadonlyArray) => value is S, thisArg?: any): S[]; /** * Returns the elements of an array that meet the condition specified in a callback function. * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. diff --git a/src/server/client.ts b/src/server/client.ts index 88177e91fa..ca2d517701 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -216,7 +216,18 @@ namespace ts.server { return { isMemberCompletion: false, isNewIdentifierLocation: false, - entries: response.body + entries: response.body.map(entry => { + + if (entry.replacementSpan !== undefined) { + const { name, kind, kindModifiers, sortText, replacementSpan} = entry; + + const convertedSpan = createTextSpanFromBounds(this.lineOffsetToPosition(fileName, replacementSpan.start), + this.lineOffsetToPosition(fileName, replacementSpan.end)); + return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }; + } + + return entry as { name: string, kind: string, kindModifiers: string, sortText: string }; + }) }; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 8381f16fa8..e0f9e8d5cf 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -306,11 +306,6 @@ namespace ts.server { throw new Error("No script with name '" + filename + "'"); } - resolvePath(path: string): string { - const result = this.host.resolvePath(path); - return result; - } - fileExists(path: string): boolean { const result = this.host.fileExists(path); return result; @@ -324,6 +319,14 @@ namespace ts.server { return this.host.getDirectories(path); } + readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] { + return this.host.readDirectory(path, extensions, exclude, include); + } + + readFile(path: string, encoding?: string): string { + return this.host.readFile(path, encoding); + } + /** * @param line 1 based index */ @@ -1591,6 +1594,7 @@ namespace ts.server { InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, PlaceOpenBraceOnNewLineForFunctions: false, diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 6442848abb..2f71c3d5b7 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -773,6 +773,11 @@ declare namespace ts.server.protocol { * is often the same as the name but may be different in certain circumstances. */ sortText: string; + /** + * An optional span that indicates the text to be replaced by this completion item. If present, + * this span should be used instead of the default one. + */ + replacementSpan?: TextSpan; } /** diff --git a/src/server/session.ts b/src/server/session.ts index 00468330ff..9cb0fd03d8 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -778,7 +778,17 @@ namespace ts.server { return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => { if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) { - result.push(entry); + const { name, kind, kindModifiers, sortText, replacementSpan } = entry; + + let convertedSpan: protocol.TextSpan = undefined; + if (replacementSpan) { + convertedSpan = { + start: compilerService.host.positionToLineOffset(fileName, replacementSpan.start), + end: compilerService.host.positionToLineOffset(fileName, replacementSpan.start + replacementSpan.length) + }; + } + + result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan }); } return result; }, []).sort((a, b) => a.name.localeCompare(b.name)); diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 993ea02fb7..17ee9d435b 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -50,6 +50,8 @@ namespace ts.formatting { // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. public SpaceAfterOpenBrace: Rule; public SpaceBeforeCloseBrace: Rule; + public NoSpaceAfterOpenBrace: Rule; + public NoSpaceBeforeCloseBrace: Rule; public NoSpaceBetweenEmptyBraceBrackets: Rule; // Insert new line after { and before } in multi-line contexts. @@ -287,6 +289,8 @@ namespace ts.formatting { // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. this.SpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space)); this.SpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space)); + this.NoSpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Delete)); + this.NoSpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Delete)); this.NoSpaceBetweenEmptyBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsObjectContext), RuleAction.Delete)); // Insert new line after { and before } in multi-line contexts. @@ -414,7 +418,7 @@ namespace ts.formatting { this.SpaceAfterPostdecrementWhenFollowedBySubtract, this.SpaceAfterSubtractWhenFollowedByUnaryMinus, this.SpaceAfterSubtractWhenFollowedByPredecrement, this.NoSpaceAfterCloseBrace, - this.SpaceAfterOpenBrace, this.SpaceBeforeCloseBrace, this.NewLineBeforeCloseBraceInBlockContext, + this.NewLineBeforeCloseBraceInBlockContext, this.SpaceAfterCloseBrace, this.SpaceBetweenCloseBraceAndElse, this.SpaceBetweenCloseBraceAndWhile, this.NoSpaceBetweenEmptyBraceBrackets, this.NoSpaceBetweenFunctionKeywordAndStar, this.SpaceAfterStarInGeneratorDeclaration, this.SpaceAfterFunctionInFuncDecl, this.NewLineAfterOpenBraceInBlockContext, this.SpaceAfterGetSetInMember, diff --git a/src/services/formatting/rulesProvider.ts b/src/services/formatting/rulesProvider.ts index 1be0f9e912..b885accaf5 100644 --- a/src/services/formatting/rulesProvider.ts +++ b/src/services/formatting/rulesProvider.ts @@ -81,6 +81,19 @@ namespace ts.formatting { rules.push(this.globalRules.NoSpaceBetweenBrackets); } + // The default value of InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces is true + // so if the option is undefined, we should treat it as true as well + if (options.InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces !== false) { + rules.push(this.globalRules.SpaceAfterOpenBrace); + rules.push(this.globalRules.SpaceBeforeCloseBrace); + rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets); + } + else { + rules.push(this.globalRules.NoSpaceAfterOpenBrace); + rules.push(this.globalRules.NoSpaceBeforeCloseBrace); + rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets); + } + if (options.InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces) { rules.push(this.globalRules.SpaceAfterTemplateHeadAndMiddle); rules.push(this.globalRules.SpaceBeforeTemplateMiddleAndTail); diff --git a/src/services/services.ts b/src/services/services.ts index f503e4bbe2..eaba4779a5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1049,6 +1049,11 @@ namespace ts { owners: string[]; } + interface VisibleModuleInfo { + moduleName: string; + moduleDir: string; + } + export interface DisplayPartsSymbolWriter extends SymbolWriter { displayParts(): SymbolDisplayPart[]; } @@ -1245,7 +1250,15 @@ namespace ts { sourceMapText?: string; } + /** + * Matches a triple slash reference directive with an incomplete string literal for its path. Used + * to determine if the caret is currently within the string literal and capture the literal fragment + * for completions. + * For example, this matches /// node); + } else { const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { @@ -3697,6 +3725,446 @@ namespace ts { } } } + + function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral): CompletionInfo { + const literalValue = normalizeSlashes(node.text); + + const scriptPath = node.getSourceFile().path; + const scriptDirectory = getDirectoryPath(scriptPath); + + const span = getDirectoryFragmentTextSpan((node).text, node.getStart() + 1); + let entries: CompletionEntry[]; + if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { + const compilerOptions = program.getCompilerOptions(); + if (compilerOptions.rootDirs) { + entries = getCompletionEntriesForDirectoryFragmentWithRootDirs( + compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); + } + else { + entries = getCompletionEntriesForDirectoryFragment( + literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath); + } + } + else { + // Check for node modules + entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span); + } + return { + isMemberCompletion: false, + isNewIdentifierLocation: true, + entries + }; + } + + /** + * Takes a script path and returns paths for all potential folders that could be merged with its + * containing folder via the "rootDirs" compiler option + */ + function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean): string[] { + // Make all paths absolute/normalized if they are not already + rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); + + // Determine the path to the directory containing the script relative to the root directory it is contained within + let relativeDirectory: string; + for (const rootDirectory of rootDirs) { + if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) { + relativeDirectory = scriptPath.substr(rootDirectory.length); + break; + } + } + + // Now find a path for each potential directory that is to be merged with the one containing the script + return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory))); + } + + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string): CompletionEntry[] { + const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); + + const result: CompletionEntry[] = []; + + for (const baseDirectory of baseDirectories) { + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, exclude, result); + } + + return result; + } + + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string, result: CompletionEntry[] = []): CompletionEntry[] { + fragment = getDirectoryPath(fragment); + if (!fragment) { + fragment = "./"; + } + else { + fragment = ensureTrailingDirectorySeparator(fragment); + } + + const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); + const baseDirectory = getDirectoryPath(absolutePath); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + + if (directoryProbablyExists(baseDirectory, host)) { + if (host.readDirectory) { + // Enumerate the available files if possible + const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); + const foundFiles = createMap(); + for (let filePath of files) { + filePath = normalizePath(filePath); + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + continue; + } + + const foundFileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); + + if (!foundFiles[foundFileName]) { + foundFiles[foundFileName] = true; + } + } + + for (const foundFile in foundFiles) { + result.push(createCompletionEntryForModule(foundFile, ScriptElementKind.scriptElement, span)); + } + } + + // If possible, get folder completion as well + if (host.getDirectories) { + const directories = host.getDirectories(baseDirectory); + for (const directory of directories) { + const directoryName = getBaseFileName(normalizePath(directory)); + + result.push(createCompletionEntryForModule(directoryName, ScriptElementKind.directory, span)); + } + } + } + + return result; + } + + /** + * Check all of the declared modules and those in node modules. Possible sources of modules: + * Modules that are found by the type checker + * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) + * Modules from node_modules (i.e. those listed in package.json) + * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions + */ + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan): CompletionEntry[] { + const options = program.getCompilerOptions(); + const { baseUrl, paths } = options; + + let result: CompletionEntry[]; + + if (baseUrl) { + const fileExtensions = getSupportedExtensions(options); + const projectDir = options.project || host.getCurrentDirectory(); + const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); + result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false, span); + + if (paths) { + for (const path in paths) { + if (paths.hasOwnProperty(path)) { + if (path === "*") { + if (paths[path]) { + for (const pattern of paths[path]) { + for (const match of getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions)) { + result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName, span)); + } + } + } + } + else if (startsWith(path, fragment)) { + const entry = paths[path] && paths[path].length === 1 && paths[path][0]; + if (entry) { + result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName, span)); + } + } + } + } + } + } + else { + result = []; + } + + getCompletionEntriesFromTypings(host, options, scriptPath, span, result); + + for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, options)) { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + } + + return result; + } + + function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[]): string[] { + if (host.readDirectory) { + const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined; + if (parsed) { + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); + const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = getBaseFileName(normalizedPrefix); + + const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; + + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + + const normalizedSuffix = normalizePath(parsed.suffix); + const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + + // If we have a suffix, then we need to read the directory all the way down. We could create a glob + // that encodes the suffix, but we would have to escape the character "?" which readDirectory + // doesn't support. For now, this is safer but slower + const includeGlob = normalizedSuffix ? "**/*" : "./*"; + + const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]); + const result: string[] = []; + + // Trim away prefix and suffix + for (const match of matches) { + const normalizedMatch = normalizePath(match); + if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) { + continue; + } + + const start = completePrefix.length; + const length = normalizedMatch.length - start - normalizedSuffix.length; + + result.push(removeFileExtension(normalizedMatch.substr(start, length))); + } + return result; + } + } + + return undefined; + } + + function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions): string[] { + // Check If this is a nested module + const isNestedModule = fragment.indexOf(directorySeparator) !== -1; + const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined; + + // Get modules that the type checker picked up + const ambientModules = map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); + let nonRelativeModules = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); + + // Nested modules of the form "module-name/sub" need to be adjusted to only return the string + // after the last '/' that appears in the fragment because that's where the replacement span + // starts + if (isNestedModule) { + const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment); + nonRelativeModules = map(nonRelativeModules, moduleName => { + if (startsWith(fragment, moduleNameWithSeperator)) { + return moduleName.substr(moduleNameWithSeperator.length); + } + return moduleName; + }); + } + + + if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { + for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) { + if (!isNestedModule) { + nonRelativeModules.push(visibleModule.moduleName); + } + else if (host.readDirectory && startsWith(visibleModule.moduleName, moduleNameFragment)) { + const nestedFiles = host.readDirectory(visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/undefined, /*include*/["./*"]); + + for (let f of nestedFiles) { + f = normalizePath(f); + const nestedModule = removeFileExtension(getBaseFileName(f)); + nonRelativeModules.push(nestedModule); + } + } + } + } + + return deduplicate(nonRelativeModules); + } + + function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number): CompletionInfo { + const token = getTokenAtPosition(sourceFile, position); + if (!token) { + return undefined; + } + const commentRanges: CommentRange[] = getLeadingCommentRanges(sourceFile.text, token.pos); + + if (!commentRanges || !commentRanges.length) { + return undefined; + } + + const range = forEach(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end && commentRange); + + if (!range) { + return undefined; + } + + const text = sourceFile.text.substr(range.pos, position - range.pos); + + const match = tripleSlashDirectiveFragmentRegex.exec(text); + if (match) { + const prefix = match[1]; + const kind = match[2]; + const toComplete = match[3]; + + const scriptPath = getDirectoryPath(sourceFile.path); + let entries: CompletionEntry[]; + if (kind === "path") { + // Give completions for a relative path + const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); + entries = getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span, sourceFile.path); + } + else { + // Give completions based on the typings available + const span: TextSpan = { start: range.pos + prefix.length, length: match[0].length - prefix.length }; + entries = getCompletionEntriesFromTypings(host, program.getCompilerOptions(), scriptPath, span); + } + + return { + isMemberCompletion: false, + isNewIdentifierLocation: true, + entries + }; + } + + return undefined; + } + + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { + // Check for typings specified in compiler options + if (options.types) { + for (const moduleName of options.types) { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + } + } + else if (host.getDirectories) { + const typeRoots = getEffectiveTypeRoots(options, host); + for (const root of typeRoots) { + getCompletionEntriesFromDirectories(host, options, root, span, result); + } + } + + if (host.getDirectories) { + // Also get all @types typings installed in visible node_modules directories + for (const package of findPackageJsons(scriptPath)) { + const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types"); + getCompletionEntriesFromDirectories(host, options, typesDir, span, result); + } + } + + return result; + } + + function getCompletionEntriesFromDirectories(host: LanguageServiceHost, options: CompilerOptions, directory: string, span: TextSpan, result: CompletionEntry[]) { + if (host.getDirectories && directoryProbablyExists(directory, host)) { + for (let typeDirectory of host.getDirectories(directory)) { + typeDirectory = normalizePath(typeDirectory); + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + } + } + } + + function findPackageJsons(currentDir: string): string[] { + const paths: string[] = []; + let currentConfigPath: string; + while (true) { + currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json"); + if (currentConfigPath) { + paths.push(currentConfigPath); + + currentDir = getDirectoryPath(currentConfigPath); + const parent = getDirectoryPath(currentDir); + if (currentDir === parent) { + break; + } + currentDir = parent; + } + else { + break; + } + } + + return paths; + } + + + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { + const result: VisibleModuleInfo[] = []; + + if (host.readFile && host.fileExists) { + for (const packageJson of findPackageJsons(scriptPath)) { + const package = tryReadingPackageJson(packageJson); + if (!package) { + return; + } + + const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules"); + const foundModuleNames: string[] = []; + + // Provide completions for all non @types dependencies + for (const key of nodeModulesDependencyKeys) { + addPotentialPackageNames(package[key], foundModuleNames); + } + + for (const moduleName of foundModuleNames) { + const moduleDir = combinePaths(nodeModulesDir, moduleName); + result.push({ + moduleName, + moduleDir + }); + } + } + } + + return result; + + function tryReadingPackageJson(filePath: string) { + try { + const fileText = host.readFile(filePath); + return JSON.parse(fileText); + } + catch (e) { + return undefined; + } + } + + function addPotentialPackageNames(dependencies: any, result: string[]) { + if (dependencies) { + for (const dep in dependencies) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types/")) { + result.push(dep); + } + } + } + } + } + + function createCompletionEntryForModule(name: string, kind: string, replacementSpan: TextSpan): CompletionEntry { + return { name, kind, kindModifiers: ScriptElementKindModifier.none, sortText: name, replacementSpan }; + } + + // Replace everything after the last directory seperator that appears + function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { + const index = text.lastIndexOf(directorySeparator); + const offset = index !== -1 ? index + 1 : 0; + return { start: textStart + offset, length: text.length - offset }; + } + + // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) + function isPathRelativeToScript(path: string) { + if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { + const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; + const slashCharCode = path.charCodeAt(slashIndex); + return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; + } + return false; + } + + function normalizeAndPreserveTrailingSlash(path: string) { + return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalizePath(path)) : normalizePath(path); + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { @@ -5627,7 +6095,6 @@ namespace ts { symbolToIndex: number[]): void { const sourceFile = container.getSourceFile(); - const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*; -//# sourceMappingURL=inputFile2.jsx.mapFileName : tests/cases/fourslash/inputFile2.d.ts +//# sourceMappingURL=inputFile2.jsx.mapFileName : /tests/cases/fourslash/inputFile2.d.ts declare var y: string; declare var x: any; diff --git a/tests/baselines/reference/getEmitOutputTsxFile_React.baseline b/tests/baselines/reference/getEmitOutputTsxFile_React.baseline index 44c20f1d83..b139e9c1ca 100644 --- a/tests/baselines/reference/getEmitOutputTsxFile_React.baseline +++ b/tests/baselines/reference/getEmitOutputTsxFile_React.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js.map -{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAA;IAGA,CAAC;IAAD,UAAC;AAAD,CAAC,AAHD,IAGC"}FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js.map +{"version":3,"file":"inputFile1.js","sourceRoot":"","sources":["inputFile1.ts"],"names":[],"mappings":"AAAA,kBAAkB;AACjB,IAAI,CAAC,GAAW,CAAC,CAAC;AAClB;IAAA;IAGA,CAAC;IAAD,UAAC;AAAD,CAAC,AAHD,IAGC"}FileName : /tests/cases/fourslash/inputFile1.js // regular ts file var t = 5; var Bar = (function () { @@ -8,7 +8,7 @@ var Bar = (function () { } return Bar; }()); -//# sourceMappingURL=inputFile1.js.mapFileName : tests/cases/fourslash/inputFile1.d.ts +//# sourceMappingURL=inputFile1.js.mapFileName : /tests/cases/fourslash/inputFile1.d.ts declare var t: number; declare class Bar { x: string; @@ -16,11 +16,11 @@ declare class Bar { } EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js.map -{"version":3,"file":"inputFile2.js","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AACA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,6BAAK,IAAI,EAAG,CAAC,GAAI,CAAA"}FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js.map +{"version":3,"file":"inputFile2.js","sourceRoot":"","sources":["inputFile2.tsx"],"names":[],"mappings":"AACA,IAAI,CAAC,GAAG,QAAQ,CAAC;AACjB,IAAI,CAAC,GAAG,6BAAK,IAAI,EAAG,CAAC,GAAI,CAAA"}FileName : /tests/cases/fourslash/inputFile2.js var y = "my div"; var x = React.createElement("div", { name: y }); -//# sourceMappingURL=inputFile2.js.mapFileName : tests/cases/fourslash/inputFile2.d.ts +//# sourceMappingURL=inputFile2.js.mapFileName : /tests/cases/fourslash/inputFile2.d.ts declare var React: any; declare var y: string; declare var x: any; diff --git a/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline b/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline index af4ccd1058..a10ef5967b 100644 --- a/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline +++ b/tests/baselines/reference/getEmitOutputWithDeclarationFile.baseline @@ -1,7 +1,7 @@ EmitSkipped: false EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js var x1 = "hello world"; var Foo = (function () { function Foo() { diff --git a/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline b/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline index 8e43219e00..5c86532169 100644 --- a/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline +++ b/tests/baselines/reference/getEmitOutputWithDeclarationFile2.baseline @@ -1,7 +1,7 @@ EmitSkipped: false EmitSkipped: false -FileName : tests/cases/fourslash/inputFile2.js +FileName : /tests/cases/fourslash/inputFile2.js "use strict"; var Foo = (function () { function Foo() { @@ -11,6 +11,6 @@ var Foo = (function () { exports.Foo = Foo; EmitSkipped: false -FileName : tests/cases/fourslash/inputFile3.js +FileName : /tests/cases/fourslash/inputFile3.js var x = "hello"; diff --git a/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline b/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline index 9e2e9978f4..6609554a29 100644 --- a/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithEarlySyntacticErrors.baseline @@ -1,5 +1,5 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js // File contains early errors. All outputs should be skipped. var uninitialized_const_error; diff --git a/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline b/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline index 8ba493e04b..326d547ae1 100644 --- a/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithEmitterErrors.baseline @@ -1,7 +1,7 @@ EmitSkipped: true Diagnostics: Exported variable 'foo' has or is using private name 'C'. -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var M; (function (M) { var C = (function () { diff --git a/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline b/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline index bf12bd3554..a181526eb2 100644 --- a/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline +++ b/tests/baselines/reference/getEmitOutputWithEmitterErrors2.baseline @@ -1,7 +1,7 @@ EmitSkipped: true Diagnostics: Exported variable 'foo' has or is using private name 'C'. -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js define(["require", "exports"], function (require, exports) { "use strict"; var C = (function () { diff --git a/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline b/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline index b624982213..55a58ea041 100644 --- a/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithSemanticErrors.baseline @@ -1,4 +1,4 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x = "hello world"; diff --git a/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline b/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline index 396e220bdb..b5848da345 100644 --- a/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline +++ b/tests/baselines/reference/getEmitOutputWithSemanticErrors2.baseline @@ -1,6 +1,6 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x = "hello world"; -FileName : tests/cases/fourslash/inputFile.d.ts +FileName : /tests/cases/fourslash/inputFile.d.ts declare var x: number; diff --git a/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline b/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline index 7f012b6a54..4c22a7e3f9 100644 --- a/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline +++ b/tests/baselines/reference/getEmitOutputWithSemanticErrorsForMultipleFiles.baseline @@ -1,8 +1,8 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js // File to emit, does not contain semantic errors // expected to be emitted correctelly regardless of the semantic errors in the other file var noErrors = true; -FileName : tests/cases/fourslash/inputFile1.d.ts +FileName : /tests/cases/fourslash/inputFile1.d.ts declare var noErrors: boolean; diff --git a/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline b/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline index 701b275eb3..f51782cc75 100644 --- a/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline +++ b/tests/baselines/reference/getEmitOutputWithSyntacticErrorsForMultipleFiles.baseline @@ -1,5 +1,5 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile1.js +FileName : /tests/cases/fourslash/inputFile1.js // File to emit, does not contain syntactic errors // expected to be emitted correctelly regardless of the syntactic errors in the other file var noErrors = true; diff --git a/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline b/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline index 2c341821fb..8aa81ba60c 100644 --- a/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline +++ b/tests/baselines/reference/getEmitOutputWithSyntaxErrors.baseline @@ -1,4 +1,4 @@ EmitSkipped: false -FileName : tests/cases/fourslash/inputFile.js +FileName : /tests/cases/fourslash/inputFile.js var x; diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts new file mode 100644 index 0000000000..4cdee88953 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -0,0 +1,33 @@ +/// + +// Should define spans for replacement that appear after the last directory seperator in import statements + +// @typeRoots: my_typings + +// @Filename: test.ts +//// import * as foo0 from "./[|some|]/*0*/ +//// import * as foo1 from "./sub/[|some|]/*1*/ +//// import * as foo2 from "[|some-|]/*2*/" +//// import * as foo3 from "../[||]/*3*/"; + + +// @Filename: someFile1.ts +//// /*someFile1*/ + +// @Filename: sub/someFile2.ts +//// /*someFile2*/ + +// @Filename: my_typings/some-module/index.d.ts +//// export var x = 9; + +goTo.marker("0"); +verify.completionListContains("someFile1", undefined, undefined, undefined, 0); + +goTo.marker("1"); +verify.completionListContains("someFile2", undefined, undefined, undefined, 1); + +goTo.marker("2"); +verify.completionListContains("some-module", undefined, undefined, undefined, 2); + +goTo.marker("3"); +verify.completionListContains("fourslash", undefined, undefined, undefined, 3); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralImport2.ts b/tests/cases/fourslash/completionForStringLiteralImport2.ts new file mode 100644 index 0000000000..25de3d2240 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralImport2.ts @@ -0,0 +1,33 @@ +/// + +// Should define spans for replacement that appear after the last directory seperator in triple slash references + +// @typeRoots: my_typings + +// @Filename: test.ts +//// /// +//// /// + +// @Filename: someFile.ts +//// /*someFile*/ + +// @Filename: sub/someOtherFile.ts +//// /*someOtherFile*/ + +// @Filename: my_typings/some-module/index.d.ts +//// export var x = 9; + +goTo.marker("0"); +verify.completionListContains("someFile.ts", undefined, undefined, undefined, 0); + +goTo.marker("1"); +verify.completionListContains("some-module", undefined, undefined, undefined, 1); + +goTo.marker("2"); +verify.completionListContains("someOtherFile.ts", undefined, undefined, undefined, 2); + +goTo.marker("3"); +verify.completionListContains("some-module", undefined, undefined, undefined, 3); \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts new file mode 100644 index 0000000000..c0e5ebbcd9 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts @@ -0,0 +1,63 @@ +/// + +// Should give completions for node modules and files within those modules with ts file extensions + +// @Filename: tests/test0.ts +//// import * as foo1 from "f/*import_as0*/ +//// import * as foo2 from "fake-module//*import_as1*/ +//// import * as foo3 from "fake-module/*import_as2*/ + +//// import foo4 = require("f/*import_equals0*/ +//// import foo5 = require("fake-module//*import_equals1*/ +//// import foo6 = require("fake-module/*import_equals2*/ + +//// var foo7 = require("f/*require0*/ +//// var foo8 = require("fake-module//*require1*/ +//// var foo9 = require("fake-module/*require2*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } + +// @Filename: node_modules/fake-module/index.js +//// /*fake-module*/ +// @Filename: node_modules/fake-module/index.d.ts +//// /*fakemodule-d-ts*/ +// @Filename: node_modules/fake-module/ts.ts +//// /*ts*/ +// @Filename: node_modules/fake-module/dts.d.ts +//// /*dts*/ +// @Filename: node_modules/fake-module/tsx.tsx +//// /*tsx*/ +// @Filename: node_modules/fake-module/js.js +//// /*js*/ +// @Filename: node_modules/fake-module/jsx.jsx +//// /*jsx*/ + +// @Filename: node_modules/fake-module-dev/index.js +//// /*fakemodule-dev*/ +// @Filename: node_modules/fake-module-dev/index.d.ts +//// /*fakemodule-dev-d-ts*/ + +// @Filename: node_modules/unlisted-module/index.ts +//// /*unlisted-module*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module-dev"); + verify.not.completionListItemsCountIsGreaterThan(2); + + goTo.marker(kind + "1"); + verify.completionListContains("index"); + verify.completionListContains("ts"); + verify.completionListContains("dts"); + verify.completionListContains("tsx"); + verify.not.completionListItemsCountIsGreaterThan(4); + + goTo.marker(kind + "2"); + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module-dev"); + verify.not.completionListItemsCountIsGreaterThan(2); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts new file mode 100644 index 0000000000..24a323bc6e --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport10.ts @@ -0,0 +1,35 @@ +/// + +// Should not give node module completions if classic module resolution is enabled + +// @moduleResolution: classic + +// @Filename: dir1/dir2/dir3/dir4/test0.ts +//// import * as foo1 from "f/*import_as0*/ +//// import * as foo3 from "fake-module/*import_as1*/ + +//// import foo4 = require("f/*import_equals0*/ +//// import foo6 = require("fake-module/*import_equals1*/ + +//// var foo7 = require("f/*require0*/ +//// var foo9 = require("fake-module/*require1*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } +// @Filename: node_modules/fake-module/ts.ts +//// /*module1*/ + +// @Filename: dir1/dir2/dir3/package.json +//// { "dependencies": { "fake-module3": "latest" } } +// @Filename: dir1/dir2/dir3/node_modules/fake-module3/ts.ts +//// /*module3*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListIsEmpty(); + + goTo.marker(kind + "1"); + verify.completionListIsEmpty(); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts new file mode 100644 index 0000000000..7b81c4da10 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport11.ts @@ -0,0 +1,38 @@ +/// + +// Should handle nested files in folders discovered via the baseUrl compiler option + +// @baseUrl: tests/cases/fourslash/modules + +// @Filename: tests/test0.ts +//// import * as foo1 from "subfolder//*import_as0*/ +//// import foo2 = require("subfolder//*import_equals0*/ +//// var foo3 = require("subfolder//*require0*/ + +//// import * as foo1 from "module-from-node//*import_as1*/ +//// import foo2 = require("module-from-node//*import_equals1*/ +//// var foo3 = require("module-from-node//*require1*/ + +// @Filename: modules/subfolder/module.ts +//// export var x = 5; + +// @Filename: package.json +//// { "dependencies": { "module-from-node": "latest" } } +// @Filename: node_modules/module-from-node/index.ts +//// /*module1*/ + + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("module"); + verify.not.completionListItemsCountIsGreaterThan(1); + + goTo.marker(kind + "1"); + + verify.completionListContains("index"); + verify.not.completionListItemsCountIsGreaterThan(1); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts new file mode 100644 index 0000000000..ed202d68ec --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport12.ts @@ -0,0 +1,28 @@ +/// + +// Should give completions for all dependencies in package.json + +// @Filename: tests/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: package.json +//// { +//// "dependencies": { "module": "latest" }, +//// "devDependencies": { "dev-module": "latest" }, +//// "optionalDependencies": { "optional-module": "latest" }, +//// "peerDependencies": { "peer-module": "latest" } +//// } + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("module"); + verify.completionListContains("dev-module"); + verify.completionListContains("optional-module"); + verify.completionListContains("peer-module"); + verify.not.completionListItemsCountIsGreaterThan(4); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts new file mode 100644 index 0000000000..bac5c54ff2 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport2.ts @@ -0,0 +1,37 @@ +/// + +// Should not give duplicate entries for similarly named files with different extensions + +// @Filename: tests/test0.ts +//// import * as foo1 from "fake-module//*import_as0*/ +//// import foo2 = require("fake-module//*import_equals0*/ +//// var foo3 = require("fake-module//*require0*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } + +// @Filename: node_modules/fake-module/repeated.ts +//// /*repeatedts*/ +// @Filename: node_modules/fake-module/repeated.tsx +//// /*repeatedtsx*/ +// @Filename: node_modules/fake-module/repeated.d.ts +//// /*repeateddts*/ +// @Filename: node_modules/fake-module/other.js +//// /*other*/ +// @Filename: node_modules/fake-module/other2.js +//// /*other2*/ + +// @Filename: node_modules/unlisted-module/index.js +//// /*unlisted-module*/ + +// @Filename: ambient.ts +//// declare module "fake-module/other" + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("repeated"); + verify.completionListContains("other"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts new file mode 100644 index 0000000000..d2103f6f10 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport3.ts @@ -0,0 +1,38 @@ +/// + +// Should give completions for js files in node modules when allowJs is set to true + +// @allowJs: true + +// @Filename: tests/test0.ts +//// import * as foo1 from "fake-module//*import_as0*/ +//// import foo2 = require("fake-module//*import_equals0*/ +//// var foo3 = require("fake-module//*require0*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } + +// @Filename: node_modules/fake-module/ts.ts +//// /*ts*/ +// @Filename: node_modules/fake-module/tsx.tsx +//// /*tsx*/ +// @Filename: node_modules/fake-module/dts.d.ts +//// /*dts*/ +// @Filename: node_modules/fake-module/js.js +//// /*js*/ +// @Filename: node_modules/fake-module/jsx.jsx +//// /*jsx*/ +// @Filename: node_modules/fake-module/repeated.js +//// /*repeatedjs*/ +// @Filename: node_modules/fake-module/repeated.jsx +//// /*repeatedjsx*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("ts"); + verify.completionListContains("tsx"); + verify.completionListContains("dts"); + verify.not.completionListItemsCountIsGreaterThan(3); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts new file mode 100644 index 0000000000..b95fd96f38 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -0,0 +1,34 @@ +/// + +// Should give completions for all node modules visible to the script + +// @Filename: dir1/dir2/dir3/dir4/test0.ts +//// import * as foo1 from "f/*import_as0*/ +//// import foo4 = require("f/*import_equals0*/ +//// var foo7 = require("f/*require0*/ + +// @Filename: package.json +//// { "dependencies": { "fake-module": "latest" } } +// @Filename: node_modules/fake-module/ts.ts +//// /*module1*/ + +// @Filename: dir1/package.json +//// { "dependencies": { "fake-module2": "latest" } } +// @Filename: dir1/node_modules/fake-module2/index.ts +//// /*module2*/ + +// @Filename: dir1/dir2/dir3/package.json +//// { "dependencies": { "fake-module3": "latest" } } +// @Filename: dir1/dir2/dir3/node_modules/fake-module3/ts.ts +//// /*module3*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("fake-module"); + verify.completionListContains("fake-module2"); + verify.completionListContains("fake-module3"); + verify.not.completionListItemsCountIsGreaterThan(3); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts new file mode 100644 index 0000000000..c8c9a18ff8 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport5.ts @@ -0,0 +1,39 @@ +/// + +// Should give completions for ambiently declared modules + +// @Filename: test0.ts +//// /// +//// /// +//// import * as foo1 from "/*import_as0*/ +//// import * as foo2 from "a/*import_as1*/ + +//// import foo3 = require("/*import_equals0*/ +//// import foo4 = require("a/*import_equals1*/ + +//// var foo5 = require("/*require0*/ +//// var foo6 = require("a/*require1*/ + +// @Filename: ambientModules.d.ts +//// declare module "ambientModule" {} +//// declare module "otherAmbientModule" {} /*dummy0*/ + +// @Filename: ambientModules2.d.ts +//// declare module "otherOtherAmbientModule" {} /*dummy1*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("ambientModule"); + verify.completionListContains("otherAmbientModule"); + verify.completionListContains("otherOtherAmbientModule"); + verify.not.completionListItemsCountIsGreaterThan(3); + + goTo.marker(kind + "1"); + + verify.completionListContains("ambientModule"); + verify.not.completionListItemsCountIsGreaterThan(1); +} + diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts new file mode 100644 index 0000000000..57ba6e440c --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport7.ts @@ -0,0 +1,30 @@ +/// + +// Should give completions for files that are discovered via the baseUrl compiler option + +// @baseUrl: tests/cases/fourslash/modules + +// @Filename: tests/test0.ts +//// import * as foo1 from "mod/*import_as0*/ +//// import foo2 = require("mod/*import_equals0*/ +//// var foo3 = require("mod/*require0*/ + +// @Filename: modules/module.ts +//// export var x = 5; + +// @Filename: package.json +//// { "dependencies": { "module-from-node": "latest" } } +// @Filename: node_modules/module-from-node/index.ts +//// /*module1*/ + + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("module"); + verify.completionListContains("module-from-node"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts new file mode 100644 index 0000000000..603e8d1104 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts @@ -0,0 +1,55 @@ +/// + +// Should give completions for modules referenced via baseUrl and paths compiler options with wildcards + +// @Filename: tsconfig.json +//// { +//// "compilerOptions": { +//// "baseUrl": "./modules", +//// "paths": { +//// "*": [ +//// "prefix/0*/suffix.ts", +//// "prefix-only/*", +//// "*/suffix-only.ts" +//// ] +//// } +//// } +//// } + + +// @Filename: tests/test0.ts +//// import * as foo1 from "0/*import_as0*/ +//// import foo2 = require("0/*import_equals0*/ +//// var foo3 = require("0/*require0*/ + +//// import * as foo1 from "1/*import_as1*/ +//// import foo2 = require("1/*import_equals1*/ +//// var foo3 = require("1/*require1*/ + +//// import * as foo1 from "2/*import_as2*/ +//// import foo2 = require("2/*import_equals2*/ +//// var foo3 = require("2/*require2*/ + + +// @Filename: modules/prefix/00test/suffix.ts +//// export var x = 5; + +// @Filename: modules/prefix-only/1test.ts +//// export var y = 5; + +// @Filename: modules/2test/suffix-only.ts +//// export var z = 5; + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("0test"); + + goTo.marker(kind + "1"); + verify.completionListContains("1test"); + + goTo.marker(kind + "2"); + verify.completionListContains("2test"); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts new file mode 100644 index 0000000000..8703c60846 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport9.ts @@ -0,0 +1,36 @@ +/// + +// Should give completions for modules referenced via baseUrl and paths compiler options with explicit name mappings + +// @Filename: tsconfig.json +//// { +//// "compilerOptions": { +//// "baseUrl": "./modules", +//// "paths": { +//// "module1": ["some/path/whatever.ts"], +//// "module2": ["some/other/path.ts"] +//// } +//// } +//// } + + +// @Filename: tests/test0.ts +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: some/path/whatever.ts +//// export var x = 9; + +// @Filename: some/other/path.ts +//// export var y = 10; + + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module1"); + verify.completionListContains("module2"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts new file mode 100644 index 0000000000..687103629e --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings1.ts @@ -0,0 +1,35 @@ +/// + +// Should give completions for typings discovered via the typeRoots compiler option + +// @typeRoots: my_typings,my_other_typings + + +// @Filename: tests/test0.ts +//// /// +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: my_typings/module-x/index.d.ts +//// export var x = 9; + +// @Filename: my_typings/module-x/whatever.d.ts +//// export var w = 9; + +// @Filename: my_typings/module-y/index.d.ts +//// export var y = 9; + +// @Filename: my_other_typings/module-z/index.d.ts +//// export var z = 9; + + +const kinds = ["types_ref", "import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module-x"); + verify.completionListContains("module-y"); + verify.completionListContains("module-z"); + verify.not.completionListItemsCountIsGreaterThan(3); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts new file mode 100644 index 0000000000..12a4fcf243 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings2.ts @@ -0,0 +1,32 @@ +/// + +// Should respect the types compiler option when giving completions + +// @typeRoots: my_typings,my_other_typings +// @types: module-x,module-z + + +// @Filename: tests/test0.ts +//// /// +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: my_typings/module-x/index.d.ts +//// export var x = 9; + +// @Filename: my_typings/module-y/index.d.ts +//// export var y = 9; + +// @Filename: my_other_typings/module-z/index.d.ts +//// export var z = 9; + + +const kinds = ["types_ref", "import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module-x"); + verify.completionListContains("module-z"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts new file mode 100644 index 0000000000..14cf71ad0d --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImportTypings3.ts @@ -0,0 +1,28 @@ +/// + +// Should give completions for typings discovered in all visible @types directories + +// @Filename: subdirectory/test0.ts +//// /// +//// import * as foo1 from "m/*import_as0*/ +//// import foo2 = require("m/*import_equals0*/ +//// var foo3 = require("m/*require0*/ + +// @Filename: subdirectory/node_modules/@types/module-x/index.d.ts +//// export var x = 9; +// @Filename: subdirectory/package.json +//// { "dependencies": { "@types/module-x": "latest" } } + +// @Filename: node_modules/@types/module-y/index.d.ts +//// export var y = 9; +// @Filename: package.json +//// { "dependencies": { "@types/module-y": "latest" } } + +const kinds = ["types_ref", "import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("module-x"); + verify.completionListContains("module-y"); + verify.not.completionListItemsCountIsGreaterThan(2); +} diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts new file mode 100644 index 0000000000..3ba82fb0ac --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -0,0 +1,70 @@ +/// + +// Should give completions for ts files when allowJs is false + +// @Filename: test0.ts +//// import * as foo1 from "./*import_as0*/ +//// import * as foo2 from ".//*import_as1*/ +//// import * as foo4 from "./folder//*import_as2*/ + +//// import foo6 = require("./*import_equals0*/ +//// import foo7 = require(".//*import_equals1*/ +//// import foo9 = require("./folder//*import_equals2*/ + +//// var foo11 = require("./*require0*/ +//// var foo12 = require(".//*require1*/ +//// var foo14 = require("./folder//*require2*/ + +// @Filename: parentTest/sub/test5.ts +//// import * as foo16 from "../g/*import_as3*/ +//// import foo17 = require("../g/*import_equals3*/ +//// var foo18 = require("../g/*require3*/ + + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f1.js +//// /*f1j*/ +// @Filename: f1.d.ts +//// /*f1d*/ +// @Filename: f2.tsx +//// /f2*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: folder/f3.ts +//// /*subf1*/ +// @Filename: folder/h1.ts +//// /*subh1*/ +// @Filename: parentTest/f4.ts +//// /*parentf1*/ +// @Filename: parentTest/g1.ts +//// /*parentg1*/ +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListIsEmpty(); + + goTo.marker(kind + "1"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("e1"); + verify.completionListContains("folder"); + verify.completionListContains("parentTest"); + verify.not.completionListItemsCountIsGreaterThan(5); + + goTo.marker(kind + "2"); + verify.completionListContains("f3"); + verify.completionListContains("h1"); + verify.not.completionListItemsCountIsGreaterThan(2); + + goTo.marker(kind + "3"); + verify.completionListContains("f4"); + verify.completionListContains("g1"); + verify.completionListContains("sub"); + verify.not.completionListItemsCountIsGreaterThan(3); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts new file mode 100644 index 0000000000..18a80ab348 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -0,0 +1,53 @@ +/// + +// Should give completions for ts and js files when allowJs is true + +// @allowJs: true + +// @Filename: test0.ts +//// import * as foo1 from ".//*import_as0*/ +//// import * as foo2 from "./f/*import_as1*/ + +//// import foo3 = require(".//*import_equals0*/ +//// import foo4 = require("./f/*import_equals1*/ + +//// var foo5 = require(".//*require0*/ +//// var foo6 = require("./f/*require1*/ + +// @Filename: f1.ts +//// /f1*/ +// @Filename: f1.js +//// /*f1j*/ +// @Filename: f1.d.ts +//// /*f1d*/ +// @Filename: f2.tsx +//// /*f2*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: e2.js +//// /*e2*/ +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("f3"); + verify.completionListContains("f4"); + verify.completionListContains("e1"); + verify.completionListContains("e2"); + verify.not.completionListItemsCountIsGreaterThan(6); + + goTo.marker(kind + "1"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("f3"); + verify.completionListContains("f4"); + verify.completionListContains("e1"); + verify.completionListContains("e2"); + verify.not.completionListItemsCountIsGreaterThan(6); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts new file mode 100644 index 0000000000..915cebdf6e --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport3.ts @@ -0,0 +1,51 @@ +/// + +// Should give completions for absolute paths + +// @Filename: tests/test0.ts +//// import * as foo1 from "/tests/cases/f/*import_as0*/ +//// import * as foo2 from "/tests/cases/fourslash/*import_as1*/ +//// import * as foo3 from "/tests/cases/fourslash//*import_as2*/ + +//// import foo4 = require("/tests/cases/f/*import_equals0*/ +//// import foo5 = require("/tests/cases/fourslash/*import_equals1*/ +//// import foo6 = require("/tests/cases/fourslash//*import_equals2*/ + +//// var foo7 = require("/tests/cases/f/*require0*/ +//// var foo8 = require("/tests/cases/fourslash/*require1*/ +//// var foo9 = require("/tests/cases/fourslash//*require2*/ + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f2.tsx +//// /*f2*/ +// @Filename: folder/f1.ts +//// /*subf1*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: e2.js +//// /*e2*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + verify.completionListContains("fourslash"); + verify.not.completionListItemsCountIsGreaterThan(1); + + goTo.marker(kind + "1"); + verify.completionListContains("fourslash"); + verify.not.completionListItemsCountIsGreaterThan(1); + + goTo.marker(kind + "2"); + verify.completionListContains("f1"); + verify.completionListContains("f2"); + verify.completionListContains("e1"); + verify.completionListContains("folder"); + verify.completionListContains("tests"); + verify.not.completionListItemsCountIsGreaterThan(5); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts new file mode 100644 index 0000000000..b9750de9c1 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -0,0 +1,52 @@ +/// + +// Should give completions for directories that are merged via the rootDirs compiler option + +// @rootDirs: tests/cases/fourslash/sub/src1,tests/cases/fourslash/src2 + +// @Filename: src2/test0.ts +//// import * as foo1 from "./mo/*import_as0*/ +//// import foo2 = require("./mo/*import_equals0*/ +//// var foo3 = require("./mo/*require0*/ + +// @Filename: src2/module0.ts +//// export var w = 0; + +// @Filename: sub/src1/module1.ts +//// export var x = 0; + +// @Filename: sub/src1/module2.ts +//// export var y = 0; + +// @Filename: sub/src1/more/module3.ts +//// export var z = 0; + + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f2.tsx +//// /*f2*/ +// @Filename: folder/f1.ts +//// /*subf1*/ +// @Filename: f3.js +//// /*f3*/ +// @Filename: f4.jsx +//// /*f4*/ +// @Filename: e1.ts +//// /*e1*/ +// @Filename: e2.js +//// /*e2*/ + +const kinds = ["import_as", "import_equals", "require"]; + +for (const kind of kinds) { + goTo.marker(kind + "0"); + + verify.completionListContains("module0"); + verify.completionListContains("module1"); + verify.completionListContains("module2"); + verify.completionListContains("more"); + + // Should not contain itself + verify.not.completionListItemsCountIsGreaterThan(4); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts new file mode 100644 index 0000000000..2ce4de2e72 --- /dev/null +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -0,0 +1,51 @@ +/// + +// Should give completions for relative references to ts files when allowJs is false + +// @Filename: test0.ts +//// /// +//// /// + +// Should give completions for relative references to js and ts files when allowJs is true + +// @allowJs: true + +// @Filename: test0.ts +//// /// + +// @Filename: f1.ts +//// /*f1*/ +// @Filename: f1.js +//// /*f1j*/ +// @Filename: f1.d.ts +//// /*f1d*/ +// @Filename: f2.tsx +//// /f2*/ +// @Filename: f4.jsx +//// /*f4*/ + +for (let i = 0; i < 5; i++) { + goTo.marker("" + i); + verify.completionListContains("f1.ts"); + verify.completionListContains("f1.js"); + verify.completionListContains("f1.d.ts"); + verify.completionListContains("f2.tsx"); + verify.completionListContains("f4.jsx"); + verify.not.completionListItemsCountIsGreaterThan(5); +} \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference3.ts b/tests/cases/fourslash/completionForTripleSlashReference3.ts new file mode 100644 index 0000000000..798e556be5 --- /dev/null +++ b/tests/cases/fourslash/completionForTripleSlashReference3.ts @@ -0,0 +1,43 @@ +/// + +// Should give completions for absolute paths + +// @Filename: tests/test0.ts +//// /// + +// Should NOT give completions for directories that are merged via the rootDirs compiler option + +// @rootDirs: sub/src1,src2 + +// @Filename: src2/test0.ts +//// ///