File: CodeAnalysisDictionary.cs
Web Access
Project: src\src\RoslynAnalyzers\Text.Analyzers\Core\Text.Analyzers.csproj (Text.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
 
namespace Text.Analyzers
{
    /// <summary>
    /// Source for "recognized" misspellings and "unrecognized" spellings obtained by parsing either XML or DIC code
    /// analysis dictionaries.
    /// </summary>
    /// <Remarks>
    /// <seealso href="https://learn.microsoft.com/visualstudio/code-quality/how-to-customize-the-code-analysis-dictionary"/>
    /// </Remarks>
    internal sealed class CodeAnalysisDictionary
    {
        /// <summary>
        /// A list of misspelled words that the spell checker will now ignore.
        /// </summary>
        /// <example>
        /// <code>
        /// <Recognized>
        ///     <Word>knokker</Word>
        /// </Recognized>
        /// </code>
        /// </example>
        private readonly HashSet<string> _recognizedWords;
 
        /// <summary>
        /// A list of correctly spelled words that the spell checker will now report.
        /// </summary>
        /// <example>
        /// <code>
        /// <Unrecognized>
        ///     <Word>meth</Word>
        /// </Unrecognized>
        /// </code>
        /// </example>
        private readonly HashSet<string> _unrecognizedWords;
 
        /// <summary>
        /// Initialize a new instance of <see cref="CodeAnalysisDictionary"/>.
        /// </summary>
        /// <param name="recognizedWords">Misspelled words that the spell checker will now ignore.</param>
        /// <param name="unrecognizedWords">Correctly spelled words that the spell checker will now report.</param>
        private CodeAnalysisDictionary(IEnumerable<string> recognizedWords, IEnumerable<string> unrecognizedWords)
        {
            _recognizedWords = new HashSet<string>(recognizedWords, StringComparer.OrdinalIgnoreCase);
            _unrecognizedWords = new HashSet<string>(unrecognizedWords, StringComparer.OrdinalIgnoreCase);
        }
 
        /// <summary>
        /// Creates a new instance of this class with recognized and unrecognized words (if specified) loaded
        /// from the specified XML <paramref name="streamReader"/>.
        /// </summary>
        /// <param name="streamReader">XML stream of a code analysis dictionary.</param>
        /// <returns>A new instance of this class with the words loaded.</returns>
        public static CodeAnalysisDictionary CreateFromXml(StreamReader streamReader)
        {
            var document = XDocument.Load(streamReader);
            // TODO: Include Deprecated/Compound terms as noted here:
            // https://learn.microsoft.com/visualstudio/code-quality/how-to-customize-the-code-analysis-dictionary
            // Tracked by:
            // https://github.com/dotnet/roslyn-analyzers/issues/4693
            return new CodeAnalysisDictionary(
                GetSectionWords(document, "Recognized", "Word"),
                GetSectionWords(document, "Unrecognized", "Word")
            );
        }
 
        /// <summary>
        /// Creates a new instance of this class with recognized words loaded from the specified DIC <paramref name="streamReader"/>.
        /// </summary>
        /// <remarks>
        /// A DIC file usually has an extension of ".dic". It consists of a list of newline-delimited words.
        /// </remarks>
        /// <param name="streamReader">DIC stream of a code analysis dictionary.</param>
        /// <returns>A new instance of this class with recognized words loaded.</returns>
        public static CodeAnalysisDictionary CreateFromDic(StreamReader streamReader)
        {
            var recognizedWords = new List<string>();
 
            string word;
            while ((word = streamReader.ReadLine()) != null)
            {
                var trimmedWord = word.Trim();
                if (trimmedWord.Length > 0)
                {
                    recognizedWords.Add(trimmedWord);
                }
            }
 
            return new CodeAnalysisDictionary(recognizedWords, Enumerable.Empty<string>());
        }
 
        private static IEnumerable<string> GetSectionWords(XDocument document, string section, string property)
            => document.Descendants(section).SelectMany(section => section.Elements(property)).Select(element => element.Value.Trim());
 
        public bool ContainsUnrecognizedWord(string word)
            => _unrecognizedWords.Contains(word);
 
        public bool ContainsRecognizedWord(string word)
            => _recognizedWords.Contains(word);
    }
}