From aa2d1008bf573fccdcc02335b11f9fb782888fcb Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 11 Jul 2017 07:23:32 -0700 Subject: [PATCH] Completion for default export should be '.default' (#16742) * Completion for default export should be '.default' * Don't include empty string in name table * getSymbolsInScope() should return local symbols, not exported symbols * Fix bug: getSymbolAtLocation should work for local symbol too --- src/compiler/checker.ts | 9 +- src/compiler/utilities.ts | 5 + src/harness/fourslash.ts | 24 +++++ src/services/completions.ts | 93 ++++++++----------- src/services/services.ts | 59 +++++------- src/services/symbolDisplay.ts | 12 +-- .../fourslash/commentsExternalModules.ts | 2 +- tests/cases/fourslash/commentsModules.ts | 2 +- .../completionListForUnicodeEscapeName.ts | 17 ++-- ...ListInScope_doesNotIncludeAugmentations.ts | 13 +++ .../completionListInvalidMemberNames.ts | 3 +- .../fourslash/completionListOnAliases2.ts | 18 ++-- ...pletionListWithModulesInsideModuleScope.ts | 57 ++++++------ .../fourslash/completionsDefaultExport.ts | 11 +++ .../cases/fourslash/exportDefaultFunction.ts | 2 +- .../findAllRefsForDefaultExport02.ts | 5 +- .../findAllRefsForDefaultExport08.ts | 4 +- tests/cases/fourslash/fourslash.ts | 2 + .../quickInfoOnNarrowedTypeInModule.ts | 8 +- .../typeOfSymbol_localSymbolOfExport.ts | 12 +++ 20 files changed, 200 insertions(+), 158 deletions(-) create mode 100644 tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts create mode 100644 tests/cases/fourslash/completionsDefaultExport.ts create mode 100644 tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee0a3a90d7..bf03958e4f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11875,6 +11875,8 @@ namespace ts { } function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = symbol.exportSymbol || symbol; + // If we have an identifier or a property access at the given location, if the location is // an dotted name expression, and if the location is not an assignment target, obtain the type // of the expression (which will reflect control flow analysis). If the expression indeed @@ -22281,11 +22283,6 @@ namespace ts { } switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location)) { - break; - } - // falls through case SyntaxKind.ModuleDeclaration: copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.ModuleMember); break; @@ -22337,7 +22334,7 @@ namespace ts { * @param meaning meaning of symbol to filter by before adding to symbol table */ function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { - if (symbol.flags & meaning) { + if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { const id = symbol.name; // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 04479e6f9c..e9cff26298 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3598,6 +3598,11 @@ namespace ts { } return previous[previous.length - 1]; } + + /** See comment on `declareModuleMember` in `binder.ts`. */ + export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { + return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; + } } namespace ts { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 587bed813f..411f6762ba 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -949,6 +949,22 @@ namespace FourSlash { this.verifySymbol(symbol, declarationRanges); } + public symbolsInScope(range: Range): ts.Symbol[] { + const node = this.goToAndGetNode(range); + return this.getChecker().getSymbolsInScope(node, ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace); + } + + public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void { + const node = this.goToAndGetNode(range); + const checker = this.getChecker(); + const type = checker.getTypeOfSymbolAtLocation(symbol, node); + + const actual = checker.typeToString(type); + if (actual !== expected) { + this.raiseError(`Expected: '${expected}', actual: '${actual}'`); + } + } + private verifyReferencesAre(expectedReferences: Range[]) { const actualReferences = this.getReferencesAtCaret() || []; @@ -3426,6 +3442,10 @@ namespace FourSlashInterface { public markerByName(s: string): FourSlash.Marker { return this.state.getMarkerByName(s); } + + public symbolsInScope(range: FourSlash.Range): ts.Symbol[] { + return this.state.symbolsInScope(range); + } } export class GoTo { @@ -3694,6 +3714,10 @@ namespace FourSlashInterface { this.state.verifySymbolAtLocation(startRange, declarationRanges); } + public typeOfSymbolAtLocation(range: FourSlash.Range, symbol: ts.Symbol, expected: string) { + this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected); + } + public referencesOf(start: FourSlash.Range, references: FourSlash.Range[]) { this.state.verifyReferencesOf(start, references); } diff --git a/src/services/completions.ts b/src/services/completions.ts index 94e8779d1d..9a43e10ad9 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -61,7 +61,7 @@ namespace ts.Completions { } else { if ((!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { - return undefined; + return undefined; } getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log); @@ -112,7 +112,7 @@ namespace ts.Completions { // Try to get a valid display name for this symbol, if we could not find one, then ignore it. // We would like to only show things that can be added after a dot, so for instance numeric properties can // not be accessed with a dot (a.1 <- invalid) - const displayName = getCompletionEntryDisplayNameForSymbol(typeChecker, symbol, target, performCharacterChecks, location); + const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks); if (!displayName) { return undefined; } @@ -307,7 +307,7 @@ namespace ts.Completions { // We don't need to perform character checks here because we're only comparing the // name against 'entryName' (which is known to be good), not building a new // completion entry. - const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); + const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined); if (symbol) { const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); @@ -341,20 +341,14 @@ namespace ts.Completions { return undefined; } - export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol { + export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol | undefined { // Compute all the completion symbols again. const completionData = getCompletionData(typeChecker, log, sourceFile, position); - if (completionData) { - const { symbols, location } = completionData; - - // Find the symbol with the matching entry name. - // We don't need to perform character checks here because we're only comparing the - // name against 'entryName' (which is known to be good), not building a new - // completion entry. - return forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(typeChecker, s, compilerOptions.target, /*performCharacterChecks*/ false, location) === entryName ? s : undefined); - } - - return undefined; + // Find the symbol with the matching entry name. + // We don't need to perform character checks here because we're only comparing the + // name against 'entryName' (which is known to be good), not building a new + // completion entry. + return completionData && forEach(completionData.symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false) === entryName ? s : undefined); } interface CompletionData { @@ -369,7 +363,7 @@ namespace ts.Completions { } type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag }; - function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData { + function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData | undefined { const isJavaScriptFile = isSourceFileJavaScript(sourceFile); let request: Request | undefined; @@ -615,7 +609,7 @@ namespace ts.Completions { // Extract module or enum members const exportedSymbols = typeChecker.getExportsOfModule(symbol); const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess((node.parent), symbol.getUnescapedName()); - const isValidTypeAccess = (symbol: Symbol) => symbolCanbeReferencedAtTypeLocation(symbol); + const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol); const isValidAccess = isRhsOfImportDeclaration ? // Any kind is allowed when dotting off namespace in internal import equals declaration (symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : @@ -630,7 +624,7 @@ namespace ts.Completions { if (!isTypeLocation) { const type = typeChecker.getTypeAtLocation(node); - addTypeProperties(type); + if (type) addTypeProperties(type); } } @@ -642,17 +636,17 @@ namespace ts.Completions { symbols.push(symbol); } } + } - if (isJavaScriptFile && type.flags & TypeFlags.Union) { - // In javascript files, for union types, we don't just get the members that - // the individual types have in common, we also include all the members that - // each individual type has. This is because we're going to add all identifiers - // anyways. So we might as well elevate the members that were at least part - // of the individual types to a higher status since we know what they are. - const unionType = type; - for (const elementType of unionType.types) { - addTypeProperties(elementType); - } + if (isJavaScriptFile && type.flags & TypeFlags.Union) { + // In javascript files, for union types, we don't just get the members that + // the individual types have in common, we also include all the members that + // each individual type has. This is because we're going to add all identifiers + // anyways. So we might as well elevate the members that were at least part + // of the individual types to a higher status since we know what they are. + const unionType = type; + for (const elementType of unionType.types) { + addTypeProperties(elementType); } } } @@ -777,12 +771,12 @@ namespace ts.Completions { (!isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)))) { // Its a type, but you can reach it by namespace.type as well - return symbolCanbeReferencedAtTypeLocation(symbol); + return symbolCanBeReferencedAtTypeLocation(symbol); } } // expressions are value space (which includes the value namespaces) - return !!(symbol.flags & SymbolFlags.Value); + return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value); }); } @@ -812,7 +806,9 @@ namespace ts.Completions { } } - function symbolCanbeReferencedAtTypeLocation(symbol: Symbol): boolean { + function symbolCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { + symbol = symbol.exportSymbol || symbol; + // This is an alias, follow what it aliases if (symbol && symbol.flags & SymbolFlags.Alias) { symbol = typeChecker.getAliasedSymbol(symbol); @@ -826,7 +822,7 @@ namespace ts.Completions { const exportedSymbols = typeChecker.getExportsOfModule(symbol); // If the exported symbols contains type, // symbol can be referenced at locations where type is allowed - return forEach(exportedSymbols, symbolCanbeReferencedAtTypeLocation); + return forEach(exportedSymbols, symbolCanBeReferencedAtTypeLocation); } } @@ -1598,22 +1594,23 @@ namespace ts.Completions { /** * Get the name to be display in completion from a given symbol. * - * @return undefined if the name is of external module otherwise a name with striped of any quote + * @return undefined if the name is of external module */ - function getCompletionEntryDisplayNameForSymbol(typeChecker: TypeChecker, symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, location: Node): string { - const displayName: string = getDeclaredName(typeChecker, symbol, location); + function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean): string | undefined { + const name = symbol.getUnescapedName(); + if (!name) return undefined; - if (displayName) { - const firstCharCode = displayName.charCodeAt(0); - // First check of the displayName is not external module; if it is an external module, it is not valid entry - if ((symbol.flags & SymbolFlags.Namespace) && (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote)) { + // First check of the displayName is not external module; if it is an external module, it is not valid entry + if (symbol.flags & SymbolFlags.Namespace) { + const firstCharCode = name.charCodeAt(0); + if (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote) { // If the symbol is external module, don't show it in the completion list // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) return undefined; } } - return getCompletionEntryDisplayName(displayName, target, performCharacterChecks); + return getCompletionEntryDisplayName(name, target, performCharacterChecks); } /** @@ -1621,24 +1618,12 @@ namespace ts.Completions { * and checking whether the name is valid identifier name. */ function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean): string { - if (!name) { - return undefined; - } - - name = stripQuotes(name); - - if (!name) { - return undefined; - } - // If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an // invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name. // e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid. // We, thus, need to check if whatever was inside the quotes is actually a valid identifier name. - if (performCharacterChecks) { - if (!isIdentifierText(name, target)) { - return undefined; - } + if (performCharacterChecks && !isIdentifierText(name, target)) { + return undefined; } return name; diff --git a/src/services/services.ts b/src/services/services.ts index be17a35d3e..d89051ee66 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2063,42 +2063,33 @@ namespace ts { } function initializeNameTable(sourceFile: SourceFile): void { - const nameTable = createUnderscoreEscapedMap(); - - walk(sourceFile); - sourceFile.nameTable = nameTable; - - function walk(node: Node) { - switch (node.kind) { - case SyntaxKind.Identifier: - setNameTable((node).text, node); - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - // We want to store any numbers/strings if they were a name that could be - // related to a declaration. So, if we have 'import x = require("something")' - // then we want 'something' to be in the name table. Similarly, if we have - // "a['propname']" then we want to store "propname" in the name table. - if (isDeclarationName(node) || - node.parent.kind === SyntaxKind.ExternalModuleReference || - isArgumentOfElementAccessExpression(node) || - isLiteralComputedPropertyDeclarationName(node)) { - setNameTable(getEscapedTextOfIdentifierOrLiteral((node)), node); - } - break; - default: - forEachChild(node, walk); - if (node.jsDoc) { - for (const jsDoc of node.jsDoc) { - forEachChild(jsDoc, walk); - } - } + const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap(); + sourceFile.forEachChild(function walk(node) { + if ((isIdentifier(node) || isStringOrNumericLiteral(node) && literalIsName(node)) && node.text) { + const text = getEscapedTextOfIdentifierOrLiteral(node); + nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); } - } - function setNameTable(text: __String, node: ts.Node): void { - nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); - } + forEachChild(node, walk); + if (node.jsDoc) { + for (const jsDoc of node.jsDoc) { + forEachChild(jsDoc, walk); + } + } + }); + } + + /** + * We want to store any numbers/strings if they were a name that could be + * related to a declaration. So, if we have 'import x = require("something")' + * then we want 'something' to be in the name table. Similarly, if we have + * "a['propname']" then we want to store "propname" in the name table. + */ + function literalIsName(node: ts.StringLiteral | ts.NumericLiteral): boolean { + return isDeclarationName(node) || + node.parent.kind === SyntaxKind.ExternalModuleReference || + isArgumentOfElementAccessExpression(node) || + isLiteralComputedPropertyDeclarationName(node); } function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index b599839cfd..0cb3916c9c 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -2,7 +2,7 @@ namespace ts.SymbolDisplay { // TODO(drosen): use contextual SemanticMeaning. export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const { flags } = symbol; + const flags = getCombinedLocalAndExportSymbolFlags(symbol); if (flags & SymbolFlags.Class) { return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? @@ -34,7 +34,7 @@ namespace ts.SymbolDisplay { if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) { return ScriptElementKind.parameterElement; } - const { flags } = symbol; + const flags = getCombinedLocalAndExportSymbolFlags(symbol); if (flags & SymbolFlags.Variable) { if (isFirstDeclarationOfSymbolParameter(symbol)) { return ScriptElementKind.parameterElement; @@ -96,7 +96,7 @@ namespace ts.SymbolDisplay { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[]; let tags: JSDocTagInfo[]; - const symbolFlags = symbol.flags; + const symbolFlags = ts.getCombinedLocalAndExportSymbolFlags(symbol); let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); let hasAddedSymbolInfo: boolean; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); @@ -110,7 +110,7 @@ namespace ts.SymbolDisplay { } let signature: Signature; - type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); + type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol.exportSymbol || symbol, location); if (type) { if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { const right = (location.parent).name; @@ -198,7 +198,7 @@ namespace ts.SymbolDisplay { hasAddedSymbolInfo = true; } } - else if ((isNameOfFunctionDeclaration(location) && !(symbol.flags & SymbolFlags.Accessor)) || // name of function declaration + else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration // get the signature from the declaration and write it const functionDeclaration = location.parent; @@ -429,7 +429,7 @@ namespace ts.SymbolDisplay { if (!documentation) { documentation = symbol.getDocumentationComment(); tags = symbol.getJsDocTags(); - if (documentation.length === 0 && symbol.flags & SymbolFlags.Property) { + if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` // there documentation comments might be attached to the right hand side symbol of their declarations. // The pattern of such special property access is that the parent symbol is the symbol of the file. diff --git a/tests/cases/fourslash/commentsExternalModules.ts b/tests/cases/fourslash/commentsExternalModules.ts index 4f1a82bc0c..582c6a3135 100644 --- a/tests/cases/fourslash/commentsExternalModules.ts +++ b/tests/cases/fourslash/commentsExternalModules.ts @@ -35,7 +35,7 @@ goTo.file("commentsExternalModules_file0.ts"); verify.quickInfoAt("1", "namespace m1", "Namespace comment"); goTo.marker('2'); -verify.completionListContains("b", "var m1.b: number", "b's comment"); +verify.completionListContains("b", "var b: number", "b's comment"); verify.completionListContains("foo", "function foo(): number", "foo's comment"); goTo.marker('3'); diff --git a/tests/cases/fourslash/commentsModules.ts b/tests/cases/fourslash/commentsModules.ts index c0b8ccc573..0205b144f1 100644 --- a/tests/cases/fourslash/commentsModules.ts +++ b/tests/cases/fourslash/commentsModules.ts @@ -99,7 +99,7 @@ verify.quickInfoAt("1", "namespace m1", "Namespace comment"); goTo.marker('2'); -verify.completionListContains("b", "var m1.b: number", "b's comment"); +verify.completionListContains("b", "var b: number", "b's comment"); verify.completionListContains("foo", "function foo(): number", "foo's comment"); goTo.marker('3'); diff --git a/tests/cases/fourslash/completionListForUnicodeEscapeName.ts b/tests/cases/fourslash/completionListForUnicodeEscapeName.ts index afafd18a58..9ada3e3596 100644 --- a/tests/cases/fourslash/completionListForUnicodeEscapeName.ts +++ b/tests/cases/fourslash/completionListForUnicodeEscapeName.ts @@ -6,8 +6,8 @@ /////*3*/ goTo.marker("0"); -verify.not.completionListContains("B"); -verify.not.completionListContains("\u0042"); +verify.completionListContains("B"); +verify.completionListContains("\u0042"); goTo.marker("2"); verify.not.completionListContains("C"); @@ -18,10 +18,9 @@ verify.not.completionListContains("A"); verify.not.completionListContains("\u0041"); goTo.marker("3"); -verify.not.completionListContains("B"); -verify.not.completionListContains("\u0042"); -verify.not.completionListContains("A"); -verify.not.completionListContains("\u0041"); -verify.not.completionListContains("C"); -verify.not.completionListContains("\u0043"); - +verify.completionListContains("B"); +verify.completionListContains("\u0042"); +verify.completionListContains("A"); +verify.completionListContains("\u0041"); +verify.completionListContains("C"); +verify.completionListContains("\u0043"); diff --git a/tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts b/tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts new file mode 100644 index 0000000000..de70890c4c --- /dev/null +++ b/tests/cases/fourslash/completionListInScope_doesNotIncludeAugmentations.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.ts +////import * as self from "./a"; +//// +////declare module "a" { +//// export const a: number; +////} +//// +/////**/ + +goTo.marker(); +verify.not.completionListContains("a"); diff --git a/tests/cases/fourslash/completionListInvalidMemberNames.ts b/tests/cases/fourslash/completionListInvalidMemberNames.ts index 95abe70415..e0a65bfca4 100644 --- a/tests/cases/fourslash/completionListInvalidMemberNames.ts +++ b/tests/cases/fourslash/completionListInvalidMemberNames.ts @@ -19,6 +19,7 @@ verify.completionListContains("bar"); verify.completionListContains("break"); verify.completionListContains("any"); verify.completionListContains("$"); +verify.completionListContains("b"); // Nothing else should show up -verify.completionListCount(4); +verify.completionListCount(5); diff --git a/tests/cases/fourslash/completionListOnAliases2.ts b/tests/cases/fourslash/completionListOnAliases2.ts index 63f895f276..5933fbe89d 100644 --- a/tests/cases/fourslash/completionListOnAliases2.ts +++ b/tests/cases/fourslash/completionListOnAliases2.ts @@ -41,19 +41,16 @@ function getVerify(isTypeLocation?: boolean) { verifyValueOrType: verify }; } -function typeLocationVerify(valueMarker: string, verify: (typeMarker: string) => void) { - verify(valueMarker + "Type"); - return valueMarker; -} function verifyModuleM(marker: string) { - const isTypeLocation = marker.indexOf("Type") !== -1; - const { verifyValue, verifyType, verifyValueOrType } = getVerify(isTypeLocation); - if (!isTypeLocation) { - marker = typeLocationVerify(marker, verifyModuleM); - } + verifyModuleMWorker(marker, /*isTypeLocation*/ false); + verifyModuleMWorker(`${marker}Type`, /*isTypeLocation*/ true); +} + +function verifyModuleMWorker(marker: string, isTypeLocation: boolean): void { goTo.marker(marker); + const { verifyValue, verifyType, verifyValueOrType } = getVerify(isTypeLocation); verifyType.completionListContains("I"); verifyValueOrType.completionListContains("C"); verifyValueOrType.completionListContains("E"); @@ -63,8 +60,9 @@ function verifyModuleM(marker: string) { verifyValueOrType.completionListContains("A"); } - // Module m +goTo.marker("1"); +verify.completionListContains("A"); verifyModuleM("1"); // Class C diff --git a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts index 5d950a5d62..cb6bf283b6 100644 --- a/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts +++ b/tests/cases/fourslash/completionListWithModulesInsideModuleScope.ts @@ -232,6 +232,7 @@ interface GotoMarkVerifyOptions { isClassScope?: boolean; isTypeLocation?: boolean; + insideMod1?: boolean; } function getVerify(isTypeLocation: boolean) { @@ -243,30 +244,30 @@ function getVerify(isTypeLocation: boolean) { }; } -function goToMarkAndGeneralVerify(marker: string, { isClassScope, isTypeLocation }: GotoMarkVerifyOptions = {}) -{ +function goToMarkAndGeneralVerify(marker: string, { isClassScope, isTypeLocation, insideMod1 }: GotoMarkVerifyOptions = {}) { goTo.marker(marker); + const mod1Dot = insideMod1 ? "" : "mod1."; const verifyValueInModule = isClassScope || isTypeLocation ? verify.not : verify; const verifyValueOrTypeInModule = isClassScope ? verify.not : verify; const verifyTypeInModule = isTypeLocation ? verify : verify.not; verifyValueInModule.completionListContains('mod1var', 'var mod1var: number'); verifyValueInModule.completionListContains('mod1fn', 'function mod1fn(): void'); - verifyValueInModule.completionListContains('mod1evar', 'var mod1.mod1evar: number'); - verifyValueInModule.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); - verifyValueInModule.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); + verifyValueInModule.completionListContains('mod1evar', `var ${mod1Dot}mod1evar: number`); + verifyValueInModule.completionListContains('mod1efn', `function ${mod1Dot}mod1efn(): void`); + verifyValueInModule.completionListContains('mod1eexvar', `var mod1.mod1eexvar: number`); verifyValueInModule.completionListContains('mod3', 'namespace mod3'); verifyValueInModule.completionListContains('shwvar', 'var shwvar: number'); verifyValueInModule.completionListContains('shwfn', 'function shwfn(): void'); verifyTypeInModule.completionListContains('mod1int', 'interface mod1int'); - verifyTypeInModule.completionListContains('mod1eint', 'interface mod1.mod1eint'); + verifyTypeInModule.completionListContains('mod1eint', `interface ${mod1Dot}mod1eint`); verifyTypeInModule.completionListContains('shwint', 'interface shwint'); verifyValueOrTypeInModule.completionListContains('mod1cls', 'class mod1cls'); verifyValueOrTypeInModule.completionListContains('mod1mod', 'namespace mod1mod'); - verifyValueOrTypeInModule.completionListContains('mod1ecls', 'class mod1.mod1ecls'); - verifyValueOrTypeInModule.completionListContains('mod1emod', 'namespace mod1.mod1emod'); + verifyValueOrTypeInModule.completionListContains('mod1ecls', `class ${mod1Dot}mod1ecls`); + verifyValueOrTypeInModule.completionListContains('mod1emod', `namespace ${mod1Dot}mod1emod`); verifyValueOrTypeInModule.completionListContains('mod2', 'namespace mod2'); verifyValueOrTypeInModule.completionListContains('shwcls', 'class shwcls'); @@ -295,12 +296,12 @@ function goToMarkAndGeneralVerify(marker: string, { isClassScope, isTypeLocation } // from mod1 -goToMarkAndGeneralVerify('mod1'); +goToMarkAndGeneralVerify('mod1', { insideMod1: true }); // from mod1 in type position -goToMarkAndGeneralVerify('mod1Type', { isTypeLocation: true }); +goToMarkAndGeneralVerify('mod1Type', { isTypeLocation: true, insideMod1: true }); // from function in mod1 -goToMarkAndGeneralVerify('function'); +goToMarkAndGeneralVerify('function', { insideMod1: true }); verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); @@ -310,34 +311,34 @@ goToMarkAndGeneralVerify('class', { isClassScope: true }); //verify.not.completionListContains('ceVar'); // from interface in mod1 -goToMarkAndGeneralVerify('interface'); +goToMarkAndGeneralVerify('interface', { insideMod1: true }); // from namespace in mod1 verifyNamespaceInMod1('namespace'); verifyNamespaceInMod1('namespaceType', /*isTypeLocation*/ true); function verifyNamespaceInMod1(marker: string, isTypeLocation?: boolean) { - goToMarkAndGeneralVerify(marker, { isTypeLocation }); + goToMarkAndGeneralVerify(marker, { isTypeLocation, insideMod1: true }); const { verifyValue, verifyType, verifyValueOrType, verifyNotValueOrType } = getVerify(isTypeLocation); verifyValue.completionListContains('m1X', 'var m1X: number'); verifyValue.completionListContains('m1Func', 'function m1Func(): void'); - verifyValue.completionListContains('m1eX', 'var mod1mod.m1eX: number'); - verifyValue.completionListContains('m1eFunc', 'function mod1mod.m1eFunc(): void'); + verifyValue.completionListContains('m1eX', 'var m1eX: number'); + verifyValue.completionListContains('m1eFunc', 'function m1eFunc(): void'); verifyType.completionListContains('m1Int', 'interface m1Int'); - verifyType.completionListContains('m1eInt', 'interface mod1mod.m1eInt'); + verifyType.completionListContains('m1eInt', 'interface m1eInt'); verifyValueOrType.completionListContains('m1Class', 'class m1Class'); - verifyValueOrType.completionListContains('m1eClass', 'class mod1mod.m1eClass'); + verifyValueOrType.completionListContains('m1eClass', 'class m1eClass'); verifyNotValueOrType.completionListContains('m1Mod', 'namespace m1Mod'); - verifyNotValueOrType.completionListContains('m1eMod', 'namespace mod1mod.m1eMod'); + verifyNotValueOrType.completionListContains('m1eMod', 'namespace m1eMod'); } // from exported function in mod1 -goToMarkAndGeneralVerify('exportedFunction'); +goToMarkAndGeneralVerify('exportedFunction', { insideMod1: true }); verify.completionListContains('bar', '(local var) bar: number'); verify.completionListContains('foob', '(local function) foob(): void'); @@ -347,27 +348,27 @@ verify.not.completionListContains('ceFunc'); verify.not.completionListContains('ceVar'); // from exported interface in mod1 -goToMarkAndGeneralVerify('exportedInterface'); +goToMarkAndGeneralVerify('exportedInterface', { insideMod1: true }); // from exported namespace in mod1 verifyExportedNamespace('exportedNamespace'); verifyExportedNamespace('exportedNamespaceType', /*isTypeLocation*/ true); function verifyExportedNamespace(marker: string, isTypeLocation?: boolean) { - goToMarkAndGeneralVerify(marker, { isTypeLocation }); + goToMarkAndGeneralVerify(marker, { isTypeLocation, insideMod1: true }); const { verifyValue, verifyType, verifyValueOrType, verifyNotValueOrType } = getVerify(isTypeLocation); verifyValue.completionListContains('mX', 'var mX: number'); verifyValue.completionListContains('mFunc', 'function mFunc(): void'); - verifyValue.completionListContains('meX', 'var mod1.mod1emod.meX: number'); - verifyValue.completionListContains('meFunc', 'function mod1.mod1emod.meFunc(): void'); + verifyValue.completionListContains('meX', 'var meX: number'); + verifyValue.completionListContains('meFunc', 'function meFunc(): void'); verifyType.completionListContains('mInt', 'interface mInt'); - verifyType.completionListContains('meInt', 'interface mod1.mod1emod.meInt'); + verifyType.completionListContains('meInt', 'interface meInt'); verifyValueOrType.completionListContains('mClass', 'class mClass'); - verifyValueOrType.completionListContains('meClass', 'class mod1.mod1emod.meClass'); + verifyValueOrType.completionListContains('meClass', 'class meClass'); verifyNotValueOrType.completionListContains('mMod', 'namespace mMod'); - verifyNotValueOrType.completionListContains('meMod', 'namespace mod1.mod1emod.meMod'); + verifyNotValueOrType.completionListContains('meMod', 'namespace meMod'); } // from extended namespace @@ -380,7 +381,7 @@ function verifyExtendedNamespace(marker: string, isTypeLocation?: boolean) { verifyValue.completionListContains('mod1evar', 'var mod1.mod1evar: number'); verifyValue.completionListContains('mod1efn', 'function mod1.mod1efn(): void'); - verifyValue.completionListContains('mod1eexvar', 'var mod1.mod1eexvar: number'); + verifyValue.completionListContains('mod1eexvar', 'var mod1eexvar: number'); verifyValue.completionListContains('mod3', 'namespace mod3'); verifyValue.completionListContains('shwvar', 'var shwvar: number'); verifyValue.completionListContains('shwfn', 'function shwfn(): void'); @@ -415,4 +416,4 @@ function verifyExtendedNamespace(marker: string, isTypeLocation?: boolean) { verify.not.completionListContains('sivar'); verify.not.completionListContains('sifn'); verify.not.completionListContains('mod2eexvar'); -} \ No newline at end of file +} diff --git a/tests/cases/fourslash/completionsDefaultExport.ts b/tests/cases/fourslash/completionsDefaultExport.ts new file mode 100644 index 0000000000..82bcefac40 --- /dev/null +++ b/tests/cases/fourslash/completionsDefaultExport.ts @@ -0,0 +1,11 @@ +/// + +// @Filename: /a.ts +////export default function f() {} + +// @Filename: /b.ts +////import * as a from "./a"; +////a./**/; + +goTo.marker(); +verify.completionListContains("default", "function f(): void"); diff --git a/tests/cases/fourslash/exportDefaultFunction.ts b/tests/cases/fourslash/exportDefaultFunction.ts index 859d9641ac..42ddced699 100644 --- a/tests/cases/fourslash/exportDefaultFunction.ts +++ b/tests/cases/fourslash/exportDefaultFunction.ts @@ -9,4 +9,4 @@ goTo.marker('1'); verify.completionListContains("func", "function func(): void", /*documentation*/ undefined, "function"); goTo.marker('2'); -verify.completionListContains("func", "function func(): void", /*documentation*/ undefined, "function"); \ No newline at end of file +verify.completionListContains("func", "function func(): void", /*documentation*/ undefined, "function"); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport02.ts b/tests/cases/fourslash/findAllRefsForDefaultExport02.ts index 558bbd2de8..6ad8b29b5c 100644 --- a/tests/cases/fourslash/findAllRefsForDefaultExport02.ts +++ b/tests/cases/fourslash/findAllRefsForDefaultExport02.ts @@ -15,8 +15,9 @@ const ranges = test.ranges(); const [r0, r1, r2, r3, r4] = ranges; const fnRanges = [r0, r1, r2, r3]; -verify.singleReferenceGroup("function DefaultExportedFunction(): () => typeof DefaultExportedFunction", fnRanges); +const fn = "function DefaultExportedFunction(): () => typeof DefaultExportedFunction"; +verify.singleReferenceGroup(fn, fnRanges); // The namespace and function do not merge, // so the namespace should be all alone. -verify.singleReferenceGroup("namespace DefaultExportedFunction", [r4]); +verify.singleReferenceGroup(`namespace DefaultExportedFunction\n${fn}`, [r4]); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport08.ts b/tests/cases/fourslash/findAllRefsForDefaultExport08.ts index b29060f609..5583ba34c9 100644 --- a/tests/cases/fourslash/findAllRefsForDefaultExport08.ts +++ b/tests/cases/fourslash/findAllRefsForDefaultExport08.ts @@ -10,6 +10,8 @@ ////namespace [|{| "isWriteAccess": true, "isDefinition": true |}DefaultExportedClass|] { ////} +verify.noErrors(); + // The namespace and class do not merge, // so the namespace should be all alone. -verify.singleReferenceGroup("namespace DefaultExportedClass"); +verify.singleReferenceGroup("class DefaultExportedClass\nnamespace DefaultExportedClass"); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 767701a2af..59cb881ab7 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -116,6 +116,7 @@ declare namespace FourSlashInterface { ranges(): Range[]; rangesByText(): ts.Map; markerByName(s: string): Marker; + symbolsInScope(range: Range): any[]; } class goTo { marker(name?: string | Marker): void; @@ -192,6 +193,7 @@ declare namespace FourSlashInterface { verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void; noReferences(markerNameOrRange?: string | Range): void; symbolAtLocation(startRange: Range, ...declarationRanges: Range[]): void; + typeOfSymbolAtLocation(range: Range, symbol: any, expected: string): void; /** * @deprecated, prefer 'referenceGroups' * Like `referencesAre`, but goes to `start` first. diff --git a/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts b/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts index 25e6cd4c68..54a80194f3 100644 --- a/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts +++ b/tests/cases/fourslash/quickInfoOnNarrowedTypeInModule.ts @@ -7,7 +7,7 @@ //// var num: number; //// var str: string; //// if (typeof /*1*/nonExportedStrOrNum === "number") { -//// num = /*2*/nonExportedStrOrNum; +//// num = /*2*/nonExportedStrOrNum; //// } //// else { //// str = /*3*/nonExportedStrOrNum.length; @@ -40,15 +40,15 @@ verify.completionListContains("nonExportedStrOrNum", "var nonExportedStrOrNum: s goTo.marker('4'); verify.quickInfoIs('var m.exportedStrOrNum: string | number'); -verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string | number"); +verify.completionListContains("exportedStrOrNum", "var exportedStrOrNum: string | number"); goTo.marker('5'); verify.quickInfoIs('var m.exportedStrOrNum: number'); -verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: number"); +verify.completionListContains("exportedStrOrNum", "var exportedStrOrNum: number"); goTo.marker('6'); verify.quickInfoIs('var m.exportedStrOrNum: string'); -verify.completionListContains("exportedStrOrNum", "var m.exportedStrOrNum: string"); +verify.completionListContains("exportedStrOrNum", "var exportedStrOrNum: string"); goTo.marker('7'); verify.quickInfoIs('var m.exportedStrOrNum: string | number'); diff --git a/tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts b/tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts new file mode 100644 index 0000000000..4d23babbd2 --- /dev/null +++ b/tests/cases/fourslash/typeOfSymbol_localSymbolOfExport.ts @@ -0,0 +1,12 @@ +/// + +////export function f() {} +////[|1|]; + +const ranges = test.ranges(); +const symbolsInScope = test.symbolsInScope(ranges[0]); +const f = symbolsInScope.find(s => s.name === "f"); +if (f === undefined) throw new Error("'f' not in scope"); +if (f.exportSymbol === undefined) throw new Error("Expected to get the local symbol"); + +verify.typeOfSymbolAtLocation(ranges[0], f, "() => void");