Skip to content

Commit a81a645

Browse files
authored
Enable member completions for import types (#23085)
* Enable member completions for import types * Add missing semicolon * Compact statements * Ye doth compacteth too much * Handle class accesibility
1 parent 9a3e2fc commit a81a645

File tree

8 files changed

+97
-19
lines changed

8 files changed

+97
-19
lines changed

src/compiler/checker.ts

+21-11
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ namespace ts {
213213
return node ? getConstantValue(node) : undefined;
214214
},
215215
isValidPropertyAccess: (node, propertyName) => {
216-
node = getParseTreeNode(node, isPropertyAccessOrQualifiedName);
216+
node = getParseTreeNode(node, isPropertyAccessOrQualifiedNameOrImportTypeNode);
217217
return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName));
218218
},
219219
isValidPropertyAccessForCompletions: (node, type, property) => {
@@ -8550,7 +8550,7 @@ namespace ts {
85508550
return links.resolvedType = unknownType;
85518551
}
85528552
const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false);
8553-
if (node.qualifier) {
8553+
if (!nodeIsMissing(node.qualifier)) {
85548554
const nameStack: Identifier[] = getIdentifierChain(node.qualifier);
85558555
let currentNamespace = moduleSymbol;
85568556
let current: Identifier | undefined;
@@ -16240,11 +16240,13 @@ namespace ts {
1624016240
* @param type The type of left.
1624116241
* @param prop The symbol for the right hand side of the property access.
1624216242
*/
16243-
function checkPropertyAccessibility(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration, left: Expression | QualifiedName, type: Type, prop: Symbol): boolean {
16243+
function checkPropertyAccessibility(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration | ImportTypeNode, left: Expression | QualifiedName | ImportTypeNode, type: Type, prop: Symbol): boolean {
1624416244
const flags = getDeclarationModifierFlagsFromSymbol(prop);
1624516245
const errorNode = node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.VariableDeclaration ?
1624616246
node.name :
16247-
(<QualifiedName>node).right;
16247+
node.kind === SyntaxKind.ImportTypeNode ?
16248+
node :
16249+
(<QualifiedName>node).right;
1624816250

1624916251
if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) {
1625016252
// Synthetic property with private constituent property
@@ -16683,13 +16685,19 @@ namespace ts {
1668316685
(getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop).isReferenced = SymbolFlags.All;
1668416686
}
1668516687

16686-
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: __String): boolean {
16687-
const left = node.kind === SyntaxKind.PropertyAccessExpression ? node.expression : node.left;
16688-
return isValidPropertyAccessWithType(node, left, propertyName, getWidenedType(checkExpression(left)));
16688+
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean {
16689+
switch (node.kind) {
16690+
case SyntaxKind.PropertyAccessExpression:
16691+
return isValidPropertyAccessWithType(node, node.expression, propertyName, getWidenedType(checkExpression(node.expression)));
16692+
case SyntaxKind.QualifiedName:
16693+
return isValidPropertyAccessWithType(node, node.left, propertyName, getWidenedType(checkExpression(node.left)));
16694+
case SyntaxKind.ImportTypeNode:
16695+
return isValidPropertyAccessWithType(node, node, propertyName, getTypeFromTypeNode(node));
16696+
}
1668916697
}
1669016698

16691-
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression, type: Type, property: Symbol): boolean {
16692-
return isValidPropertyAccessWithType(node, node.expression, property.escapedName, type)
16699+
function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode, type: Type, property: Symbol): boolean {
16700+
return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.ImportTypeNode ? node : node.expression, property.escapedName, type)
1669316701
&& (!(property.flags & SymbolFlags.Method) || isValidMethodAccess(property, type));
1669416702
}
1669516703
function isValidMethodAccess(method: Symbol, actualThisType: Type): boolean {
@@ -16711,8 +16719,8 @@ namespace ts {
1671116719
}
1671216720

1671316721
function isValidPropertyAccessWithType(
16714-
node: PropertyAccessExpression | QualifiedName,
16715-
left: LeftHandSideExpression | QualifiedName,
16722+
node: PropertyAccessExpression | QualifiedName | ImportTypeNode,
16723+
left: LeftHandSideExpression | QualifiedName | ImportTypeNode,
1671616724
propertyName: __String,
1671716725
type: Type): boolean {
1671816726

@@ -25336,6 +25344,8 @@ namespace ts {
2533625344
case SyntaxKind.FunctionKeyword:
2533725345
case SyntaxKind.EqualsGreaterThanToken:
2533825346
return getSymbolOfNode(node.parent);
25347+
case SyntaxKind.ImportTypeNode:
25348+
return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined;
2533925349

2534025350
default:
2534125351
return undefined;

src/compiler/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2890,9 +2890,9 @@ namespace ts {
28902890
/* @internal */ getMergedSymbol(symbol: Symbol): Symbol;
28912891

28922892
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
2893-
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
2893+
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
28942894
/** Exclude accesses to private properties or methods with a `this` parameter that `type` doesn't satisfy. */
2895-
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression, type: Type, property: Symbol): boolean;
2895+
/* @internal */ isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode, type: Type, property: Symbol): boolean;
28962896
/** Follow all aliases to get the original symbol. */
28972897
getAliasedSymbol(symbol: Symbol): Symbol;
28982898
/** Follow a *single* alias to get the immediately aliased symbol. */

src/compiler/utilities.ts

+11
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,9 @@ namespace ts {
811811
if (parent.kind === SyntaxKind.TypeQuery) {
812812
return false;
813813
}
814+
if (parent.kind === SyntaxKind.ImportTypeNode) {
815+
return !(parent as ImportTypeNode).isTypeOf;
816+
}
814817
// Do not recursively call isPartOfTypeNode on the parent. In the example:
815818
//
816819
// let a: A.B.C;
@@ -5728,6 +5731,14 @@ namespace ts {
57285731
return false;
57295732
}
57305733

5734+
/* @internal */
5735+
export function isPropertyAccessOrQualifiedNameOrImportTypeNode(node: Node): node is PropertyAccessExpression | QualifiedName | ImportTypeNode {
5736+
const kind = node.kind;
5737+
return kind === SyntaxKind.PropertyAccessExpression
5738+
|| kind === SyntaxKind.QualifiedName
5739+
|| kind === SyntaxKind.ImportTypeNode;
5740+
}
5741+
57315742
// Expression
57325743

57335744
export function isPropertyAccessOrQualifiedName(node: Node): node is PropertyAccessExpression | QualifiedName {

src/harness/tsconfig.json

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
"../services/navigationBar.ts",
5555
"../services/outliningElementsCollector.ts",
5656
"../services/patternMatcher.ts",
57+
"../services/pathCompletions.ts",
58+
"../services/completions.ts",
5759
"../services/services.ts",
5860
"../services/shims.ts",
5961
"../services/signatureHelp.ts",

src/services/completions.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,9 @@ namespace ts.Completions {
881881
case SyntaxKind.QualifiedName:
882882
node = (parent as QualifiedName).left;
883883
break;
884+
case SyntaxKind.ImportTypeNode:
885+
node = parent;
886+
break;
884887
default:
885888
// There is nothing that precedes the dot, so this likely just a stray character
886889
// or leading into a '...' token. Just bail out instead.
@@ -1012,18 +1015,19 @@ namespace ts.Completions {
10121015
completionKind = CompletionKind.PropertyAccess;
10131016

10141017
// Since this is qualified name check its a type node location
1015-
const isTypeLocation = insideJsDocTagTypeExpression || isPartOfTypeNode(node.parent);
1018+
const isImportType = isLiteralImportTypeNode(node);
1019+
const isTypeLocation = insideJsDocTagTypeExpression || (isImportType && !(node as ImportTypeNode).isTypeOf) || isPartOfTypeNode(node.parent);
10161020
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
10171021
const allowTypeOrValue = isRhsOfImportDeclaration || (!isTypeLocation && isPossiblyTypeArgumentPosition(contextToken, sourceFile));
1018-
if (isEntityName(node)) {
1022+
if (isEntityName(node) || isImportType) {
10191023
let symbol = typeChecker.getSymbolAtLocation(node);
10201024
if (symbol) {
10211025
symbol = skipAlias(symbol, typeChecker);
10221026

10231027
if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) {
10241028
// Extract module or enum members
10251029
const exportedSymbols = Debug.assertEachDefined(typeChecker.getExportsOfModule(symbol), "getExportsOfModule() should all be defined");
1026-
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name);
1030+
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? <ImportTypeNode>node : <PropertyAccessExpression>(node.parent), symbol.name);
10271031
const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol);
10281032
const isValidAccess = allowTypeOrValue ?
10291033
// Any kind is allowed when dotting off namespace in internal import equals declaration
@@ -1063,7 +1067,7 @@ namespace ts.Completions {
10631067
}
10641068
else {
10651069
for (const symbol of type.getApparentProperties()) {
1066-
if (typeChecker.isValidPropertyAccessForCompletions(<PropertyAccessExpression>(node.parent), type, symbol)) {
1070+
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportTypeNode ? <ImportTypeNode>node : <PropertyAccessExpression>node.parent, type, symbol)) {
10671071
addPropertySymbol(symbol);
10681072
}
10691073
}

tests/baselines/reference/api/tsserverlibrary.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1821,7 +1821,7 @@ declare namespace ts {
18211821
isArgumentsSymbol(symbol: Symbol): boolean;
18221822
isUnknownSymbol(symbol: Symbol): boolean;
18231823
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
1824-
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
1824+
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
18251825
/** Follow all aliases to get the original symbol. */
18261826
getAliasedSymbol(symbol: Symbol): Symbol;
18271827
getExportsOfModule(moduleSymbol: Symbol): Symbol[];

tests/baselines/reference/api/typescript.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1821,7 +1821,7 @@ declare namespace ts {
18211821
isArgumentsSymbol(symbol: Symbol): boolean;
18221822
isUnknownSymbol(symbol: Symbol): boolean;
18231823
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
1824-
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean;
1824+
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
18251825
/** Follow all aliases to get the original symbol. */
18261826
getAliasedSymbol(symbol: Symbol): Symbol;
18271827
getExportsOfModule(moduleSymbol: Symbol): Symbol[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @Filename: /ns.ts
4+
////export namespace Foo {
5+
//// export namespace Bar {
6+
//// export class Baz {}
7+
//// export interface Bat {}
8+
//// export const a: number;
9+
//// const b: string;
10+
//// }
11+
////}
12+
13+
// @Filename: /top.ts
14+
////export interface Bat {}
15+
////export const a: number;
16+
17+
// @Filename: /equals.ts
18+
////class Foo {
19+
//// public static bar: string;
20+
//// private static baz: number;
21+
////}
22+
////export = Foo;
23+
24+
// @Filename: /usage1.ts
25+
////type A = typeof import("./ns")./*1*/
26+
// @Filename: /usage2.ts
27+
////type B = typeof import("./ns").Foo./*2*/
28+
// @Filename: /usage3.ts
29+
////type C = typeof import("./ns").Foo.Bar./*3*/
30+
// @Filename: /usage4.ts
31+
////type D = import("./ns")./*4*/
32+
// @Filename: /usage5.ts
33+
////type E = import("./ns").Foo./*5*/
34+
// @Filename: /usage6.ts
35+
////type F = import("./ns").Foo.Bar./*6*/
36+
// @Filename: /usage7.ts
37+
////type G = typeof import("./top")./*7*/
38+
// @Filename: /usage8.ts
39+
////type H = import("./top")./*8*/
40+
// @Filename: /usage9.ts
41+
////type H = typeof import("./equals")./*9*/
42+
43+
verify.completionsAt("1", ["Foo"]);
44+
verify.completionsAt("2", ["Bar"]);
45+
verify.completionsAt("3", ["Baz", "a"]);
46+
verify.completionsAt("4", ["Foo"]);
47+
verify.completionsAt("5", ["Bar"]);
48+
verify.completionsAt("6", ["Baz", "Bat"]);
49+
verify.completionsAt("7", ["a"]);
50+
verify.completionsAt("8", ["Bat"]);
51+
verify.completionsAt("9", ["prototype", "bar"]);

0 commit comments

Comments
 (0)