Beispiel #1
0
        /// <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;
        }
Beispiel #2
0
 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")}",
     }));
 }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        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());
        }
Beispiel #5
0
        /// <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);
        }
Beispiel #6
0
 public static bool ShouldCreateCompletionItemsForImportStatements(CompletionState completionState, INode completionNode)
 {
     return(completionState.StartingNode.IsLiteralFromImportOrExportDeclaration());
 }
Beispiel #7
0
        /// <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));
        }
Beispiel #8
0
        /// <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);
        }
Beispiel #9
0
        /// <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);
        }
Beispiel #10
0
        /// <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);
        }
Beispiel #11
0
        /// <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);
        }
Beispiel #12
0
        /// <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);
        }
Beispiel #13
0
        /// <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);
        }
Beispiel #14
0
        /// <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);
        }
Beispiel #17
0
        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()));
                    }
                }
            }
Beispiel #18
0
        /// <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);
        }