using System; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; namespace Vellum.Generator { /// /// Describes a SaveConverter as plain data, which an incremental generator step can compare between runs. A symbol /// or a Location can't be compared that way, since each edit makes new ones. /// public sealed class ConverterModel : IEquatable { public readonly string AssemblyName; public readonly string MetadataName; public readonly string Name; public readonly string ConvertedName; public readonly ConverterProblem Problem; /// /// Holds the file the converter is declared in, or null if it's in a referenced assembly. /// public readonly string FilePath; public readonly TextSpan Span; public readonly LinePositionSpan LineSpan; /// /// Holds the name of the base class the converter saves every derived type of, or null if it isn't a family. /// public readonly string FamilyBase; /// /// Holds the positions of the converted generic type's arguments that the converter marks [Saveable]. /// public readonly EquatableArray SaveablePositions; /// /// Holds whether the family's base is an interface, as opposed to a class. /// public readonly bool FamilyIsInterface; /// /// Holds the name of each interface the family's interface extends. /// public readonly EquatableArray FamilyExtends; /// /// Holds the name of the assembly that defines the family's base, or null if it isn't a family. /// public readonly string FamilyBaseAssembly; /// /// Holds what keeps an unmatchable converter from being matched, worded to finish "but ...", or null if the /// converter isn't unmatchable. /// public readonly string ProblemDetail; private readonly SourcePlace place; public ConverterModel(string assemblyName, string metadataName, string name, string convertedName, ConverterProblem problem, string filePath, TextSpan span, LinePositionSpan lineSpan, string familyBase = null, EquatableArray saveablePositions = default, bool familyIsInterface = false, EquatableArray familyExtends = default, string familyBaseAssembly = null, string problemDetail = null) { ProblemDetail = problemDetail; FamilyBaseAssembly = familyBaseAssembly; FamilyBase = familyBase; SaveablePositions = saveablePositions; FamilyIsInterface = familyIsInterface; FamilyExtends = familyExtends; AssemblyName = assemblyName; MetadataName = metadataName; Name = name; ConvertedName = convertedName; Problem = problem; FilePath = filePath; Span = span; LineSpan = lineSpan; place = new SourcePlace(filePath, span, lineSpan); } /// /// Returns the name finds by. /// public static string MetadataNameOf(INamedTypeSymbol type) { string returnMe = type.MetadataName; for (INamedTypeSymbol outer = type.ContainingType; outer != null; outer = outer.ContainingType) { returnMe = outer.MetadataName + "+" + returnMe; } if (!type.ContainingNamespace.IsGlobalNamespace) { returnMe = type.ContainingNamespace.ToDisplayString() + "." + returnMe; } return returnMe; } /// /// Gets how C# names the converter in a typeof, with any type arguments left open. /// public string TypeofName { get { var returnMe = new StringBuilder("global::"); for (int i = 0; i < MetadataName.Length; ++i) { char letter = MetadataName[i]; if (letter == '+') { returnMe.Append('.'); } else if (letter == '`') { int arity = 0; while (i + 1 < MetadataName.Length && char.IsDigit(MetadataName[i + 1])) { ++i; arity = arity * 10 + (MetadataName[i] - '0'); } returnMe.Append('<').Append(',', Math.Max(arity - 1, 0)).Append('>'); } else { returnMe.Append(letter); } } return returnMe.ToString(); } } /// /// Returns this converter with a different problem. /// public ConverterModel WithProblem(ConverterProblem problem) { return new ConverterModel(AssemblyName, MetadataName, Name, ConvertedName, problem, FilePath, Span, LineSpan, FamilyBase, SaveablePositions, FamilyIsInterface, FamilyExtends, FamilyBaseAssembly, ProblemDetail); } /// /// Returns the problem as a diagnostic where the converter is declared in , or /// null if it has none. /// public Diagnostic ToDiagnostic(Compilation compilation) { DiagnosticDescriptor descriptor; switch (Problem) { case ConverterProblem.Unusable: descriptor = VellumDiagnostics.UnusableConverter; break; case ConverterProblem.BuiltIn: descriptor = VellumDiagnostics.BuiltInConverter; break; case ConverterProblem.Duplicate: descriptor = VellumDiagnostics.DuplicateConverter; break; case ConverterProblem.Unmatchable: descriptor = VellumDiagnostics.UnmatchableConverter; break; case ConverterProblem.Interface: descriptor = VellumDiagnostics.InterfaceConverter; break; default: return null; } return Diagnostic.Create(descriptor, place.LocationIn(compilation), Name, ConvertedName, ProblemDetail); } public bool Equals(ConverterModel other) { return other != null && AssemblyName == other.AssemblyName && MetadataName == other.MetadataName && Name == other.Name && ConvertedName == other.ConvertedName && Problem == other.Problem && FilePath == other.FilePath && Span == other.Span && LineSpan == other.LineSpan && FamilyBase == other.FamilyBase && SaveablePositions.Equals(other.SaveablePositions) && FamilyIsInterface == other.FamilyIsInterface && FamilyExtends.Equals(other.FamilyExtends) && FamilyBaseAssembly == other.FamilyBaseAssembly && ProblemDetail == other.ProblemDetail; } public override bool Equals(object obj) { return Equals(obj as ConverterModel); } public override int GetHashCode() { return (MetadataName.GetHashCode() * 31 + ConvertedName.GetHashCode()) * 31 + Span.GetHashCode(); } } }