diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 0eb1071681a..08c1c788f63 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -23,6 +23,10 @@ namespace FourSlashInterface { return this.state.getRanges(); } + public rangesInFile(fileName?: string): FourSlash.Range[] { + return this.state.getRangesInFile(fileName); + } + public spans(): ts.TextSpan[] { return this.ranges().map(r => ts.createTextSpan(r.pos, r.end - r.pos)); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 789ff81f861..0fc816729ff 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -89,21 +89,30 @@ namespace ts { export function getMeaningFromLocation(node: Node): SemanticMeaning { node = getAdjustedReferenceLocation(node); + const parent = node.parent; if (node.kind === SyntaxKind.SourceFile) { return SemanticMeaning.Value; } - else if (node.parent.kind === SyntaxKind.ExportAssignment - || node.parent.kind === SyntaxKind.ExternalModuleReference - || node.parent.kind === SyntaxKind.ImportSpecifier - || node.parent.kind === SyntaxKind.ImportClause - || isImportEqualsDeclaration(node.parent) && node === node.parent.name) { + else if (isExportAssignment(parent) + || isExportSpecifier(parent) + || isExternalModuleReference(parent) + || isImportSpecifier(parent) + || isImportClause(parent) + || isImportEqualsDeclaration(parent) && node === parent.name) { + let decl: Node = parent; + while (decl) { + if (isImportEqualsDeclaration(decl) || isImportClause(decl) || isExportDeclaration(decl)) { + return decl.isTypeOnly ? SemanticMeaning.Type : SemanticMeaning.All; + } + decl = decl.parent; + } return SemanticMeaning.All; } else if (isInRightSideOfInternalImportEqualsDeclaration(node)) { return getMeaningFromRightHandSideOfImportEquals(node as Identifier); } else if (isDeclarationName(node)) { - return getMeaningFromDeclaration(node.parent); + return getMeaningFromDeclaration(parent); } else if (isEntityName(node) && findAncestor(node, or(isJSDocNameReference, isJSDocLinkLike, isJSDocMemberName))) { return SemanticMeaning.All; @@ -114,11 +123,11 @@ namespace ts { else if (isNamespaceReference(node)) { return SemanticMeaning.Namespace; } - else if (isTypeParameterDeclaration(node.parent)) { - Debug.assert(isJSDocTemplateTag(node.parent.parent)); // Else would be handled by isDeclarationName + else if (isTypeParameterDeclaration(parent)) { + Debug.assert(isJSDocTemplateTag(parent.parent)); // Else would be handled by isDeclarationName return SemanticMeaning.Type; } - else if (isLiteralTypeNode(node.parent)) { + else if (isLiteralTypeNode(parent)) { // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. return SemanticMeaning.Type | SemanticMeaning.Value; } diff --git a/tests/cases/fourslash/documentHighlightInTypeExport.ts b/tests/cases/fourslash/documentHighlightInTypeExport.ts new file mode 100644 index 00000000000..5a16a9bbc90 --- /dev/null +++ b/tests/cases/fourslash/documentHighlightInTypeExport.ts @@ -0,0 +1,55 @@ +/// + +// @Filename: /1.ts +//// type [|A|] = 1; +//// export { [|A|] as [|B|] }; + +{ + const [AType, AExport, asB] = test.rangesInFile("/1.ts"); + verify.documentHighlightsOf(AType, [AType, AExport, asB]); + verify.documentHighlightsOf(AExport, [AType, AExport, asB]); + verify.documentHighlightsOf(asB, [asB]); +} + +// @Filename: /2.ts +//// type [|A|] = 1; +//// let [|A|]: [|A|] = 1; +//// export { [|A|] as [|B|] }; + +{ // a little strange, but the the type/value namespaces work too + const [AType, ALet, ADecl, AExport, asB] = test.rangesInFile("/2.ts"); + verify.documentHighlightsOf(AType, [AType, ADecl, AExport, asB]); + verify.documentHighlightsOf(ADecl, [AType, ADecl, AExport, asB]); + verify.documentHighlightsOf(ALet, [ALet, AExport, asB]); + verify.documentHighlightsOf(AExport, [AType, ALet, ADecl, AExport, asB]); + verify.documentHighlightsOf(asB, [asB]); +} + +// @Filename: /3.ts +//// type [|A|] = 1; +//// let [|A|]: [|A|] = 1; +//// export type { [|A|] as [|B|] }; + +{ // properly handle type only + const [AType, ALet, ADecl, AExport, asB] = test.rangesInFile("/3.ts"); + verify.documentHighlightsOf(AType, [AType, ADecl, AExport, asB]); + verify.documentHighlightsOf(ADecl, [AType, ADecl, AExport, asB]); + verify.documentHighlightsOf(AExport, [AType, ADecl, AExport, asB]); + verify.documentHighlightsOf(ALet, [ALet]); + verify.documentHighlightsOf(asB, [asB]); +} + +// would be nice if this could work the same for imports too, but getSymbolAtLocation() +// of the imported symbol (when aliased) returns undefined + +// // @Filename: /4.ts +// //// import type { [|Tee|] as [|T|] } from "whatEveh"; +// //// let [|T|]: [|T|]; +// +// { +// const [TeeImport, asT, TLet, TDecl] = test.rangesInFile("/4.ts"); +// verify.documentHighlightsOf(TeeImport, [TeeImport, asT, TDecl]); +// // verify.documentHighlightsOf(asT, [TeeImport, asT, TDecl]); +// // verify.documentHighlightsOf(TDecl, [TeeImport, asT, TDecl]); +// // verify.documentHighlightsOf(TLet, [TLet]); +// } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 26c3c220b72..2b4a197a3cb 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -187,6 +187,7 @@ declare namespace FourSlashInterface { markerName(m: Marker): string; marker(name?: string): Marker; ranges(): Range[]; + rangesInFile(fileName?: string): Range[]; spans(): Array<{ start: number, length: number }>; rangesByText(): ts.Map; markerByName(s: string): Marker;