<commit_before>﻿// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Recommendations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Recommendations
{
    [ExportLanguageService(typeof(IRecommendationService), LanguageNames.CSharp)]
    internal class CSharpRecommendationService : AbstractRecommendationService
    {
        protected override Tuple<IEnumerable<ISymbol>, AbstractSyntaxContext> GetRecommendedSymbolsAtPositionWorker(
            Workspace workspace, SemanticModel semanticModel, int position, OptionSet options, CancellationToken cancellationToken)
        {
            var context = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken);

            var filterOutOfScopeLocals = options.GetOption(RecommendationOptions.FilterOutOfScopeLocals, semanticModel.Language);
            var symbols = GetSymbolsWorker(context, filterOutOfScopeLocals, cancellationToken);

            var hideAdvancedMembers = options.GetOption(RecommendationOptions.HideAdvancedMembers, semanticModel.Language);
            symbols = symbols.FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, semanticModel.Compilation);

            return Tuple.Create<IEnumerable<ISymbol>, AbstractSyntaxContext>(symbols, context);
        }

        private static IEnumerable<ISymbol> GetSymbolsWorker(
            CSharpSyntaxContext context,
            bool filterOutOfScopeLocals,
            CancellationToken cancellationToken)
        {
            if (context.IsInNonUserCode ||
                context.IsPreProcessorDirectiveContext)
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }

            // TODO: don't show completion set at namespace name part to match Dev10 behavior
            // if we want to provide new feature that shows all existing namespaces later, remove this
            if (context.IsNamespaceDeclarationNameContext)
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }

            if (context.IsRightOfNameSeparator)
            {
                return GetSymbolsOffOfContainer(context, cancellationToken);
            }
            else
            {
                return GetSymbolsForCurrentContext(context, filterOutOfScopeLocals, cancellationToken);
            }
        }

        private static IEnumerable<ISymbol> GetSymbolsForCurrentContext(
            CSharpSyntaxContext context,
            bool filterOutOfScopeLocals,
            CancellationToken cancellationToken)
        {
            if (context.IsGlobalStatementContext)
            {
                // Script and interactive
                return GetSymbolsForGlobalStatementContext(context, cancellationToken);
            }
            else if (context.IsAnyExpressionContext || context.IsStatementContext)
            {
                return GetSymbolsForExpressionOrStatementContext(context, filterOutOfScopeLocals, cancellationToken);
            }
            else if (context.IsTypeContext || context.IsNamespaceContext)
            {
                return GetSymbolsForTypeOrNamespaceContext(context, cancellationToken);
            }
            else if (context.IsLabelContext)
            {
                return GetSymbolsForLabelContext(context, cancellationToken);
            }
            else if (context.IsTypeArgumentOfConstraintContext)
            {
                return GetSymbolsForTypeArgumentOfConstraintClause(context, cancellationToken);
            }
            else if (context.IsDestructorTypeContext)
            {
                return SpecializedCollections.SingletonEnumerable(context.SemanticModel.GetDeclaredSymbol(context.ContainingTypeOrEnumDeclaration));
            }

            return SpecializedCollections.EmptyEnumerable<ISymbol>();
        }

        private static IEnumerable<ISymbol> GetSymbolsOffOfContainer(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            // Ensure that we have the correct token in A.B| case
            var node = context.TargetToken.Parent;

            if (node.CSharpKind() == SyntaxKind.SimpleMemberAccessExpression)
            {
                return GetSymbolsOffOfExpression(context, ((MemberAccessExpressionSyntax)node).Expression, cancellationToken);
            }
            else if (node.CSharpKind() == SyntaxKind.PointerMemberAccessExpression)
            {
                return GetSymbolsOffOfDereferencedExpression(context, ((MemberAccessExpressionSyntax)node).Expression, cancellationToken);
            }
            else if (node.CSharpKind() == SyntaxKind.QualifiedName)
            {
                return GetSymbolsOffOfName(context, ((QualifiedNameSyntax)node).Left, cancellationToken);
            }
            else if (node.CSharpKind() == SyntaxKind.AliasQualifiedName)
            {
                return GetSymbolsOffOffAlias(context, ((AliasQualifiedNameSyntax)node).Alias, cancellationToken);
            }
            else
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }
        }

        private static IEnumerable<ISymbol> GetSymbolsForGlobalStatementContext(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var syntaxTree = context.SyntaxTree;
            var position = context.Position;
            var token = context.LeftToken;

            // The following code is a hack to get around a binding problem when asking binding
            // questions immediately after a using directive. This is special-cased in the binder
            // factory to ensure that using directives are not within scope inside other using
            // directives. That generally works fine for .cs, but it's a problem for interactive
            // code in this case:
            //
            // using System;
            // |

            if (token.CSharpKind() == SyntaxKind.SemicolonToken &&
                token.IsParentKind(SyntaxKind.UsingDirective) &&
                position >= token.Span.End)
            {
                var compUnit = (CompilationUnitSyntax)syntaxTree.GetRoot(cancellationToken);
                if (compUnit.Usings.Count > 0 && compUnit.Usings.Last().GetLastToken() == token)
                {
                    token = token.GetNextToken(includeZeroWidth: true);
                }
            }

            var symbols = context.SemanticModel
                .LookupSymbols(token.SpanStart);

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsForTypeArgumentOfConstraintClause(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var enclosingSymbol = context.LeftToken.Parent
                .AncestorsAndSelf()
                .Select(n => context.SemanticModel.GetDeclaredSymbol(n))
                .WhereNotNull()
                .FirstOrDefault();

            var symbols = enclosingSymbol != null
                ? enclosingSymbol.GetTypeArguments()
                : SpecializedCollections.EmptyEnumerable<ISymbol>();

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsOffOffAlias(
            CSharpSyntaxContext context,
            IdentifierNameSyntax alias,
            CancellationToken cancellationToken)
        {
            var aliasSymbol = context.SemanticModel.GetAliasInfo(alias, cancellationToken);
            if (aliasSymbol == null)
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }

            return context.SemanticModel.LookupNamespacesAndTypes(
                alias.SpanStart,
                aliasSymbol.Target);
        }

        private static IEnumerable<ISymbol> GetSymbolsForLabelContext(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var symbols = context.SemanticModel.LookupLabels(context.LeftToken.SpanStart);

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsForTypeOrNamespaceContext(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var symbols = context.SemanticModel.LookupNamespacesAndTypes(context.LeftToken.SpanStart);

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsForExpressionOrStatementContext(
            CSharpSyntaxContext context,
            bool filterOutOfScopeLocals,
            CancellationToken cancellationToken)
        {
            // Check if we're in an interesting situation like this:
            //
            //     i          // <-- here
            //     I = 0;

            // The problem is that "i I = 0" causes a local to be in scope called "I".  So, later when<commit_msg>This deals with bug 552717: C# IntelliSense: 'case' shouldn't appear in completion list after 'goto '. It stops showing 'case 0', 'case 1' etc. labels in goto statements inside a switch block. It also stops showing the 'default' *keyword* after such a 'goto'. (The symbol completion provider already shows it in intellisense if it's been defined, and it without this change we'd see two of these).<commit_after>﻿// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Recommendations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Recommendations
{
    [ExportLanguageService(typeof(IRecommendationService), LanguageNames.CSharp)]
    internal class CSharpRecommendationService : AbstractRecommendationService
    {
        protected override Tuple<IEnumerable<ISymbol>, AbstractSyntaxContext> GetRecommendedSymbolsAtPositionWorker(
            Workspace workspace, SemanticModel semanticModel, int position, OptionSet options, CancellationToken cancellationToken)
        {
            var context = CSharpSyntaxContext.CreateContext(workspace, semanticModel, position, cancellationToken);

            var filterOutOfScopeLocals = options.GetOption(RecommendationOptions.FilterOutOfScopeLocals, semanticModel.Language);
            var symbols = GetSymbolsWorker(context, filterOutOfScopeLocals, cancellationToken);

            var hideAdvancedMembers = options.GetOption(RecommendationOptions.HideAdvancedMembers, semanticModel.Language);
            symbols = symbols.FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, semanticModel.Compilation);

            return Tuple.Create<IEnumerable<ISymbol>, AbstractSyntaxContext>(symbols, context);
        }

        private static IEnumerable<ISymbol> GetSymbolsWorker(
            CSharpSyntaxContext context,
            bool filterOutOfScopeLocals,
            CancellationToken cancellationToken)
        {
            if (context.IsInNonUserCode ||
                context.IsPreProcessorDirectiveContext)
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }

            // TODO: don't show completion set at namespace name part to match Dev10 behavior
            // if we want to provide new feature that shows all existing namespaces later, remove this
            if (context.IsNamespaceDeclarationNameContext)
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }

            if (context.IsRightOfNameSeparator)
            {
                return GetSymbolsOffOfContainer(context, cancellationToken);
            }
            else
            {
                return GetSymbolsForCurrentContext(context, filterOutOfScopeLocals, cancellationToken);
            }
        }

        private static IEnumerable<ISymbol> GetSymbolsForCurrentContext(
            CSharpSyntaxContext context,
            bool filterOutOfScopeLocals,
            CancellationToken cancellationToken)
        {
            if (context.IsGlobalStatementContext)
            {
                // Script and interactive
                return GetSymbolsForGlobalStatementContext(context, cancellationToken);
            }
            else if (context.IsAnyExpressionContext || context.IsStatementContext)
            {
                return GetSymbolsForExpressionOrStatementContext(context, filterOutOfScopeLocals, cancellationToken);
            }
            else if (context.IsTypeContext || context.IsNamespaceContext)
            {
                return GetSymbolsForTypeOrNamespaceContext(context, cancellationToken);
            }
            else if (context.IsLabelContext)
            {
                return GetSymbolsForLabelContext(context, cancellationToken);
            }
            else if (context.IsTypeArgumentOfConstraintContext)
            {
                return GetSymbolsForTypeArgumentOfConstraintClause(context, cancellationToken);
            }
            else if (context.IsDestructorTypeContext)
            {
                return SpecializedCollections.SingletonEnumerable(context.SemanticModel.GetDeclaredSymbol(context.ContainingTypeOrEnumDeclaration));
            }

            return SpecializedCollections.EmptyEnumerable<ISymbol>();
        }

        private static IEnumerable<ISymbol> GetSymbolsOffOfContainer(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            // Ensure that we have the correct token in A.B| case
            var node = context.TargetToken.Parent;

            if (node.CSharpKind() == SyntaxKind.SimpleMemberAccessExpression)
            {
                return GetSymbolsOffOfExpression(context, ((MemberAccessExpressionSyntax)node).Expression, cancellationToken);
            }
            else if (node.CSharpKind() == SyntaxKind.PointerMemberAccessExpression)
            {
                return GetSymbolsOffOfDereferencedExpression(context, ((MemberAccessExpressionSyntax)node).Expression, cancellationToken);
            }
            else if (node.CSharpKind() == SyntaxKind.QualifiedName)
            {
                return GetSymbolsOffOfName(context, ((QualifiedNameSyntax)node).Left, cancellationToken);
            }
            else if (node.CSharpKind() == SyntaxKind.AliasQualifiedName)
            {
                return GetSymbolsOffOffAlias(context, ((AliasQualifiedNameSyntax)node).Alias, cancellationToken);
            }
            else
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }
        }

        private static IEnumerable<ISymbol> GetSymbolsForGlobalStatementContext(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var syntaxTree = context.SyntaxTree;
            var position = context.Position;
            var token = context.LeftToken;

            // The following code is a hack to get around a binding problem when asking binding
            // questions immediately after a using directive. This is special-cased in the binder
            // factory to ensure that using directives are not within scope inside other using
            // directives. That generally works fine for .cs, but it's a problem for interactive
            // code in this case:
            //
            // using System;
            // |

            if (token.CSharpKind() == SyntaxKind.SemicolonToken &&
                token.IsParentKind(SyntaxKind.UsingDirective) &&
                position >= token.Span.End)
            {
                var compUnit = (CompilationUnitSyntax)syntaxTree.GetRoot(cancellationToken);
                if (compUnit.Usings.Count > 0 && compUnit.Usings.Last().GetLastToken() == token)
                {
                    token = token.GetNextToken(includeZeroWidth: true);
                }
            }

            var symbols = context.SemanticModel
                .LookupSymbols(token.SpanStart);

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsForTypeArgumentOfConstraintClause(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var enclosingSymbol = context.LeftToken.Parent
                .AncestorsAndSelf()
                .Select(n => context.SemanticModel.GetDeclaredSymbol(n))
                .WhereNotNull()
                .FirstOrDefault();

            var symbols = enclosingSymbol != null
                ? enclosingSymbol.GetTypeArguments()
                : SpecializedCollections.EmptyEnumerable<ISymbol>();

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsOffOffAlias(
            CSharpSyntaxContext context,
            IdentifierNameSyntax alias,
            CancellationToken cancellationToken)
        {
            var aliasSymbol = context.SemanticModel.GetAliasInfo(alias, cancellationToken);
            if (aliasSymbol == null)
            {
                return SpecializedCollections.EmptyEnumerable<ISymbol>();
            }

            return context.SemanticModel.LookupNamespacesAndTypes(
                alias.SpanStart,
                aliasSymbol.Target);
        }

        private static IEnumerable<ISymbol> GetSymbolsForLabelContext(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var allLabels = context.SemanticModel.LookupLabels(context.LeftToken.SpanStart);

            // Exclude labels (other than 'default') that come from case switch statements

            return allLabels
                .Where(label => label.DeclaringSyntaxReferences.First().GetSyntax(cancellationToken)
                    .MatchesKind(SyntaxKind.LabeledStatement, SyntaxKind.DefaultSwitchLabel))
                .AsImmutableOrEmpty();
        }

        private static IEnumerable<ISymbol> GetSymbolsForTypeOrNamespaceContext(
            CSharpSyntaxContext context,
            CancellationToken cancellationToken)
        {
            var symbols = context.SemanticModel.LookupNamespacesAndTypes(context.LeftToken.SpanStart);

            return symbols;
        }

        private static IEnumerable<ISymbol> GetSymbolsForExpressionOrStatementContext(
            CSharpSyntaxContext context,
            bool filterOutOfScopeLocals,
            CancellationToken cancellationToken)
        {
            // Check if we're in an interesting situation like this:
            //
            //     i          // <-- here
            //     I = 0;

            // The problem is that "i I = 0" causes a local to be in scope called "I".  So, later when
{'fraction_non_alphanumeric': 0.065511559394817, 'fraction_numerical': 0.0010985170020472363, 'mean_word_length': 2.287590282337492, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1694563', 'n_tokens_mistral': 4656, 'n_tokens_neox': 4475, 'n_words': 1049}