Skip to content

Commit 893ba1d

Browse files
committed
Filter symbols based on the meaning at the location
1 parent 928da67 commit 893ba1d

34 files changed

+563
-261
lines changed

Jakefile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,7 @@ var nodeServerInFile = "tests/webTestServer.ts";
954954
compileFile(nodeServerOutFile, [nodeServerInFile], [builtLocalDirectory, tscFile], [], /*useBuiltCompiler:*/ true, { noOutFile: true, lib: "es6" });
955955

956956
desc("Runs browserify on run.js to produce a file suitable for running tests in the browser");
957-
task("browserify", ["tests", builtLocalDirectory, nodeServerOutFile], function() {
957+
task("browserify", ["tests", run, builtLocalDirectory, nodeServerOutFile], function() {
958958
var cmd = 'browserify built/local/run.js -t ./scripts/browserify-optional -d -o built/local/bundle.js';
959959
exec(cmd);
960960
}, { async: true });

src/compiler/checker.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14554,12 +14554,31 @@ namespace ts {
1455414554
? (<PropertyAccessExpression>node).expression
1455514555
: (<QualifiedName>node).left;
1455614556

14557-
const type = checkExpression(left);
14557+
return isValidPropertyAccessWithType(node, left, propertyName, getWidenedType(checkExpression(left)));
14558+
}
14559+
14560+
function isValidPropertyAccessWithType(
14561+
node: PropertyAccessExpression | QualifiedName,
14562+
left: LeftHandSideExpression | QualifiedName,
14563+
propertyName: string,
14564+
type: Type): boolean {
14565+
1455814566
if (type !== unknownType && !isTypeAny(type)) {
14559-
const prop = getPropertyOfType(getWidenedType(type), propertyName);
14567+
const prop = getPropertyOfType(type, propertyName);
1456014568
if (prop) {
1456114569
return checkPropertyAccessibility(node, left, type, prop);
1456214570
}
14571+
14572+
// In js files properties of unions are allowed in completion
14573+
if (isInJavaScriptFile(left) && (type.flags & TypeFlags.Union)) {
14574+
for (const elementType of (<UnionType>type).types) {
14575+
if (isValidPropertyAccessWithType(node, left, propertyName, elementType)) {
14576+
return true;
14577+
}
14578+
}
14579+
}
14580+
14581+
return false;
1456314582
}
1456414583
return true;
1456514584
}

src/services/completions.ts

Lines changed: 98 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ namespace ts.Completions {
559559
isMemberCompletion = true;
560560
isNewIdentifierLocation = false;
561561

562+
// Since this is qualified name check its a type node location
563+
const isTypeLocation = isPartOfTypeNode(node.parent);
564+
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
562565
if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) {
563566
let symbol = typeChecker.getSymbolAtLocation(node);
564567

@@ -570,16 +573,24 @@ namespace ts.Completions {
570573
if (symbol && symbol.flags & SymbolFlags.HasExports) {
571574
// Extract module or enum members
572575
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
576+
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name);
577+
const isValidTypeAccess = (symbol: Symbol) => symbolCanbeReferencedAtTypeLocation(symbol);
578+
const isValidAccess = isRhsOfImportDeclaration ?
579+
// Any kind is allowed when dotting off namespace in internal import equals declaration
580+
(symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) :
581+
isTypeLocation ? isValidTypeAccess : isValidValueAccess;
573582
forEach(exportedSymbols, symbol => {
574-
if (typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name)) {
583+
if (isValidAccess(symbol)) {
575584
symbols.push(symbol);
576585
}
577586
});
578587
}
579588
}
580589

581-
const type = typeChecker.getTypeAtLocation(node);
582-
addTypeProperties(type);
590+
if (!isTypeLocation) {
591+
const type = typeChecker.getTypeAtLocation(node);
592+
addTypeProperties(type);
593+
}
583594
}
584595

585596
function addTypeProperties(type: Type) {
@@ -687,13 +698,88 @@ namespace ts.Completions {
687698
isStatement(scopeNode);
688699
}
689700

690-
/// TODO filter meaning based on the current context
691701
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
692-
symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);
702+
symbols = filterGlobalCompletion(typeChecker.getSymbolsInScope(scopeNode, symbolMeanings));
693703

694704
return true;
695705
}
696706

