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

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis
{
    internal static class CodeRefactoringHelpers
    {
        /// <summary>
        /// <para>
        /// Returns an instance of <typeparamref name="TSyntaxNode"/> for refactoring given specified selection in document or null
        /// if no such instance exists.
        /// </para>
        /// <para>
        /// A <typeparamref name="TSyntaxNode"/> instance is returned if selection is zero-width and inside/touching a Token whose
        /// direct parent is of type <typeparamref name="TSyntaxNode"/> or if a Node of said type is the smallest Node containing
        /// the whole <paramref name="selection"/>. Otherwise returns <code>null</code>.
        /// </para>
        /// <para>
        /// Note: this function strips all whitespace from both the beginning and the end of given <paramref name="selection"/>.
        /// The stripped version is then used to determine relevant <see cref="SyntaxNode"/>.
        /// </para>
        /// </summary>
        public static async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(
            Document document, TextSpan selection, CancellationToken cancellationToken, bool traverseUpInSyntaxTreeIfOnEdgeOfSpan = false) where TSyntaxNode : SyntaxNode
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var selectionStripped = await GetStrippedTextSpan(document, selection, cancellationToken).ConfigureAwait(false);

            var node = root.FindNode(selectionStripped) as TSyntaxNode;
            if (node != null)
            {
                return node;
            }
            {
                // e.g. "C LocalFunction[||](C c)" -> root.FindNode return ParameterList but we still want to return LocalFunctionNode
                var identifier = await root.SyntaxTree.GetTouchingTokenAsync(selectionStripped.Start,
                    token => token.Parent is TSyntaxNode, cancellationToken).ConfigureAwait(false);
                return identifier.Parent as TSyntaxNode;
            }

            return node;
        }

        public static Task<bool> RefactoringSelectionIsValidAsync(
            Document document, TextSpan selection, SyntaxNode node, CancellationToken cancellation)
        {
            return RefactoringSelectionIsValidAsync(document, selection, node, ImmutableArray<SyntaxNode>.Empty, cancellation);
        }

        /// <summary>
        /// <para>
        /// Determines if a refactoring should be offered for a given node, given the specified selection in a document.
        /// The refactoring is offered either if the selection is zero-width and not inside one of the specified holes
        /// or if the selection contains the entire node and perhaps some whitespace.
        /// </para>
        /// <para>
        /// Note: this function considers the span containing the node to start at the earliest preceding whitespace
        /// (including up to one newline) before the node’s <see cref="SyntaxNode.SpanStart"/> and up through the last
        /// whitespace following the <see cref="SyntaxNode.Span"/>’s <see cref="TextSpan.End"/>.
        /// </para>
        /// <para>
        /// For the <paramref name="holes"/>, the position is considered invalid if it is *within* the hole, not if it
        /// is touching the hole edges.
        /// </para>
        /// </summary>
        public static async Task<bool> RefactoringSelectionIsValidAsync(
            Document document,
            TextSpan selection,
            SyntaxNode node,
            ImmutableArray<SyntaxNode> holes,
            CancellationToken cancellationToken)
        {
            if (selection.Length == 0)
            {
                return await RefactoringPositionIsValidAsync(
                    document, selection.Start, node, holes, cancellationToken).ConfigureAwait(false);
            }

            // If we have a selection, it needs to be selecting at least the full node. We allow the selection to start
            // in whitespace (including up to one newline) before the start of the node, and we allow it to end after
            // the end of the node and the end of the line the node ends on.
            var expandedSpan = await GetExpandedNodeSpan(document, node, cancellationToken).ConfigureAwait(false);
            if ((selection.Start >= expandedSpan.Start && selection.Start <= node.SpanStart) &&
                (selection.End >= node.Span.End && selection.End <= expandedSpan.End))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// <para>
        /// Determines if a refactoring should be offered for a given node, given the specified position in a document.
        /// The refactoring is offered if the position is somewhere on the span containing the node and not in any of
        /// the specified <paramref name="holes"/> to avoid.
        /// </para>
        /// <para>
        /// Note: this function considers the span containing the node to start at the earliest preceding whitespace
        /// (including up to one newline) before the node’s <see cref="SyntaxNode.SpanStart"/> and up through the last
        /// whitespace following the <see cref="SyntaxNode.Span"/>’s <see cref="TextSpan.End"/>.
        /// </para>
        /// <para>
        /// For the <paramref name="holes"/>, the position is considered invalid if it is *within* the hole, not if it
        /// is touching the hole edges.
        /// </para>
        /// </summary>
        public static async Task<bool> RefactoringPositionIsValidAsync(
            Document document,
            int position,
            SyntaxNode node,
            ImmutableArray<SyntaxNode> holes,
            CancellationToken cancellationToken)
        {
            var expandedSpan = await GetExpandedNodeSpan(document, node, cancellationToken).ConfigureAwait(false);
            if (!expandedSpan.IntersectsWith(position))
            {
                // Position isn’t within the node’s widened span. Definitely not valid here.
                return false;
            }

            foreach (var hole in holes)
            {
                if (position > hole.Span.Start && position < hole.Span.End)
                {
                    // Position was in one of the holes. Not valid here.
                    return false;
                }
            }

            return true;
        }

        private static Task<TextSpan> GetExpandedNodeSpan(
            Document document,
            SyntaxNode node,
            CancellationToken cancellationToken)
        {
            return GetExpandedTextSpan(document, node.Span, cancellationToken);
        }

        private static async Task<TextSpan> GetExpandedTextSpan(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var nodeStartLine = sourceText.Lines.GetLineFromPosition(span.Start);

            // Enable vertical selections that catch the previous line break and perhaps some whitespace.
            if (nodeStartLine.LineNumber != 0)
            {
                nodeStartLine = sourceText.Lines[nodeStartLine.LineNumber - 1];
            }

            var nodeEndLine = sourceText.Lines.GetLineFromPosition(span.End);

            var start = span.Start;
            var end = span.End;

            while (start > nodeStartLine.Start && char.IsWhiteSpace(sourceText[start - 1]))
            {
                start--;
            }

            while (end < nodeEndLine.End && char.IsWhiteSpace(sourceText[end]))
            {
                end++;
            }

            return TextSpan.FromBounds(start, end);
        }

        /// <summary>
        /// Strips leading and trailing whitespace from <paramref name="span"/>.
        /// </summary>
        /// <remarks>
        /// Returns unchanged <paramref name="span"/> in case <see cref="TextSpan.IsEmpty"/>.
        /// Returns empty Span with original <see cref="TextSpan.Start"/> in case it contains only whitespace.
        /// </remarks>
        private static async Task<TextSpan> GetStrippedTextSpan(
            Document document,
            TextSpan span,
            CancellationToken cancellationToken
            )
        {
            var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
            var start = span.Start;
            var end = span.End;

            if (span.IsEmpty)
            {
                return span;
            }

            while (start < end && char.IsWhiteSpace(sourceText[end - 1]))<commit_msg>cleaup + fix github suggestions commit.<commit_after>﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis
{
    internal static class CodeRefactoringHelpers
    {
        /// <summary>
        /// <para>
        /// Returns an instance of <typeparamref name="TSyntaxNode"/> for refactoring given specified selection in document or null
        /// if no such instance exists.
        /// </para>
        /// <para>
        /// A <typeparamref name="TSyntaxNode"/> instance is returned if selection is zero-width and inside/touching a Token whose
        /// direct parent is of type <typeparamref name="TSyntaxNode"/> or if a Node of said type is the smallest Node containing
        /// the whole <paramref name="selection"/>. Otherwise returns <code>null</code>.
        /// </para>
        /// <para>
        /// Note: this function strips all whitespace from both the beginning and the end of given <paramref name="selection"/>.
        /// The stripped version is then used to determine relevant <see cref="SyntaxNode"/>.
        /// </para>
        /// </summary>
        public static async Task<TSyntaxNode> TryGetSelectedNodeAsync<TSyntaxNode>(
            Document document, TextSpan selection, CancellationToken cancellationToken, bool traverseUpInSyntaxTreeIfOnEdgeOfSpan = false) where TSyntaxNode : SyntaxNode
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var selectionStripped = await GetStrippedTextSpan(document, selection, cancellationToken).ConfigureAwait(false);

            if (root.FindNode(selectionStripped) is TSyntaxNode node)
            {
                return node;
            }

            // e.g. "C LocalFunction[||](C c)" -> root.FindNode return ParameterList but we still want to return LocalFunctionNode
            var touchingToken = await root.SyntaxTree.GetTouchingTokenAsync(selectionStripped.Start,
                token => token.Parent is TSyntaxNode, cancellationToken).ConfigureAwait(false);
            return touchingToken.Parent as TSyntaxNode;
        }

        public static Task<bool> RefactoringSelectionIsValidAsync(
            Document document, TextSpan selection, SyntaxNode node, CancellationToken cancellation)
        {
            return RefactoringSelectionIsValidAsync(document, selection, node, ImmutableArray<SyntaxNode>.Empty, cancellation);
        }

        /// <summary>
        /// <para>
        /// Determines if a refactoring should be offered for a given node, given the specified selection in a document.
        /// The refactoring is offered either if the selection is zero-width and not inside one of the specified holes
        /// or if the selection contains the entire node and perhaps some whitespace.
        /// </para>
        /// <para>
        /// Note: this function considers the span containing the node to start at the earliest preceding whitespace
        /// (including up to one newline) before the node’s <see cref="SyntaxNode.SpanStart"/> and up through the last
        /// whitespace following the <see cref="SyntaxNode.Span"/>’s <see cref="TextSpan.End"/>.
        /// </para>
        /// <para>
        /// For the <paramref name="holes"/>, the position is considered invalid if it is *within* the hole, not if it
        /// is touching the hole edges.
        /// </para>
        /// </summary>
        public static async Task<bool> RefactoringSelectionIsValidAsync(
            Document document,
            TextSpan selection,
            SyntaxNode node,
            ImmutableArray<SyntaxNode> holes,
            CancellationToken cancellationToken)
        {
            if (selection.Length == 0)
            {
                return await RefactoringPositionIsValidAsync(
                    document, selection.Start, node, holes, cancellationToken).ConfigureAwait(false);
            }

            // If we have a selection, it needs to be selecting at least the full node. We allow the selection to start
            // in whitespace (including up to one newline) before the start of the node, and we allow it to end after
            // the end of the node and the end of the line the node ends on.
            var expandedSpan = await GetExpandedNodeSpan(document, node, cancellationToken).ConfigureAwait(false);
            if ((selection.Start >= expandedSpan.Start && selection.Start <= node.SpanStart) &&
                (selection.End >= node.Span.End && selection.End <= expandedSpan.End))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// <para>
        /// Determines if a refactoring should be offered for a given node, given the specified position in a document.
        /// The refactoring is offered if the position is somewhere on the span containing the node and not in any of
        /// the specified <paramref name="holes"/> to avoid.
        /// </para>
        /// <para>
        /// Note: this function considers the span containing the node to start at the earliest preceding whitespace
        /// (including up to one newline) before the node’s <see cref="SyntaxNode.SpanStart"/> and up through the last
        /// whitespace following the <see cref="SyntaxNode.Span"/>’s <see cref="TextSpan.End"/>.
        /// </para>
        /// <para>
        /// For the <paramref name="holes"/>, the position is considered invalid if it is *within* the hole, not if it
        /// is touching the hole edges.
        /// </para>
        /// </summary>
        public static async Task<bool> RefactoringPositionIsValidAsync(
            Document document,
            int position,
            SyntaxNode node,
            ImmutableArray<SyntaxNode> holes,
            CancellationToken cancellationToken)
        {
            var expandedSpan = await GetExpandedNodeSpan(document, node, cancellationToken).ConfigureAwait(false);
            if (!expandedSpan.IntersectsWith(position))
            {
                // Position isn’t within the node’s widened span. Definitely not valid here.
                return false;
            }

            foreach (var hole in holes)
            {
                if (position > hole.Span.Start && position < hole.Span.End)
                {
                    // Position was in one of the holes. Not valid here.
                    return false;
                }
            }

            return true;
        }

        private static Task<TextSpan> GetExpandedNodeSpan(Document document, SyntaxNode node, CancellationToken cancellationToken)
        {
            return GetExpandedTextSpan(document, node.Span, cancellationToken);
        }

        private static async Task<TextSpan> GetExpandedTextSpan(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var nodeStartLine = sourceText.Lines.GetLineFromPosition(span.Start);

            // Enable vertical selections that catch the previous line break and perhaps some whitespace.
            if (nodeStartLine.LineNumber != 0)
            {
                nodeStartLine = sourceText.Lines[nodeStartLine.LineNumber - 1];
            }

            var nodeEndLine = sourceText.Lines.GetLineFromPosition(span.End);

            var start = span.Start;
            var end = span.End;

            while (start > nodeStartLine.Start && char.IsWhiteSpace(sourceText[start - 1]))
            {
                start--;
            }

            while (end < nodeEndLine.End && char.IsWhiteSpace(sourceText[end]))
            {
                end++;
            }

            return TextSpan.FromBounds(start, end);
        }

        /// <summary>
        /// Strips leading and trailing whitespace from <paramref name="span"/>.
        /// </summary>
        /// <remarks>
        /// Returns unchanged <paramref name="span"/> in case <see cref="TextSpan.IsEmpty"/>.
        /// Returns empty Span with original <see cref="TextSpan.Start"/> in case it contains only whitespace.
        /// </remarks>
        private static async Task<TextSpan> GetStrippedTextSpan(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
            var start = span.Start;
            var end = span.End;

            if (span.IsEmpty)
            {
                return span;
            }

            while (start < end && char.IsWhiteSpace(sourceText[end - 1]))
{'fraction_non_alphanumeric': 0.08998556678139225, 'fraction_numerical': 0.0007771733096480515, 'mean_word_length': 2.0405063291139243, 'pattern_counts': {'":': 0, '<': 133, '<?xml version=': 0, '>': 135, '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': '6642064', 'n_tokens_mistral': 4341, 'n_tokens_neox': 4271, 'n_words': 1490}