private static void ProcessT4Local(string id, MemberDeclarationSyntax memberNode, ClassDeclarationSyntax classNode, GeneratorExecutionContext context, T4Processor t4Processor, HashSet <string> alreadyProcessedT4, StringBuilder sb) { if (memberNode.AttributeLists == null) { return; } foreach (AttributeSyntax t4Attr in memberNode.GetAttributesOfType(Consts.ApplyT4Attributes)) { if (!t4Attr.HasAttributeParameter(Consts.T4TemplateScopeEnums, "Local")) { continue; } context.CancellationToken.ThrowIfCancellationRequested(); if (t4Attr.ArgumentList != null && t4Attr.ArgumentList.Arguments.Count > 0) { string templateName = t4Attr.ArgumentList.Arguments[0].ToString().Trim('\"'); string processedName = (classNode.SyntaxTree.FilePath + "::" + id + "::" + templateName).ToLower(); // apply to the source only once if (alreadyProcessedT4.Contains(processedName)) { continue; } t4Processor.ProcessTemplate(context, t4Attr, classNode, sb); alreadyProcessedT4.Add(processedName); } } }
public static StringBuilder ProcessAddTrait(GeneratorExecutionContext context, HashSet <string> generatedFiles, HashSet <string> alreadyProcessedT4, T4Processor t4Processor, AttributeSyntax addTraitAttr, SemanticModel semanticModel, ClassDeclarationSyntax destClass, HashSet <string> alreadyProcessedTraits) { StringBuilder sb = new(); var firstParam = addTraitAttr.ArgumentList?.Arguments.FirstOrDefault(); var typeofTraitNode = firstParam?.DescendantNodes().OfType <TypeOfExpressionSyntax>().FirstOrDefault(); if (typeofTraitNode == null) { return(Utils.ReturnError($"AddTrait param must be typeof(Type), but got {firstParam}")); } TypeInfo addTraitType = semanticModel.GetTypeInfo(typeofTraitNode.Type); string traitClassName = addTraitType.Type?.ToDisplayString(); if (addTraitType.Type == null) { return(Utils.ReturnError($"the specified type \"{traitClassName}\" doesn't have any attributes")); } if (alreadyProcessedTraits.Contains(traitClassName)) { return(Utils.ReturnError($"the trait \"{traitClassName}\" was already procesed, cannot have duplicate traits")); } alreadyProcessedTraits.Add(traitClassName); if (!addTraitType.Type.GetAttributes().Any(w => Consts.TraitAttributes.Contains(w.AttributeClass?.Name))) { return(Utils.ReturnError($"the specified type \"{traitClassName}\" doesn't have Trait attribute")); } SyntaxNode traitTypeSyntax = semanticModel.GetSymbolInfo(typeofTraitNode.Type).Symbol?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); if (traitTypeSyntax is not ClassDeclarationSyntax traitClassNode) { return(Utils.ReturnError($"the specified Trait \"{traitClassName}\" is not a class")); } var traitCompUnit = traitClassNode.Ancestors().OfType <CompilationUnitSyntax>().FirstOrDefault(); if (traitCompUnit == null) { return(Utils.ReturnError($"cannot find computational unit for the class node {traitClassNode.Identifier}")); } string destinationGenerics = Utils.RemoveWhitespace(destClass.TypeParameterList?.ToString()); string traitGenerics = Utils.RemoveWhitespace(traitClassNode.TypeParameterList?.ToString()); if (traitGenerics != destinationGenerics) { return(Utils.ReturnError($"Generics definition should be exactly the same for Trait and destination classes, but got {Utils.RemoveNewLine(traitClassNode.TypeParameterList?.ToString())} and {Utils.RemoveNewLine(destClass.TypeParameterList?.ToString())}")); } string destConstraints = Utils.RemoveWhitespace(destClass.ConstraintClauses.ToString()); string traitConstraints = Utils.RemoveWhitespace(traitClassNode.ConstraintClauses.ToString()); if (traitConstraints != destConstraints) { return(Utils.ReturnError($"Constraints definitions should be exactly the same for Trait and destination classes, but got {Utils.RemoveNewLine(traitClassNode.ConstraintClauses.ToString())} and {Utils.RemoveNewLine(destClass.ConstraintClauses.ToString())}")); } bool hadUsings = false; foreach (var traitUsingDirective in traitCompUnit.Usings.Where(w => !Consts.IgnoreUsings.Contains(w.Name.ToString()))) { sb.AppendLine(traitUsingDirective.ToString()); hadUsings = true; } if (hadUsings) { sb.AppendLine(""); } var traitNamespaceNode = traitClassNode.Ancestors().OfType <NamespaceDeclarationSyntax>().FirstOrDefault(); if (traitNamespaceNode != null) { sb.AppendLine($"namespace {traitNamespaceNode.Name} {{"); hadUsings = false; foreach (var traitNamespaceUsing in traitNamespaceNode.Usings.Where(w => !Consts.IgnoreUsings.Contains(w.Name.ToString()))) { sb.AppendLine(traitNamespaceUsing.ToString()); hadUsings = true; } if (hadUsings) { sb.AppendLine(""); } } sb.AppendLine($" {destClass.Modifiers} class {destClass.Identifier} {{"); // process all destination class local T4 templates ProcessT4Local(destClass.Identifier.ToString(), destClass, destClass, context, t4Processor, alreadyProcessedT4, sb); // process all trait class local T4 templates ProcessT4Local(traitClassNode.Identifier.ToString(), traitClassNode, destClass, context, t4Processor, alreadyProcessedT4, sb); // process all destination members' nodes local T4 templates foreach (MemberDeclarationSyntax destMemberNode in destClass.Members) { ProcessT4Local(GetMemberNodeId(destMemberNode), destMemberNode, destClass, context, t4Processor, alreadyProcessedT4, sb); } List <MemberNodeInfo> destMembersInfo = Utils.GetAllMembersInfo(destClass); foreach (MemberDeclarationSyntax traitMemberNode in traitClassNode.Members) { AttributeSyntax[] traitMemberAttrs = traitMemberNode.AttributeLists.SelectMany(s => s.Attributes).ToArray(); // process local T4 templates for trait members ProcessT4Local(GetMemberNodeId(traitMemberNode), traitMemberNode, destClass, context, t4Processor, alreadyProcessedT4, sb); // if has an ignore attribute, skip the member if (traitMemberAttrs.Any(w => Consts.TraitIgnoreAttributes.Contains(w.Name.ToString()))) { continue; } // if has process attribute, execute and add to the class or to a new file, depending on the attribute option AttributeSyntax processAttr = traitMemberAttrs.FirstOrDefault(w => Consts.TraitProcessAttributes.Contains(w.Name.ToString())); if (processAttr != null) { if (CheckProcessMethod(sb, traitMemberNode, "Process", out MethodDeclarationSyntax methodNode)) { ExecuteMethod(context, generatedFiles, destClass, traitClassName, methodNode, processAttr, sb); } continue; } bool canBeOverriden = traitMemberAttrs.Any(w => Consts.OverrideableAttributes.Contains(w.Name.ToString())); List <MemberNodeInfo> traitMemberInfos = Utils.GetMemberNodeInfo(traitMemberNode); MemberNodeInfo[] sameNameCandidates = destMembersInfo.Where(w => traitMemberInfos.Any(a => a.Name == w.Name)).ToArray(); // check if destination class has members with the same name (and the same signature for methods) if (sameNameCandidates.Length > 0 && SkipToOverride(sameNameCandidates, traitMemberInfos, sb, traitMemberNode, canBeOverriden)) { continue; } var outputNode = traitMemberNode; if (canBeOverriden) { outputNode = Utils.RemoveAttributes(traitMemberNode, Consts.OverrideableAttributes); } sb.Append(outputNode.ToFullString()); } sb.AppendLine(" }"); if (traitNamespaceNode != null) { sb.AppendLine("}"); } return(sb); }