<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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles
{
    internal struct NamingStyle
    {
        public Guid ID { get; }
        public string Name { get; }
        public string Prefix { get; }
        public string Suffix { get; }
        public string WordSeparator { get; }
        public Capitalization CapitalizationScheme { get; }

        public NamingStyle(
            Guid id, string name = null, string prefix = null, string suffix = null,
            string wordSeparator = null, Capitalization capitalizationScheme = Capitalization.PascalCase) : this()
        {
            ID = id;
            Name = name;
            Prefix = prefix ?? "";
            Suffix = suffix ?? "";
            WordSeparator = wordSeparator ?? "";
            CapitalizationScheme = capitalizationScheme;
        }

        public NamingStyle With(
          Optional<string> name = default(Optional<string>),
          Optional<string> prefix = default(Optional<string>),
          Optional<string> suffix = default(Optional<string>),
          Optional<string> wordSeparator = default(Optional<string>),
          Optional<Capitalization> capitalizationScheme = default(Optional<Capitalization>))
        {
            var newName = name.HasValue ? name.Value : this.Name;
            var newPrefix = prefix.HasValue ? prefix.Value : this.Prefix;
            var newSuffix = suffix.HasValue ? suffix.Value : this.Suffix;
            var newWordSeparator = wordSeparator.HasValue ? wordSeparator.Value : this.WordSeparator;
            var newCapitalizationScheme = capitalizationScheme.HasValue ? capitalizationScheme.Value : this.CapitalizationScheme;

            if (newName == this.Name &&
                newPrefix == this.Prefix &&
                newSuffix == this.Suffix &&
                newWordSeparator == this.WordSeparator &&
                newCapitalizationScheme == this.CapitalizationScheme)
            {
                return this;
            }

            return new NamingStyle(this.ID,
                newName, newPrefix, newSuffix, newWordSeparator, newCapitalizationScheme);
        }

        public string CreateName(IEnumerable<string> words)
        {
            var wordsWithCasing = ApplyCapitalization(words);
            var combinedWordsWithCasing = string.Join(WordSeparator, wordsWithCasing);
            return Prefix + combinedWordsWithCasing + Suffix;
        }

        private IEnumerable<string> ApplyCapitalization(IEnumerable<string> words)
        {
            switch (CapitalizationScheme)
            {
                case Capitalization.PascalCase:
                    return words.Select(CapitalizeFirstLetter);
                case Capitalization.CamelCase:
                    return words.Take(1).Select(DecapitalizeFirstLetter).Concat(words.Skip(1).Select(CapitalizeFirstLetter));
                case Capitalization.FirstUpper:
                    return words.Take(1).Select(CapitalizeFirstLetter).Concat(words.Skip(1).Select(DecapitalizeFirstLetter));
                case Capitalization.AllUpper:
                    return words.Select(w => w.ToUpper());
                case Capitalization.AllLower:
                    return words.Select(w => w.ToLower());
                default:
                    throw new InvalidOperationException();
            }
        }

        private string CapitalizeFirstLetter(string word)
        {
            var chars = word.ToCharArray();
            return new string(chars.Take(1).Select(c => char.ToUpper(c)).Concat(chars.Skip(1)).ToArray());
        }

        private string DecapitalizeFirstLetter(string word)
        {
            var chars = word.ToCharArray();
            return new string(chars.Take(1).Select(c => char.ToLower(c)).Concat(chars.Skip(1)).ToArray());
        }

        public bool IsNameCompliant(string name, out string failureReason)
        {
            if (!name.StartsWith(Prefix))
            {
                failureReason = string.Format(FeaturesResources.Missing_prefix_colon_0, Prefix);
                return false;
            }

            if (!name.EndsWith(Suffix))
            {
                failureReason = string.Format(FeaturesResources.Missing_suffix_colon_0, Suffix);
                return false;
            }

            if (name.Length <= Prefix.Length + Suffix.Length)
            {
                failureReason = null;
                return true;
            }

            var spanToCheck = TextSpan.FromBounds(Prefix.Length, name.Length - Suffix.Length);

            switch (CapitalizationScheme)
            {
                case Capitalization.PascalCase: return CheckPascalCase(name, spanToCheck, out failureReason);
                case Capitalization.CamelCase: return CheckCamelCase(name, spanToCheck, out failureReason);
                case Capitalization.FirstUpper: return CheckFirstUpper(name, spanToCheck, out failureReason);
                case Capitalization.AllUpper: return CheckAllUpper(name, spanToCheck, out failureReason);
                case Capitalization.AllLower: return CheckAllLower(name, spanToCheck, out failureReason);
                default: throw new InvalidOperationException();
            }
        }

        private WordSpanEnumerable GetWordSpans(string name, TextSpan nameSpan)
            => new WordSpanEnumerable(name, nameSpan, WordSeparator);

        private struct WordSpanEnumerable
        {
            private readonly string name;
            private readonly TextSpan nameSpan;
            private readonly string wordSeparator;

            public WordSpanEnumerable(string name, TextSpan nameSpan, string wordSeparator)
            {
                this.name = name;
                this.nameSpan = nameSpan;
                this.wordSeparator = wordSeparator;
            }

            public WordSpanEnumerator GetEnumerator()
                => new WordSpanEnumerator(name, nameSpan, wordSeparator);
        }

        private struct WordSpanEnumerator
        {
            private string name;
            private TextSpan nameSpan;
            private string wordSeparator;

            public WordSpanEnumerator(string name, TextSpan nameSpan, string wordSeparator)
            {
                this.name = name;
                this.nameSpan = nameSpan;
                this.wordSeparator = wordSeparator;
            }

            public TextSpan Current { get { throw new NotImplementedException(); } }
            public bool MoveNext() { throw new NotImplementedException(); }
        }
<commit_msg>Check names without allocations.<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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles
{
    internal struct NamingStyle
    {
        public Guid ID { get; }
        public string Name { get; }
        public string Prefix { get; }
        public string Suffix { get; }
        public string WordSeparator { get; }
        public Capitalization CapitalizationScheme { get; }

        public NamingStyle(
            Guid id, string name = null, string prefix = null, string suffix = null,
            string wordSeparator = null, Capitalization capitalizationScheme = Capitalization.PascalCase) : this()
        {
            ID = id;
            Name = name;
            Prefix = prefix ?? "";
            Suffix = suffix ?? "";
            WordSeparator = wordSeparator ?? "";
            CapitalizationScheme = capitalizationScheme;
        }

        public NamingStyle With(
          Optional<string> name = default(Optional<string>),
          Optional<string> prefix = default(Optional<string>),
          Optional<string> suffix = default(Optional<string>),
          Optional<string> wordSeparator = default(Optional<string>),
          Optional<Capitalization> capitalizationScheme = default(Optional<Capitalization>))
        {
            var newName = name.HasValue ? name.Value : this.Name;
            var newPrefix = prefix.HasValue ? prefix.Value : this.Prefix;
            var newSuffix = suffix.HasValue ? suffix.Value : this.Suffix;
            var newWordSeparator = wordSeparator.HasValue ? wordSeparator.Value : this.WordSeparator;
            var newCapitalizationScheme = capitalizationScheme.HasValue ? capitalizationScheme.Value : this.CapitalizationScheme;

            if (newName == this.Name &&
                newPrefix == this.Prefix &&
                newSuffix == this.Suffix &&
                newWordSeparator == this.WordSeparator &&
                newCapitalizationScheme == this.CapitalizationScheme)
            {
                return this;
            }

            return new NamingStyle(this.ID,
                newName, newPrefix, newSuffix, newWordSeparator, newCapitalizationScheme);
        }

        public string CreateName(IEnumerable<string> words)
        {
            var wordsWithCasing = ApplyCapitalization(words);
            var combinedWordsWithCasing = string.Join(WordSeparator, wordsWithCasing);
            return Prefix + combinedWordsWithCasing + Suffix;
        }

        private IEnumerable<string> ApplyCapitalization(IEnumerable<string> words)
        {
            switch (CapitalizationScheme)
            {
                case Capitalization.PascalCase:
                    return words.Select(CapitalizeFirstLetter);
                case Capitalization.CamelCase:
                    return words.Take(1).Select(DecapitalizeFirstLetter).Concat(words.Skip(1).Select(CapitalizeFirstLetter));
                case Capitalization.FirstUpper:
                    return words.Take(1).Select(CapitalizeFirstLetter).Concat(words.Skip(1).Select(DecapitalizeFirstLetter));
                case Capitalization.AllUpper:
                    return words.Select(w => w.ToUpper());
                case Capitalization.AllLower:
                    return words.Select(w => w.ToLower());
                default:
                    throw new InvalidOperationException();
            }
        }

        private string CapitalizeFirstLetter(string word)
        {
            var chars = word.ToCharArray();
            return new string(chars.Take(1).Select(c => char.ToUpper(c)).Concat(chars.Skip(1)).ToArray());
        }

        private string DecapitalizeFirstLetter(string word)
        {
            var chars = word.ToCharArray();
            return new string(chars.Take(1).Select(c => char.ToLower(c)).Concat(chars.Skip(1)).ToArray());
        }

        public bool IsNameCompliant(string name, out string failureReason)
        {
            if (!name.StartsWith(Prefix))
            {
                failureReason = string.Format(FeaturesResources.Missing_prefix_colon_0, Prefix);
                return false;
            }

            if (!name.EndsWith(Suffix))
            {
                failureReason = string.Format(FeaturesResources.Missing_suffix_colon_0, Suffix);
                return false;
            }

            if (name.Length <= Prefix.Length + Suffix.Length)
            {
                failureReason = null;
                return true;
            }

            var spanToCheck = TextSpan.FromBounds(Prefix.Length, name.Length - Suffix.Length);
            Debug.Assert(spanToCheck.Length > 0);

            switch (CapitalizationScheme)
            {
                case Capitalization.PascalCase: return CheckPascalCase(name, spanToCheck, out failureReason);
                case Capitalization.CamelCase: return CheckCamelCase(name, spanToCheck, out failureReason);
                case Capitalization.FirstUpper: return CheckFirstUpper(name, spanToCheck, out failureReason);
                case Capitalization.AllUpper: return CheckAllUpper(name, spanToCheck, out failureReason);
                case Capitalization.AllLower: return CheckAllLower(name, spanToCheck, out failureReason);
                default: throw new InvalidOperationException();
            }
        }

        private WordSpanEnumerable GetWordSpans(string name, TextSpan nameSpan)
            => new WordSpanEnumerable(name, nameSpan, WordSeparator);

        private struct WordSpanEnumerable
        {
            private readonly string _name;
            private readonly TextSpan _nameSpan;
            private readonly string _wordSeparator;

            public WordSpanEnumerable(string name, TextSpan nameSpan, string wordSeparator)
            {
                _name = name;
                _nameSpan = nameSpan;
                _wordSeparator = wordSeparator;
            }

            public WordSpanEnumerator GetEnumerator()
                => new WordSpanEnumerator(_name, _nameSpan, _wordSeparator);
        }

        private struct WordSpanEnumerator
        {
            private readonly string _name;
            private readonly TextSpan _nameSpan;
            private readonly string _wordSeparator;

            public WordSpanEnumerator(string name, TextSpan nameSpan, string wordSeparator)
            {
                Debug.Assert(nameSpan.Length > 0);
                _name = name;
                _nameSpan = nameSpan;
                _wordSeparator = wordSeparator;
                Current = new TextSpan(nameSpan.Start, 0);
            }

            public TextSpan Current { get; private set; }

            public bool MoveNext()
            {
                if (_wordSeparator == "")
                {
                    // No separator.  So only ever return a single word
                    if (Current.Length == 0)
                    {
                        Current = _nameSpan;
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }

                while (true)
                {
                    var nextWordSeparator = _name.IndexOf(_wordSeparator, Current.End);
                    if (nextWordSeparator == Current.End)
                    {
                        // We're right at the word separator.  Skip it and continue searching.
                        Current = new TextSpan(Current.End + _wordSeparator.Length, 0);
                        continue;
                    }

                    // If didn't find a word separator, it's as if the next word separator is at the end of name span.
                    if (nextWordSeparator < 0)
                    {
                        nextWordSeparator = _nameSpan.End;
                    }

                    // found a separator in front of us.  Note: it may be in our suffix portion.  
                    // So us the min of the separator position and our end position.
                    Current = new TextSpan(Current.End, Math.Min(_nameSpan.End, nextWordSeparator));
                    break;
                }

                return Current.Length > 0 && Current.End <= _nameSpan.End;
            }
        }

{'fraction_non_alphanumeric': 0.07773487773487774, 'fraction_numerical': 0.001994851994851995, 'mean_word_length': 1.855226896931839, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 44, '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': '6990044', 'n_tokens_mistral': 3781, 'n_tokens_neox': 3652, 'n_words': 898}