Filter symbols based on the meaning at the location

This commit is contained in:
Sheetal Nandi
2017-05-24 15:24:49 -07:00
parent 928da675ac
commit 893ba1de15
34 changed files with 558 additions and 256 deletions

View File

@@ -14554,12 +14554,31 @@ namespace ts {
? (<PropertyAccessExpression>node).expression
: (<QualifiedName>node).left;
const type = checkExpression(left);
return isValidPropertyAccessWithType(node, left, propertyName, getWidenedType(checkExpression(left)));
}
function isValidPropertyAccessWithType(
node: PropertyAccessExpression | QualifiedName,
left: LeftHandSideExpression | QualifiedName,
propertyName: string,
type: Type): boolean {
if (type !== unknownType && !isTypeAny(type)) {
const prop = getPropertyOfType(getWidenedType(type), propertyName);
const prop = getPropertyOfType(type, propertyName);
if (prop) {
return checkPropertyAccessibility(node, left, type, prop);
}
// In js files properties of unions are allowed in completion
if (isInJavaScriptFile(left) && (type.flags & TypeFlags.Union)) {
for (const elementType of (<UnionType>type).types) {
if (isValidPropertyAccessWithType(node, left, propertyName, elementType)) {
return true;
}
}
}
return false;
}
return true;
}

View File

@@ -559,6 +559,9 @@ namespace ts.Completions {
isMemberCompletion = true;
isNewIdentifierLocation = false;
// Since this is qualified name check its a type node location
const isTypeLocation = isPartOfTypeNode(node.parent);
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression) {
let symbol = typeChecker.getSymbolAtLocation(node);
@@ -570,16 +573,24 @@ namespace ts.Completions {
if (symbol && symbol.flags & SymbolFlags.HasExports) {
// Extract module or enum members
const exportedSymbols = typeChecker.getExportsOfModule(symbol);
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name);
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) :
isTypeLocation ? isValidTypeAccess : isValidValueAccess;
forEach(exportedSymbols, symbol => {
if (typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name)) {
if (isValidAccess(symbol)) {
symbols.push(symbol);
}
});
}
}
const type = typeChecker.getTypeAtLocation(node);
addTypeProperties(type);
if (!isTypeLocation) {
const type = typeChecker.getTypeAtLocation(node);
addTypeProperties(type);
}
}
function addTypeProperties(type: Type) {
@@ -687,13 +698,88 @@ namespace ts.Completions {
isStatement(scopeNode);
}
/// TODO filter meaning based on the current context
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);
symbols = filterGlobalCompletion(typeChecker.getSymbolsInScope(scopeNode, symbolMeanings));
return true;
}
function filterGlobalCompletion(symbols: Symbol[]) {
return filter(symbols, symbol => {
if (!isSourceFile(location)) {
// export = /**/ here we want to get all meanings, so any symbol is ok
if (isExportAssignment(location.parent)) {
return true;
}
// This is an alias, follow what it aliases
if (symbol && symbol.flags & SymbolFlags.Alias) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
// import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace)
if (isInRightSideOfInternalImportEqualsDeclaration(location)) {
return !!(symbol.flags & SymbolFlags.Namespace);
}
if (!isContextTokenValueLocation(contextToken) &&
(isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken))) {
// Its a type, but you can reach it by namespace.type as well
return symbolCanbeReferencedAtTypeLocation(symbol);
}
}
// expressions are value space (which includes the value namespaces)
return !!(symbol.flags & SymbolFlags.Value);
});
}
function isContextTokenValueLocation(contextToken: Node) {
if (contextToken) {
const parentKind = contextToken.parent.kind;
switch (contextToken.kind) {
case SyntaxKind.TypeOfKeyword:
return parentKind === SyntaxKind.TypeQuery;
}
}
}
function isContextTokenTypeLocation(contextToken: Node) {
if (contextToken) {
const parentKind = contextToken.parent.kind;
switch (contextToken.kind) {
case SyntaxKind.ColonToken:
return parentKind === SyntaxKind.PropertyDeclaration ||
parentKind === SyntaxKind.PropertySignature ||
parentKind === SyntaxKind.Parameter ||
parentKind === SyntaxKind.VariableDeclaration ||
isFunctionLikeKind(parentKind);
case SyntaxKind.EqualsToken:
return parentKind === SyntaxKind.TypeAliasDeclaration;
case SyntaxKind.AsKeyword:
return parentKind === SyntaxKind.AsExpression;
}
}
}
function symbolCanbeReferencedAtTypeLocation(symbol: Symbol): boolean {
// This is an alias, follow what it aliases
if (symbol && symbol.flags & SymbolFlags.Alias) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
if (symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule)) {
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 !!(symbol.flags & (SymbolFlags.NamespaceModule | SymbolFlags.Type));
}
/**
* Finds the first node that "embraces" the position, so that one may
* accurately aggregate locals from the closest containing scope.
@@ -1126,21 +1212,6 @@ namespace ts.Completions {
return undefined;
}
function isFunction(kind: SyntaxKind): boolean {
if (!isFunctionLikeKind(kind)) {
return false;
}
switch (kind) {
case SyntaxKind.Constructor:
case SyntaxKind.ConstructorType:
case SyntaxKind.FunctionType:
return false;
default:
return true;
}
}
/**
* @returns true if we are certain that the currently edited location must define a new location; false otherwise.
*/
@@ -1152,7 +1223,7 @@ namespace ts.Completions {
containingNodeKind === SyntaxKind.VariableDeclarationList ||
containingNodeKind === SyntaxKind.VariableStatement ||
containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, |
isFunction(containingNodeKind) ||
isFunctionLikeButNotConstructor(containingNodeKind) ||
containingNodeKind === SyntaxKind.ClassDeclaration || // class A<T, |
containingNodeKind === SyntaxKind.ClassExpression || // var C = class D<T, |
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, |
@@ -1170,7 +1241,7 @@ namespace ts.Completions {
case SyntaxKind.OpenParenToken:
return containingNodeKind === SyntaxKind.CatchClause ||
isFunction(containingNodeKind);
isFunctionLikeButNotConstructor(containingNodeKind);
case SyntaxKind.OpenBraceToken:
return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { |
@@ -1188,7 +1259,7 @@ namespace ts.Completions {
containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< |
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< |
containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< |
isFunction(containingNodeKind);
isFunctionLikeKind(containingNodeKind);
case SyntaxKind.StaticKeyword:
return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(contextToken.parent.parent);
@@ -1257,6 +1328,10 @@ namespace ts.Completions {
return false;
}
function isFunctionLikeButNotConstructor(kind: SyntaxKind) {
return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor;
}
function isDotOfNumericLiteral(contextToken: Node): boolean {
if (contextToken.kind === SyntaxKind.NumericLiteral) {
const text = contextToken.getFullText();

View File

@@ -82,7 +82,7 @@ namespace ts {
else if (node.parent.kind === SyntaxKind.ExportAssignment) {
return SemanticMeaning.All;
}
else if (isInRightSideOfImport(node)) {
else if (isInRightSideOfInternalImportEqualsDeclaration(node)) {
return getMeaningFromRightHandSideOfImportEquals(node);
}
else if (isDeclarationName(node)) {
@@ -114,7 +114,7 @@ namespace ts {
return SemanticMeaning.Namespace;
}
function isInRightSideOfImport(node: Node) {
export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) {
while (node.parent.kind === SyntaxKind.QualifiedName) {
node = node.parent;
}