/// <summary> /// Handles the case where auto-complete is invoked directly in an /// object literal expression with no type information. /// I.e. calling a function with an object literal as an expression. /// Func( /// { /// ...Typing here /// } /// ); /// </summary> public static IEnumerable<ISymbol> CreateSymbolsFromObjectLiteralExpression(CompletionState completionState, INode completionNode) { var objectLiteralExpression = completionNode.Cast<IObjectLiteralExpression>(); var contextualType = completionState.TypeChecker.GetContextualType(objectLiteralExpression); if (contextualType != null) { var objectLiteralTypes = AutoCompleteHelpers.ExpandUnionAndBaseInterfaceTypes(contextualType); return GetRemainingPropertiesForObjectLiteral(objectLiteralExpression, objectLiteralTypes, completionState.TypeChecker); } return null; }
public static IEnumerable <CompletionItem> GetCompletionsForImportStatement(CompletionState completionState, INode completionNode) { // If the file belongs to the module with explicit policy about what the module can reference, // when the intellisense should show just that list. // Otherwise, we need to show all known modules. return(FilterModulesFor(completionState) .Select( m => new CompletionItem() { Kind = CompletionItemKind.Text, Label = m.Descriptor.Name, InsertText = m.Descriptor.Name, Detail = $"Module config file: '{m.Definition.ModuleConfigFile.ToString(completionState.PathTable)}'", Documentation = $"Module kind: {(m.Definition.ResolutionSemantics == NameResolutionSemantics.ExplicitProjectReferences ? "V1" : "V2")}", })); }
/// <summary> /// Tries to find a completion handler given for the current AST node. /// </summary> /// <remarks> /// Note that in order for this function to work correctly, the <see cref="m_completionInformation"/> list must /// remain sorted by "starting syntax kind" and then by the "longest" parent kind list as /// the longest chains are checked first, then smaller chains, and so on, to find the best /// handler for the job. /// </remarks> private bool TryFindCompletionHandlerForNode(CompletionState completionState, out SymbolCompletionHandler symbolHandler, out CompletionItemCompletionHandler completionHandler, out INode completionNode) { symbolHandler = null; completionHandler = null; completionNode = null; foreach (var completionInfo in m_completionInformation) { if (completionInfo.StartingSyntaxKind == completionState.StartingNode.Kind) { symbolHandler = completionInfo.SymbolCompletionHandler; completionHandler = completionInfo.CompletionItemCompletionHandler; completionNode = completionState.StartingNode; // If we don't have any parent kinds, then we are done. if (completionInfo.ParentKinds.IsNullOrEmpty()) { return(true); } // Walk up the parent kinds and make sure that our node has // the correct type of parent. foreach (var parentKind in completionInfo.ParentKinds) { completionNode = (completionNode.Parent?.Kind == parentKind) ? completionNode.Parent : null; if (completionNode == null) { break; } } // If at this point, we still have a completion node, then we are good to go as we have matched // the longest parent kind list. if (completionNode != null) { if (completionInfo.ShouldRunHandler == null || completionInfo.ShouldRunHandler(completionState, completionNode)) { return(true); } } } } return(false); }
private static IEnumerable <ParsedModule> FilterModulesFor(CompletionState completionState) { HashSet <string> allowedModules = null; var module = completionState.Workspace.TryGetModuleBySpecFileName(completionState.StartingNode.GetSourceFile().GetAbsolutePath(completionState.PathTable)); if (module?.Definition.AllowedModuleDependencies != null) { allowedModules = new HashSet <string>(module.Definition.AllowedModuleDependencies.Select(m => m.Name)); } return(completionState.Workspace.SpecModules // Need to filter out special config module ("__Config__"), current module // and left only modules allowed by the module configuration (if available). .Where(m => !m.Descriptor.IsSpecialConfigModule() && m.Descriptor != module.Descriptor && (allowedModules == null || allowedModules.Contains(m.Descriptor.Name))) .ToList()); }
/// <summary> /// Handles the switch(stringLiteralType) { case "{completion happens here}" } /// </summary> internal static IEnumerable <CompletionItem> CreateCompletionFromCaseClause(CompletionState completionState, INode completionNode) { var caseClause = completionNode.Cast <ICaseClause>(); // Walk up the parent chain until we find the switch statement as the expression of the // switch statement is the type we want to complete. // The type-script version of this does a "node.parent.parent.parent.parent" type of expression // which seems fragile if the AST parsing were changed in any way. So we will just walk the chain. var switchStatement = GetSwitchStatement(caseClause); var type = completionState.TypeChecker.GetTypeAtLocation(switchStatement.Cast <ISwitchStatement>().Expression); if (type != null) { var results = AddStringLiteralSymbolsFromType(type, completionState.TypeChecker); return(CreateCompletionItemsFromStrings(results)); } return(null); }
public static bool ShouldCreateCompletionItemsForImportStatements(CompletionState completionState, INode completionNode) { return(completionState.StartingNode.IsLiteralFromImportOrExportDeclaration()); }
/// <summary> /// Creates an array of symbols for a qualified name. /// </summary> /// <remarks> /// This was ported from the TypeScript version of the language server. /// </remarks> public static IEnumerable <ISymbol> CreateCompletionItemsFromQualifiedName(CompletionState completionState, INode completionNode) { var qualifiedName = completionNode.Cast <IQualifiedName>(); return(PropertyAccessExpression.GetTypeScriptMemberSymbols(qualifiedName.Left, completionState.TypeChecker)); }
/// <summary> /// Handles the case of: /// function myFunction(args: "A" | "B" | "C"); /// const myVar = myFunc({completion happens here}); /// </summary> /// <remarks> /// The type is based on the expected type of the initializer. So we ask the type checker for /// that type. We then expand all union types and then return the unique set /// of possibilities. /// </remarks> internal static IEnumerable <CompletionItem> CreateCompletionFromCallExperssion(CompletionState completionState, INode completionNode) { var callExpression = completionNode.Cast <ICallExpression>(); // The user can type a random function that looks like a call expresion: // const myVar == fooBar(); // In which case we have nothing to resolve var callSymbol = completionState.TypeChecker.GetSymbolAtLocation(callExpression.Expression); if (callSymbol == null) { return(null); } var typeOfExpression = completionState.TypeChecker.GetTypeAtLocation(callExpression.Expression); if (typeOfExpression == null) { return(null); } var signature = completionState.TypeChecker.GetSignaturesOfType(typeOfExpression, SignatureKind.Call).FirstOrDefault(); var parameterIndex = DScriptFunctionSignature.GetActiveParameterIndex(callExpression, completionState.StartingNode); var parameterCount = signature?.Parameters?.Count ?? 0; if (parameterIndex >= 0 && parameterIndex < parameterCount) { var parameter = signature.Parameters[parameterIndex]; var type = completionState.TypeChecker.GetTypeAtLocation(parameter.GetFirstDeclarationOrDefault()); if (type != null) { var results = AddStringLiteralSymbolsFromType(type, completionState.TypeChecker); return(CreateCompletionItemsFromStrings(results)); } } return(null); }
/// <summary> /// Handles the case of: /// const myVar : someInterfaceType = "{ stringLiteralProperty: {completion happens here} }"; /// </summary> /// <remarks> /// The type is based on the expected type of the initializer. So we ask the type checker for /// that type. We then expand all union types and then return the unique set /// of possibilities. /// </remarks> internal static IEnumerable <CompletionItem> CreateCompletionItemsFromPropertyAssignment(CompletionState completionState, INode completionNode) { var propertyAssignment = completionNode.Cast <IPropertyAssignment>(); var type = completionState.TypeChecker.GetContextualType(propertyAssignment.Initializer); if (type != null) { var results = AddStringLiteralSymbolsFromType(type, completionState.TypeChecker); return(CreateCompletionItemsFromStrings(results)); } return(null); }
/// <summary> /// Handles the case where auto-complete is from a property access. /// </summary> /// <remarks> /// This occurs when typing a property access of /// let someValue = object. /// A property access expression (<see cref="IPropertyAccessExpression"/>) is composed of an "left hand expression" /// (represented in the <see cref="IPropertyAccessExpression.Expression"/> field, the "dot" has its /// own field <see cref="IPropertyAccessExpression.DotToken"/>, and the remainder is store in the <see cref="IPropertyAccessExpression.Name"/> /// field. /// /// The type checker needs the node that proeprty is being accessed on, the expression field. /// </remarks> internal static IEnumerable <ISymbol> CreateSymbolsFromPropertyAccessExpression(CompletionState completionState, INode completionNode) { // A property access expression contains a // "Left hand expression", the "Dot Token" and the "Name" // We care about the properties var propertyAccessExpression = completionNode.Cast <IPropertyAccessExpression>(); if (propertyAccessExpression.Expression != null) { return(GetTypeScriptMemberSymbols(propertyAccessExpression.Expression, completionState.TypeChecker)); } return(null); }
/// <summary> /// Handles the case of return "" where the completion is a string literal. /// </summary> internal static IEnumerable <CompletionItem> CreateCompletionItemsFromReturnStatement(CompletionState completionState, INode completionNode) { var returnStatement = completionNode.Cast <IReturnStatement>(); if (returnStatement.Expression != null) { var type = completionState.TypeChecker.GetContextualType(returnStatement.Expression); if (type != null) { var results = AddStringLiteralSymbolsFromType(type, completionState.TypeChecker); return(CreateCompletionItemsFromStrings(results)); } } return(null); }
/// <summary> /// Handles the case of (x === y) ? "" : "" where the completion is a string literal. /// </summary> internal static IEnumerable <CompletionItem> CreateCompletionItemsFromSwitchExpression(CompletionState completionState, INode completionNode) { var switchExpression = completionNode.Cast <ISwitchExpression>(); var type = completionState.TypeChecker.GetContextualType(switchExpression); // $TODO: return(null); }
/// <summary> /// Handles the if (stringLiteralType === "completion happens here"). /// </summary> /// <remarks> /// Actually this handles any types of binary experssions. For example !== is handled as well. /// </remarks> internal static IEnumerable <CompletionItem> CreateCompletionFromBinaryExpression(CompletionState completionState, INode completionNode) { var binaryExpression = completionNode.Cast <IBinaryExpression>(); // We want the side of the expression that is opposite the string literal // So, check both sides of the expression and pick the one that isn't the string literal node (which is our starting node in this case) Contract.Assert(ReferenceEquals(binaryExpression.Left, completionState.StartingNode) || ReferenceEquals(binaryExpression.Right, completionState.StartingNode)); var notTheStringLiteralNode = ReferenceEquals(binaryExpression.Left, completionState.StartingNode) ? binaryExpression.Right : binaryExpression.Left; if (notTheStringLiteralNode != null) { var type = completionState.TypeChecker.GetTypeAtLocation(notTheStringLiteralNode); if (type != null) { var results = AddStringLiteralSymbolsFromType(type, completionState.TypeChecker); return(CreateCompletionItemsFromStrings(results)); } } return(null); }
/// <summary> /// Determines whether the completion provider should call the <see cref="CreateCompletionFromBinaryExpression(CompletionState, INode)"/> handler. /// </summary> internal static bool ShouldRunCreateCompletionFromBinaryExpression(CompletionState copmletionState, INode completionNode) { var binaryExpression = completionNode.Cast <IBinaryExpression>(); return(IsEqualityOperator(binaryExpression.OperatorToken)); }
/// <summary> /// Attempts to create auto-complete items from the "tagged template" statements for file, path, directory and relative path (f``, d``, p``) /// </summary> internal static IEnumerable <CompletionItem> TryCreateFileCompletionItemsForTaggedTemplatedExpression(CompletionState completionState, INode completionNode) { var templateExpression = completionNode.Cast <ITaggedTemplateExpression>(); // GetTemplateText does a Cast on the expression and then access the "text" property // instead of just returning a null or empty string. // So, before we can use GetTemplateText, we must check to see if it is actually of // the correct type. // We cannot use the syntax kind, as ILiteralExpression can cover many syntax kinds - // Strings, numbers, etc. var literalExprssion = templateExpression?.TemplateExpression?.As <ILiteralExpression>(); var existingFileExpressionText = literalExprssion?.GetTemplateText(); if (!string.IsNullOrEmpty(existingFileExpressionText)) { if (!RelativePath.TryCreate(completionState.PathTable.StringTable, existingFileExpressionText, out var normalizedPath)) { return(null); } existingFileExpressionText = normalizedPath.ToString(completionState.PathTable.StringTable); } else { existingFileExpressionText = string.Empty; } var sourceFileRootPath = Path.GetDirectoryName(completionState.StartingNode.SourceFile.ToUri().LocalPath); // Now, for this bit of fun. If the user has started typing a path already, then we still // want to complete what they have started typing. // If what they have typed does not have a directory separator in it, then we will use // that as the search pattern below. If it does however, then we will combine that // with the spec directory and then attempt to get the remainder to use as the search path. // So... if the user has typed `foo` we will use the spec path as the root search directory // and `foo*` as the search pattern. // If the user has typed `foo\bar` then we will use `<spec path>\foo` as the search path and // "bar" as the search pattern. var originalSourceFileRootPath = sourceFileRootPath; if (existingFileExpressionText.Contains(Path.DirectorySeparatorChar)) { sourceFileRootPath = Path.Combine(sourceFileRootPath, existingFileExpressionText); existingFileExpressionText = Path.GetFileName(sourceFileRootPath); sourceFileRootPath = Path.GetDirectoryName(sourceFileRootPath); } // If the user types just a "\" character, then the source file root path can be null if (string.IsNullOrEmpty(sourceFileRootPath)) { return(null); } // If we managed to get a path that is outside of our spec path, then bail. Do not allow auto completion if (!sourceFileRootPath.StartsWith(originalSourceFileRootPath, System.StringComparison.OrdinalIgnoreCase) || !Directory.Exists(sourceFileRootPath)) { return(null); } bool isDirectoryTag = templateExpression.GetInterpolationKind() == InterpolationKind.DirectoryInterpolation; // Leverage BuildXL's recoverable IO exception extension to ensure that if we hit // cases like "access denied" that it does not take down the plugin. IEnumerable <string> fileSystemEntries = null; try { fileSystemEntries = ExceptionUtilities.HandleRecoverableIOException( () => { return(isDirectoryTag ? Directory.EnumerateDirectories(sourceFileRootPath, existingFileExpressionText + "*", SearchOption.TopDirectoryOnly) : Directory.EnumerateFileSystemEntries(sourceFileRootPath, existingFileExpressionText + "*", SearchOption.TopDirectoryOnly)); }, ex => { throw new BuildXLException(ex.Message); }); } catch (BuildXLException) { } if (fileSystemEntries.IsNullOrEmpty()) { return(null); } // We aren't done yet :) // Let's see if we can filter further if we are in an array literal experssion.. // So, if the user has something like this // sources: [ // f`fileA.cpp`, // f`fileB.cpp`, // // And they have files fileC-FileZ.cpp on disk, we filter out fileA.cpp and fileB.cpp. // This is very similar to filtering out properties that are already set on an object // literal. if (templateExpression.Parent?.Kind == SyntaxKind.ArrayLiteralExpression) { var existingFiles = new List <string>(); var existingArrayLiteralExpression = templateExpression.Parent.Cast <IArrayLiteralExpression>(); foreach (var existingFileItem in existingArrayLiteralExpression.Elements) { if (existingFileItem.Kind == SyntaxKind.TaggedTemplateExpression) { var existingFileTemplateExpression = existingFileItem.Cast <ITaggedTemplateExpression>(); // GetTemplateText does a Cast on the expression and then access the "text" property // instead of just returning a null or empty string. // So, before we can use GetTemplateText, we must check to see if it is actually of // the correct type. // We cannot use the syntax kind, as ILiteralExpression can cover many syntax kinds - // Strings, numbers, etc. var existingFileLiteralExprssion = existingFileTemplateExpression?.TemplateExpression?.As <ILiteralExpression>(); var existingFilePathText = existingFileLiteralExprssion?.GetTemplateText(); if (!string.IsNullOrEmpty(existingFilePathText)) { existingFiles.Add(Path.Combine(originalSourceFileRootPath, existingFilePathText)); } } } fileSystemEntries = fileSystemEntries.Where(possibleEntry => !existingFiles.Contains(possibleEntry, StringComparer.InvariantCultureIgnoreCase)); } return(fileSystemEntries.Select(name => { var itemLabel = name.Substring(sourceFileRootPath.Length + 1); var item = new CompletionItem() { Kind = CompletionItemKind.File, Label = itemLabel, }; return item; })); }
internal static bool ShouldCreateFileCompletionItemsForTaggedTemplatedExpression(CompletionState completionState, INode completionNode) { var templateExpression = completionState.StartingNode.Parent.Cast <ITaggedTemplateExpression>(); if (!templateExpression.IsPathInterpolation()) { return(false); } // The a`` (PathAtom) interpolation kind is not supported for completion as it is user specified name. // The r`` (RelativePath) is also not supported. var interpolationKind = templateExpression.GetInterpolationKind(); if (interpolationKind == InterpolationKind.PathAtomInterpolation || interpolationKind == InterpolationKind.RelativePathInterpolation) { return(false); } return(true); }
private Result <ArrayOrObject <CompletionItem, CompletionList>, ResponseError> GetCompletionItemsForNode(CompletionState completionState) { IEnumerable <ISymbol> autoCompleteSymbols = null; if (TryFindCompletionHandlerForNode(completionState, out var symbolHandler, out var completionHandler, out var completionNode)) { Contract.Assert(symbolHandler != null || completionHandler != null); if (symbolHandler != null) { autoCompleteSymbols = symbolHandler(completionState, completionNode); } else { var handlerCompletionItems = completionHandler(completionState, completionNode); if (handlerCompletionItems != null) { return(Result <ArrayOrObject <CompletionItem, CompletionList>, ResponseError> .Success(handlerCompletionItems.ToArray())); } } }
/// <summary> /// Handles the case of: /// const myVar : StringLiterlType = "{completion happens here}"; /// </summary> /// <remarks> /// The type is based on the expected type of the initializer. So we ask the type checker for /// that type. We then expand all union types and then return the unique set /// of possibilities. /// </remarks> internal static IEnumerable <CompletionItem> CreateCompletionItemsFromVariableDeclaration(CompletionState completionState, INode completionNode) { var variableDeclaration = completionNode.Cast <IVariableDeclaration>(); var type = completionState.TypeChecker.GetContextualType(variableDeclaration.Initializer); if (type != null) { var results = AddStringLiteralSymbolsFromType(type, completionState.TypeChecker); return(CreateCompletionItemsFromStrings(results)); } return(null); }