707+
function filterGlobalCompletion(symbols: Symbol[]) {
708+
return filter(symbols, symbol => {
709+
if (!isSourceFile(location)) {
710+
// export = /**/ here we want to get all meanings, so any symbol is ok
711+
if (isExportAssignment(location.parent)) {
712+
return true;
713+
}
714+
715+
// This is an alias, follow what it aliases
716+
if (symbol && symbol.flags & SymbolFlags.Alias) {
717+
symbol = typeChecker.getAliasedSymbol(symbol);
718+
}
719+
720+
// import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace)
721+
if (isInRightSideOfInternalImportEqualsDeclaration(location)) {
722+
return !!(symbol.flags & SymbolFlags.Namespace);
723+
}
724+
725+
if (!isContextTokenValueLocation(contextToken) &&
726+
(isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken))) {
727+
// Its a type, but you can reach it by namespace.type as well
728+
return symbolCanbeReferencedAtTypeLocation(symbol);
729+
}
730+
}
731+
732+
// expressions are value space (which includes the value namespaces)
733+
return !!(symbol.flags & SymbolFlags.Value);
734+
});
735+
}
736+
737+
function isContextTokenValueLocation(contextToken: Node) {
738+
if (contextToken) {
739+
const parentKind = contextToken.parent.kind;
740+
switch (contextToken.kind) {
741+
case SyntaxKind.TypeOfKeyword:
742+
return parentKind === SyntaxKind.TypeQuery;
743+
}
744+
}
745+
}
746+
747+
function isContextTokenTypeLocation(contextToken: Node) {
748+
if (contextToken) {
749+
const parentKind = contextToken.parent.kind;
750+
switch (contextToken.kind) {
751+
case SyntaxKind.ColonToken:
752+
return parentKind === SyntaxKind.PropertyDeclaration ||
753+
parentKind === SyntaxKind.PropertySignature ||
754+
parentKind === SyntaxKind.Parameter ||
755+
parentKind === SyntaxKind.VariableDeclaration ||
756+
isFunctionLikeKind(parentKind);
757+
758+
case SyntaxKind.EqualsToken:
759+
return parentKind === SyntaxKind.TypeAliasDeclaration;
760+
761+
case SyntaxKind.AsKeyword:
762+
return parentKind === SyntaxKind.AsExpression;
763+
}
764+
}
765+
}
766+
767+
function symbolCanbeReferencedAtTypeLocation(symbol: Symbol): boolean {
768+
// This is an alias, follow what it aliases
769+
if (symbol && symbol.flags & SymbolFlags.Alias) {
770+
symbol = typeChecker.getAliasedSymbol(symbol);
771+
}
772+
773+
if (symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule)) {
774+
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
775+
// If the exported symbols contains type,
776+
// symbol can be referenced at locations where type is allowed
777+
return forEach(exportedSymbols, symbolCanbeReferencedAtTypeLocation);
778+
}
779+
780+
return !!(symbol.flags & (SymbolFlags.NamespaceModule | SymbolFlags.Type));
781+
}
782+
697783
/**
698784
* Finds the first node that "embraces" the position, so that one may
699785
* accurately aggregate locals from the closest containing scope.
@@ -1126,21 +1212,6 @@ namespace ts.Completions {
11261212
return undefined;
11271213
}
11281214

1129-
function isFunction(kind: SyntaxKind): boolean {
1130-
if (!isFunctionLikeKind(kind)) {
1131-
return false;
1132-
}
1133-
1134-
switch (kind) {
1135-
case SyntaxKind.Constructor:
1136-
case SyntaxKind.ConstructorType:
1137-
case SyntaxKind.FunctionType:
1138-
return false;
1139-
default:
1140-
return true;
1141-
}
1142-
}
1143-
11441215
/**
11451216
* @returns true if we are certain that the currently edited location must define a new location; false otherwise.
11461217
*/
@@ -1152,7 +1223,7 @@ namespace ts.Completions {
11521223
containingNodeKind === SyntaxKind.VariableDeclarationList ||
11531224
containingNodeKind === SyntaxKind.VariableStatement ||
11541225
containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, |
1155-
isFunction(containingNodeKind) ||
1226+
isFunctionLikeButNotConstructor(containingNodeKind) ||
11561227
containingNodeKind === SyntaxKind.ClassDeclaration || // class A<T, |
11571228
containingNodeKind === SyntaxKind.ClassExpression || // var C = class D<T, |
11581229
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, |
@@ -1170,7 +1241,7 @@ namespace ts.Completions {
11701241

11711242
case SyntaxKind.OpenParenToken:
11721243
return containingNodeKind === SyntaxKind.CatchClause ||
1173-
isFunction(containingNodeKind);
1244+
isFunctionLikeButNotConstructor(containingNodeKind);
11741245

11751246
case SyntaxKind.OpenBraceToken:
11761247
return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { |
@@ -1188,7 +1259,7 @@ namespace ts.Completions {
11881259
containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< |
11891260
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< |
11901261
containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< |
1191-
isFunction(containingNodeKind);
1262+
isFunctionLikeKind(containingNodeKind);
11921263

11931264
case SyntaxKind.StaticKeyword:
11941265
return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(contextToken.parent.parent);
@@ -1257,6 +1328,10 @@ namespace ts.Completions {
12571328
return false;
12581329
}
12591330

1331+
function isFunctionLikeButNotConstructor(kind: SyntaxKind) {
1332+
return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor;
1333+
}
1334+
12601335
function isDotOfNumericLiteral(contextToken: Node): boolean {
12611336
if (contextToken.kind === SyntaxKind.NumericLiteral) {
12621337
const text = contextToken.getFullText();

src/services/utilities.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ namespace ts {
8282
else if (node.parent.kind === SyntaxKind.ExportAssignment) {
8383
return SemanticMeaning.All;
8484
}
85-
else if (isInRightSideOfImport(node)) {
85+
else if (isInRightSideOfInternalImportEqualsDeclaration(node)) {
8686
return getMeaningFromRightHandSideOfImportEquals(node);
8787
}
8888
else if (isDeclarationName(node)) {
@@ -114,7 +114,7 @@ namespace ts {
114114
return SemanticMeaning.Namespace;
115115
}
116116

117-
function isInRightSideOfImport(node: Node) {
117+
export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) {
118118
while (node.parent.kind === SyntaxKind.QualifiedName) {
119119
node = node.parent;
120120
}

tests/cases/fourslash/commentsInheritance.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ verify.quickInfos({
346346
});
347347

348348
goTo.marker('16');
349-
verify.completionListContains("i1", "interface i1", "i1 is interface with properties");
349+
verify.not.completionListContains("i1", "interface i1", "i1 is interface with properties");
350350
verify.completionListContains("i1_i", "var i1_i: i1", "");
351351
verify.completionListContains("c1", "class c1", "");
352352
verify.completionListContains("c1_i", "var c1_i: c1", "");
@@ -603,9 +603,9 @@ verify.quickInfos({
603603
});
604604

605605
goTo.marker('51');
606-
verify.completionListContains("i2", "interface i2", "");
606+
verify.not.completionListContains("i2", "interface i2", "");
607607
verify.completionListContains("i2_i", "var i2_i: i2", "");
608-
verify.completionListContains("i3", "interface i3", "");
608+
verify.not.completionListContains("i3", "interface i3", "");
609609
verify.completionListContains("i3_i", "var i3_i: i3", "");
610610

611611
goTo.marker('51i');

tests/cases/fourslash/commentsInterface.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ verify.currentParameterHelpArgumentDocCommentIs("");
163163
verify.quickInfoAt("33q", "(method) i2.nc_fnfoo(b: number): string");
164164

165165
goTo.marker('34');
166-
verify.completionListContains("i1", "interface i1", "this is interface 1");
166+
verify.not.completionListContains("i1", "interface i1", "this is interface 1");
167167
verify.completionListContains("i1_i", "var i1_i: i1", "");
168-
verify.completionListContains("nc_i1", "interface nc_i1", "");
168+
verify.not.completionListContains("nc_i1", "interface nc_i1", "");
169169
verify.completionListContains("nc_i1_i", "var nc_i1_i: nc_i1", "");
170-
verify.completionListContains("i2", "interface i2", "this is interface 2 with memebers");
170+
verify.not.completionListContains("i2", "interface i2", "this is interface 2 with memebers");
171171
verify.completionListContains("i2_i", "var i2_i: i2", "");
172172
verify.completionListContains("i2_i_x", "var i2_i_x: number", "");
173173
verify.completionListContains("i2_i_foo", "var i2_i_foo: (b: number) => string", "");
@@ -194,7 +194,7 @@ verify.completionListContains("a", "(parameter) a: number", "i3_i a");
194194

195195
verify.quickInfoAt("40q", "var i3_i: i3");
196196
goTo.marker('40');
197-
verify.completionListContains("i3", "interface i3", "");
197+
verify.not.completionListContains("i3", "interface i3", "");
198198
verify.completionListContains("i3_i", "var i3_i: i3", "");
199199

200200
goTo.marker('41');

tests/cases/fourslash/commentsOverloads.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,13 +295,13 @@ verify.completionListContains('f3', 'function f3(a: number): number (+1 overload
295295
verify.completionListContains('f4', 'function f4(a: number): number (+1 overload)', 'this is signature 4 - with number parameter');
296296

297297
goTo.marker('18');
298-
verify.completionListContains('i1', 'interface i1', '');
298+
verify.not.completionListContains('i1', 'interface i1', '');
299299
verify.completionListContains('i1_i', 'var i1_i: new i1(b: number) => any (+1 overload)', '');
300-
verify.completionListContains('i2', 'interface i2', '');
300+
verify.not.completionListContains('i2', 'interface i2', '');
301301
verify.completionListContains('i2_i', 'var i2_i: new i2(a: string) => any (+1 overload)', '');
302-
verify.completionListContains('i3', 'interface i3', '');
302+
verify.not.completionListContains('i3', 'interface i3', '');
303303
verify.completionListContains('i3_i', 'var i3_i: new i3(a: string) => any (+1 overload)', 'new 1');
304-
verify.completionListContains('i4', 'interface i4', '');
304+
verify.not.completionListContains('i4', 'interface i4', '');
305305
verify.completionListContains('i4_i', 'var i4_i: new i4(a: string) => any (+1 overload)', '');
306306

307307
goTo.marker('19');

tests/cases/fourslash/completionListAfterFunction2.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ goTo.marker("1");
99
verify.not.completionListContains("a");
1010

1111
goTo.marker("2");
12+
verify.not.completionListContains("b");
13+
edit.insert("typeof ");
1214
verify.completionListContains("b");
13-

tests/cases/fourslash/completionListCladule.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//// export var x: number;
99
////}
1010
////Foo/*c1*/; // should get "x", "prototype"
11-
////var s: Foo/*c2*/; // should get "x" and "prototype"
11+
////var s: Foo/*c2*/; // no types, in Foo, so shouldnt have anything
1212
////var f = new Foo();
1313
////f/*c3*/;
1414

@@ -20,9 +20,7 @@ verify.completionListContains("staticMethod");
2020

2121
goTo.marker("c2");
2222
edit.insert(".");
23-
verify.completionListContains("x");
24-
verify.completionListContains("staticMethod");
25-
verify.completionListContains("prototype");
23+
verify.completionListIsEmpty();
2624

2725
goTo.marker("c3");
2826
edit.insert(".");

tests/cases/fourslash/completionListInClassExpressionWithTypeParameter.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
///<reference path="fourslash.ts" />
1+
///<reference path="fourslash.ts" />
22

33
//// var x = class myClass <TypeParam> {
44
//// getClassName (){
55
//// /*0*/
6+
//// var tmp: /*0Type*/;
67
//// }
78
//// prop: Ty/*1*/
89
//// }
910

1011
goTo.marker("0");
12+
verify.not.completionListContains("TypeParam", "(type parameter) TypeParam in myClass<TypeParam>", /*documentation*/ undefined, "type parameter");
13+
goTo.marker("0Type");
1114
verify.completionListContains("TypeParam", "(type parameter) TypeParam in myClass<TypeParam>", /*documentation*/ undefined, "type parameter");
1215

1316
goTo.marker("1");

0 commit comments

Comments
 (0)