public static IType GetTypeOfFirstVariableDeclarationOrDefault(this IVariableStatement variableStatement, ITypeChecker typeChecker) { var firstDeclaration = GetFirstDeclarationOrDefault(variableStatement); if (firstDeclaration == null) { return(null); } return(typeChecker.GetTypeAtLocation(firstDeclaration)); }
/// <summary> /// Attempts to add a file to a source file list. The conditions that must be met are specified in the /// array of <paramref name="configurations"/> /// </summary> public static bool TryAddSourceFileToSourceFile(ITypeChecker checker, ISourceFile sourceFile, string sourceFileName, Workspace workspace, PathTable pathTable, AddSourceFileConfiguration[] configurations) { INode sourcesNode = null; try { // Use single or default to ensure that we only match a single sources property. // If we find more than one, we don't know which source file list to augment. // SingleOrDefault throws an InvalidOperationException if it finds more than one element // and returns default<T> if there are 0. sourcesNode = NodeWalker.TraverseBreadthFirstAndSelf(sourceFile).SingleOrDefault(node => { // We expect that the property for the source file list to be in an object literal // and hence be a property assignment inside that object literal. // The statement will look something like: // const result = TargetType.build( { sources: [f`foo.cpp`] } ); if (node.Kind == SyntaxKind.PropertyAssignment && node.Cast <IPropertyAssignment>().Name.Kind == SyntaxKind.Identifier && node.Parent?.Kind == SyntaxKind.ObjectLiteralExpression) { var propertyName = node.Cast <IPropertyAssignment>().Name.Text; // Now check the configurations to see if the any match as there // can be different names (such as "references", "sources", etc.) as // well as different functions, etc. AddSourceFileConfiguration singleConfiguration = null; try { // We use single or default to ensure that only one matching configuration is found. // SingleOrDefault throws an InvalidOperationException if it finds more than one element // and returns default<T> if there are 0. singleConfiguration = configurations.SingleOrDefault(configuration => { // Check to see if this is the correct property name. if (propertyName != configuration.PropertyName) { return(false); } // Now we will try to find the matching call expression (function name) // The reason we are going to walk parent nodes is that we allow // a "merge" or "override" to be nested inside the function call // as long as the argument type and the expected module the type exists // in match the configuration parameter. var nodeParent = node.Parent.Parent; while (nodeParent != null) { if (nodeParent.Kind != SyntaxKind.CallExpression) { return(false); } var callExpression = nodeParent.Cast <ICallExpression>(); string calledFunction = string.Empty; // Depending on the module the function is being called from it may be a straight // call (such as "build()") or it could be an accessor if it was imported // from another module (such as "StaticLibrary.build()"). if (callExpression.Expression?.Kind == SyntaxKind.PropertyAccessExpression) { var propertyAccessExpression = callExpression.Expression.Cast <IPropertyAccessExpression>(); calledFunction = propertyAccessExpression.Name?.Text; } else if (callExpression.Expression?.Kind == SyntaxKind.Identifier) { calledFunction = callExpression.Expression.Cast <Identifier>().Text; } else { return(false); } // If the called function matches, and has the minimum number of parameters to contain our argument type // then verify it matches the type name given in the configuration. if (calledFunction == configuration.FunctionName && callExpression.Arguments?.Length > configuration.ArgumentPosition) { var type = checker.GetContextualType(callExpression.Arguments[configuration.ArgumentPosition]); if (type != null && IsTypeCorrectForAddSourceFileConfiguration(type, workspace, pathTable, configuration)) { return(true); } } else if (DScriptUtilities.IsMergeOrOverrideCallExpression(callExpression)) { // In the case of a merge or override function, we make sure it is the proper type and keep moving // up the parent chain to find the function call. var type = checker.GetTypeAtLocation(callExpression.TypeArguments[0]); if (type != null && IsTypeCorrectForAddSourceFileConfiguration(type, workspace, pathTable, configuration)) { nodeParent = nodeParent.Parent; continue; } } return(false); } return(false); }); } catch (InvalidOperationException) { return(false); } return(singleConfiguration != null); } return(false); }); } catch (InvalidOperationException) { } if (sourcesNode != null) { var propertyAssignment = sourcesNode.Cast <IPropertyAssignment>(); // Will support array literals for now. var initializer = propertyAssignment.Initializer.As <IArrayLiteralExpression>(); if (initializer == null) { // TODO: potentially we could have a glob call here, and what we can do this: // [...(oldExpression), newFile] return(false); } var alreadyPresent = initializer.Elements.Any(element => { return(element.Kind == SyntaxKind.TaggedTemplateExpression && element.Cast <ITaggedTemplateExpression>().Template?.Text.Equals(sourceFileName, StringComparison.OrdinalIgnoreCase) == true); }); if (!alreadyPresent) { initializer.Elements.Add(new TaggedTemplateExpression("f", sourceFileName)); 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); }