File: EditorConfigFile.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.EditorConfig.Parsing
{
    /// <summary>
    /// Base representation of an editorconfig file that has been parsed
    /// </summary>
    /// <typeparam name="T">The kind of options that we expect to encounter in the editorconfig file.</typeparam>
    /// <param name="FilePath">The full path to the editorconfig file on disk. Optional if not doing pathwise comparisons</param>
    /// <param name="Options">The set of options that were discovered in the file.</param>
    internal record class EditorConfigFile<T>(string? FilePath, ImmutableArray<T> Options)
        where T : EditorConfigOption
    {
 
        private readonly Lazy<ImmutableArray<Section>> _sections = new(() => Options.SelectAsArray(x => x.Section).Distinct());
 
        public ImmutableArray<Section> Sections => _sections.Value;
 
        /// <summary>
        /// Attempts to find a section of the editorconfig file that is an exact match for the given language.
        /// </summary>
        public bool TryGetSectionForLanguage(
            Language language,
            [NotNullWhen(true)] out Section? sectionResult)
            => TryGetSectionForLanguage(language, SectionMatch.ExactLanguageMatch, out sectionResult);
 
        /// <summary>
        /// Attempts to find a section of the editorconfig file that applies to the given language for the given criteria.
        /// </summary>
        public bool TryGetSectionForLanguage(
            Language language,
            SectionMatch matchKind,
            [NotNullWhen(true)] out Section? sectionResult)
        {
            sectionResult = Sections
                .Select(section => (matchKind: section.GetMatchKind(language), section))
                .Where(tuple => tuple.matchKind.IsBetterOrEqualMatchThan(matchKind))
                .OrderBy(x => x.matchKind)
                .ThenByDescending(x => x.section.Span.Start)
                .Select(x => x.section)
                .FirstOrDefault();
            return sectionResult is not null;
        }
 
        /// <summary>
        /// Attempts to find a section of the editorconfig file that applies to the given file.
        /// </summary>
        public bool TryGetSectionForFilePath(
            string filePath,
            [NotNullWhen(true)] out Section? sectionResult)
        {
            if (FilePath is null)
            {
                throw new InvalidOperationException("No path was given for this editorconfig file");
            }
 
            return TryGetSectionForFilePath(filePath, SectionMatch.ExactLanguageMatch, out sectionResult);
        }
 
        /// <summary>
        /// Attempts to find a section of the editorconfig file that applies to the given file for the given criteria.
        /// </summary>
        public bool TryGetSectionForFilePath(
            string filePath,
            SectionMatch matchKind,
            [NotNullWhen(true)] out Section? sectionResult)
        {
            if (FilePath is null)
            {
                throw new InvalidOperationException("No path was given for this editorconfig file");
            }
 
            sectionResult = Sections
                .SelectAsArray(section => (matchKind: section.GetMatchKind(filePath), section))
                .WhereAsArray(tuple => tuple.matchKind.IsBetterOrEqualMatchThan(matchKind))
                .OrderBy(x => x.matchKind) // Sort by best match kind
                .ThenByDescending(x => x.section.Span.Start) // in event of a further tie, pick entry at the bottom of the file
                .Select(x => x.section)
                .FirstOrDefault();
            return sectionResult is not null;
        }
    }
}