|
// 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.IO;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.EditorConfig.Parsing
{
internal sealed class Section : IEquatable<Section>
{
public string? FilePath { get; init; }
public bool IsGlobal { get; init; }
public TextSpan Span { get; init; }
public string Text { get; init; }
public string FullText { get; init; }
private bool IsSplatHeader => Text.Equals("*", StringComparison.Ordinal);
private readonly SectionMatcher? _matcher;
private readonly string? _containingDirectory;
public Section(string? filePath, bool isGlobal, TextSpan span, string text, string fullText)
{
FilePath = filePath;
IsGlobal = isGlobal;
Span = span;
Text = text;
FullText = fullText;
if (SectionMatcher.TryParseSection(Text, out var matcher))
{
_matcher = matcher;
}
if (FilePath is not null)
{
var containingDirectory = Path.GetDirectoryName(FilePath);
RoslynDebug.AssertNotNull(containingDirectory);
_containingDirectory = containingDirectory.ToLowerInvariant();
}
}
/// <summary>
/// Returns the default section header text for the given language combination
/// </summary>
/// <param name="language">The language combination to find the default header text for.</param>
/// <returns>the default header text.</returns>
public static string? GetHeaderTextForLanguage(Language language)
{
if (language.HasFlag(Language.CSharp) && language.HasFlag(Language.VisualBasic))
{
return "[*.{cs,vb}]\r\n";
}
else if (language.HasFlag(Language.CSharp))
{
return "[*.cs]\r\n";
}
else if (language.HasFlag(Language.VisualBasic))
{
return "[*.vb]\r\n";
}
return null;
}
/// <summary>
/// Checks where this header supports the given language for the given match criteria
/// </summary>
/// <param name="language">The language to check support for.</param>
/// <param name="matchKind">The criteria for which we consider a language a mache the default is <see cref="SectionMatch.ExactLanguageMatch"/>.</param>
/// <returns>If this section is a match for the given language, meaning options can be added here.</returns>
public bool SupportsLanguage(Language language, SectionMatch matchKind = default)
=> GetMatchKind(language).IsBetterOrEqualMatchThan(matchKind);
/// <summary>
/// Checks where this header supports the given file path for the given match criteria
/// </summary>
/// <param name="codeFilePath">full path to a file</param>
/// <param name="matchKind">The criteria for which we consider a language a mache the default is <see cref="SectionMatch.ExactLanguageMatch"/>.</param>
/// <remarks>
/// If the section header cannot be parsed because it it invalid this method will always return no match.
/// If no file path was given in the operation that produces this section and a relative path comparison is required to check for support this method will return no match.
/// </remarks>
/// <returns>If this section is a match for the given file, meaning options can be added here.</returns>
public bool SupportsFilePath(string codeFilePath, SectionMatch matchKind = default)
=> GetMatchKind(codeFilePath).IsBetterOrEqualMatchThan(matchKind);
public SectionMatch GetMatchKind(Language language)
{
if (IsGlobal)
{
return SectionMatch.GlobalSectionMatch;
}
if (IsSplatHeader)
{
return SectionMatch.SplatMatch;
}
if (_matcher is not { } matcher)
{
// the header could not be parsed
return SectionMatch.NoMatch;
}
return matcher.GetLanguageMatchKind(language);
}
public SectionMatch GetMatchKind(string codeFilePath)
{
if (IsGlobal)
{
return SectionMatch.GlobalSectionMatch;
}
if (IsSplatHeader)
{
return SectionMatch.SplatMatch;
}
if (_matcher is not { } matcher)
{
// the header could not be parsed
return SectionMatch.NoMatch;
}
if (!codeFilePath.TryGetLanguageFromFilePath(out var language))
{
// the file is from an unknown language
return SectionMatch.NoMatch;
}
var languageMatchKind = matcher.GetLanguageMatchKind(language);
if (_containingDirectory is null)
{
if (languageMatchKind.IsWorseMatchThan(SectionMatch.AnyLanguageMatch))
{
// no editorconfig path was given and we need to
// evaluate paths relative to each other to give an answer
throw new InvalidOperationException("No path given for editorconfig");
}
return languageMatchKind;
}
var relativePath = GetPathRelativeToEditorconfig(_containingDirectory, codeFilePath);
if (relativePath.IsEmpty())
{
// we _are_ the editorconfig file so we match
return SectionMatch.SupersetFilePatternMatch;
}
return matcher.GetPathMatchKind(relativePath);
static string GetPathRelativeToEditorconfig(string directoryContainingEditorconfig, string codeFilePath)
{
var relativePath = PathUtilities.GetRelativePath(directoryContainingEditorconfig, codeFilePath);
relativePath = relativePath.Replace("\\", "/");
if (!relativePath.StartsWith("/"))
{
relativePath = "/" + relativePath;
}
return relativePath;
}
}
public static bool operator !=(Section left, Section right)
=> !(left == right);
public static bool operator ==(Section left, Section right)
=> ReferenceEquals(left, right) ||
(left is not null && left.Equals(right));
public override bool Equals(object? obj)
=> Equals(obj as Section);
public bool Equals(Section? other)
=> ReferenceEquals(this, other) ||
(other is not null &&
StringComparer.OrdinalIgnoreCase.Equals(FilePath, other.FilePath) &&
Span == other.Span &&
Text == other.Text);
public override int GetHashCode()
=> Hash.Combine(
StringComparer.OrdinalIgnoreCase.GetHashCode(FilePath ?? string.Empty),
Hash.Combine(
Span.GetHashCode(),
Text.GetHashCode()));
}
}
|