/// <summary> /// Returns whether or not the symbol is a type, or if it references a module that has exported types. /// </summary> /// <remarks> /// Note: This was ported directly from the TypeScript implementation. The name of the function /// isn't exactly representative of what it is doing. /// </remarks> private static bool SymbolCanBeReferencedAtTypeLocation(ISymbol symbol, ITypeChecker typeChecker) { symbol = symbol.ExportSymbol ?? symbol; symbol = SkipAlias(symbol, typeChecker); if ((symbol.Flags & SymbolFlags.Type) != SymbolFlags.None) { return(true); } if ((symbol.Flags & SymbolFlags.Module) != SymbolFlags.None) { var exportedSymbols = typeChecker.GetExportsOfModule(symbol); foreach (var exportedSymbol in exportedSymbols) { if (SymbolCanBeReferencedAtTypeLocation(exportedSymbol.Value, typeChecker)) { return(true); } } } return(false); }
/// <summary> /// Creates an array of symbols for a node whose parent is either a qualified name or a property access expression. /// </summary> /// <remarks> /// This was ported from the TypeScript version of the language server. /// </remarks> public static IEnumerable <ISymbol> GetTypeScriptMemberSymbols(INode node, ITypeChecker typeChecker) { var symbols = new HashSet <ISymbol>(); // If we are part of a type (say creating an interface and and assigning a type to a property) // interface Test { // myField: ImportedModuleQualifiedName.<Type> // }; // when we want to filter the symbols ot the types bool isTypeLocation = node.Parent != null && IsPartOfTypeNode(node.Parent); // This case handles when you are accessing a property out of // an import statement. // const foo = importFrom("").<Type or Value>. // NOTE: We don't really hit this case in our completion implementation // as it is already handled as part of the property access expression completion code. bool isRhsOfImportDeclaration = IsInRightSideOfImport(node); if (IsEntityNode(node)) { var symbol = typeChecker.GetSymbolAtLocation(node); if (symbol != null) { symbol = SkipAlias(symbol, typeChecker); if ((symbol.Flags & (SymbolFlags.Enum | SymbolFlags.Module)) != SymbolFlags.None) { var exportedSymbols = typeChecker.GetExportsOfModule(symbol); foreach (var exportedSymbol in exportedSymbols) { if (isRhsOfImportDeclaration) { if (typeChecker.IsValidPropertyAccess(node.Parent, exportedSymbol.Value.Name) || SymbolCanBeReferencedAtTypeLocation(exportedSymbol.Value, typeChecker)) { symbols.Add(exportedSymbol.Value); } } else if (isTypeLocation) { if (SymbolCanBeReferencedAtTypeLocation(exportedSymbol.Value, typeChecker)) { symbols.Add(exportedSymbol.Value); } } else { if (typeChecker.IsValidPropertyAccess(node.Parent, exportedSymbol.Value.Name)) { symbols.Add(exportedSymbol.Value); } } } // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). if (!isTypeLocation && symbol.Declarations != null && symbol.Declarations.Any(d => d.Kind != SyntaxKind.SourceFile && d.Kind != SyntaxKind.ModuleDeclaration && d.Kind != SyntaxKind.EnumDeclaration)) { symbols.AddRange(AddTypeProperties(typeChecker.GetTypeOfSymbolAtLocation(symbol, node), node, typeChecker)); } } } } // If we are not in a type location, and not handling module exports, then add the symbols for the // type of the "left side" (i.e. the "QualifiedName" in "QualifiedName."Access") that can be accessed // through QualifiedName (i.e. if the user types QualifiedName.<Completion happens here> what symbols // have valid property access from there). if (!isTypeLocation) { symbols.AddRange(AddTypeProperties(typeChecker.GetTypeAtLocation(node), node, typeChecker)); } return(symbols); }