private static bool IsNodeEligibleForRename(INode node) { switch (node.Kind) { case SyntaxKind.Identifier: case SyntaxKind.StringLiteral: case SyntaxKind.ThisKeyword: return(true); case SyntaxKind.NumericLiteral: // TODO: Our version of IsLiteralNameOfPropertyDeclarationOrIndexAccess does not suppoert computed property or string literal type // TODO: Whereas the typescript version does. return(DScriptUtilities.IsLiteralNameOfPropertyDeclarationOrIndexAccess(node)); default: return(false); } }
/// <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> /// Implements the "textDocument/hover" portion of the language server protocol. /// </summary> public Result <Hover, ResponseError> Hover(TextDocumentPositionParams positionParams, CancellationToken token) { // TODO: support cancellation Contract.Requires(positionParams.Position != null); if (!TryFindNode(positionParams, out var node)) { return(Result <Hover, ResponseError> .Success(null)); } // Label names are things such as "break", "continue", "jump" for which // we do not need to provide any hover. if (DScriptUtilities.IsLabelName(node)) { return(Result <Hover, ResponseError> .Success(null)); } // Attempt to get the type from the node var typeAtLocation = TypeChecker.GetTypeAtLocation(node); if ((typeAtLocation.Flags & TypeFlags.Any) != TypeFlags.None && node.Kind == SyntaxKind.Identifier) { // Now if we are likely to return any (unknown), and we are on an identifier // its parent is a type-reference then let's display that instead. // An example of this is "someTemplateVariable.merge<>" if (node.Parent?.Kind == SyntaxKind.TypeReference) { node = node.Parent; } else if (node.Parent?.Kind == SyntaxKind.QualifiedName) { // The same type of thing can happen with values referenced // from an imported value (i.e. qualified name) such as // import "A" from "SomeModule"; // let z = A.B.C.value; // "value" is the identifier and "A.B.C" are the qualified names. // As you walk up the chain, you will eventually find "A" which is // a type-reference. var nodeWalk = node.Parent; while (nodeWalk != null && nodeWalk.Kind == SyntaxKind.QualifiedName) { nodeWalk = nodeWalk.Parent; } if (nodeWalk.Kind == SyntaxKind.TypeReference) { node = nodeWalk; } } } // TODO: Not sure why GetSymbolAtLocation isn't enough var symbol = TypeChecker.GetSymbolAtLocation(node) ?? node.Symbol ?? node.ResolvedSymbol; // Find the first declaration. We will use it to special case a few things below. var firstDeclaration = symbol?.GetFirstDeclarationOrDefault(); // Grab the symbol name from the first declaration. string symbolAsString = firstDeclaration?.Name?.Text; // Handle a few special cases before we default to showing the type. // Typically these special cases are where hover is performed on a declaration of // things such as an enum, import statement (and eventually others) if (firstDeclaration != null && symbolAsString != null) { // Special case import * from "" and import {} from "" and import {x as y} from "" // So we can show the user a hover that says "import {} from 'module' so they can easily // see where it is coming from. if (firstDeclaration.Kind == SyntaxKind.ImportSpecifier || firstDeclaration.Kind == SyntaxKind.NamespaceImport) { IImportClause importClause; if (firstDeclaration.Kind == SyntaxKind.ImportSpecifier) { var importSpecifier = firstDeclaration.Cast <IImportSpecifier>(); var namedImports = importSpecifier.Parent.Cast <INamedImports>(); importClause = namedImports.Parent.Cast <IImportClause>(); } else { var namespaceImport = firstDeclaration.Cast <INamespaceImport>(); importClause = namespaceImport.Parent.Cast <IImportClause>(); } var importDeclaration = importClause.Parent.Cast <IImportDeclaration>(); return(CreateScriptHoverWithCodeSnippetAndDocumentation( symbol, node, string.Format(BuildXL.Ide.LanguageServer.Strings.HoverImportFormat, symbolAsString, importDeclaration.ModuleSpecifier.ToDisplayString()))); } // For interface declarations, just show a hover of "interface X" if (firstDeclaration.Kind == SyntaxKind.InterfaceDeclaration) { return(CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, firstDeclaration.GetFormattedText())); // We have decided to show the formatted text of the declaration as it allows // the user to browse the entire interface. // If we decide we want a more conscise hover, we can uncomment the following // code. //var interfaceDeclaration = firstDeclaration.Cast<IInterfaceDeclaration>(); //return CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, string.Format(Strings.HoverInterfaceFormat, interfaceDeclaration.Name.Text)); } // For enum declarations, show a hover of "enum { x, y, z}" if (firstDeclaration.Kind == SyntaxKind.EnumDeclaration) { return(CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, firstDeclaration.GetFormattedText())); // We have decided to show the formatted text of the declaration as it allows // the user to browse the entire enumeration. // If we decide we want a more conscise hover, we can uncomment the following // code. /* * var enumDeclaration = firstDeclaration.Cast<IEnumDeclaration>(); * * var memberStrings = new List<string>(); * foreach (var member in enumDeclaration.Members) * { * memberStrings.Add(member.Cast<IEnumMember>().Name.Text); * } * * var memberString = memberStrings.Aggregate((current, next) => string.Format(Strings.HoverEnumMemberFormat, current, next)); * * return CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, string.Format(Strings.HoverEnumFormat, enumDeclaration.Name.Text, memberString)); */ } // Handle things that are function-like (such as interface methods, functions, etc). var functionLike = firstDeclaration.As <IFunctionLikeDeclaration>(); if (functionLike != null) { return(CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, functionLike.ToDisplayString())); } } // So if we can't find a suitable declaration for the node to display for hover, // Then we will display the type associated the symbol, which is super useful // for things like variable declarations, etc. (You'll notice that variable declarations are not // handled above, for exactly this purpose). if (node != null && symbol != null) { return(CreateHoverBasedOnTypeInformation(symbol, node, symbolAsString)); } return(Result <Hover, ResponseError> .Success(null)); }