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();
}
}
}