// Returns null if nothing to do, Diagnostic if there's an error to report, or RegexType if the type was analyzed successfully. private static object?GetSemanticTargetForGeneration( GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { var methodSyntax = (MethodDeclarationSyntax)context.TargetNode; SemanticModel sm = context.SemanticModel; Compilation compilation = sm.Compilation; INamedTypeSymbol?regexSymbol = compilation.GetBestTypeByMetadataName(RegexName); INamedTypeSymbol?generatedRegexAttributeSymbol = compilation.GetBestTypeByMetadataName(GeneratedRegexAttributeName); if (regexSymbol is null || generatedRegexAttributeSymbol is null) { // Required types aren't available return(null); } TypeDeclarationSyntax?typeDec = methodSyntax.Parent as TypeDeclarationSyntax; if (typeDec is null) { return(null); } IMethodSymbol regexMethodSymbol = context.TargetSymbol as IMethodSymbol; if (regexMethodSymbol is null) { return(null); } ImmutableArray <AttributeData>?boundAttributes = regexMethodSymbol.GetAttributes(); if (boundAttributes is null || boundAttributes.Value.Length == 0) { return(null); } bool attributeFound = false; string?pattern = null; int? options = null; int? matchTimeout = null; string?cultureName = string.Empty; foreach (AttributeData attributeData in boundAttributes) { if (!SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, generatedRegexAttributeSymbol)) { continue; } if (attributeData.ConstructorArguments.Any(ca => ca.Kind == TypedConstantKind.Error)) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, methodSyntax.GetLocation())); } if (pattern is not null) { return(Diagnostic.Create(DiagnosticDescriptors.MultipleGeneratedRegexAttributes, methodSyntax.GetLocation())); } ImmutableArray <TypedConstant> items = attributeData.ConstructorArguments; if (items.Length == 0 || items.Length > 4) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, methodSyntax.GetLocation())); } attributeFound = true; pattern = items[0].Value as string; if (items.Length >= 2) { options = items[1].Value as int?; if (items.Length == 4) { matchTimeout = items[2].Value as int?; cultureName = items[3].Value as string; } // If there are 3 parameters, we need to check if the third argument is // int matchTimeoutMilliseconds, or string cultureName. else if (items.Length == 3) { if (items[2].Type.SpecialType == SpecialType.System_Int32) { matchTimeout = items[2].Value as int?; } else { cultureName = items[2].Value as string; } } } } if (!attributeFound) { return(null); } if (pattern is null || cultureName is null) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "(null)")); } if (!regexMethodSymbol.IsPartialDefinition || regexMethodSymbol.IsAbstract || regexMethodSymbol.Parameters.Length != 0 || regexMethodSymbol.Arity != 0 || !SymbolEqualityComparer.Default.Equals(regexMethodSymbol.ReturnType, regexSymbol)) { return(Diagnostic.Create(DiagnosticDescriptors.RegexMethodMustHaveValidSignature, methodSyntax.GetLocation())); } RegexOptions regexOptions = options is not null ? (RegexOptions)options : RegexOptions.None; // If RegexOptions.IgnoreCase was specified or the inline ignore case option `(?i)` is present in the pattern, then we will (in priority order): // - If a culture name was passed in: // - If RegexOptions.CultureInvariant was also passed in, then we emit a diagnostic due to the explicit conflict. // - We try to initialize a culture using the passed in culture name to be used for case-sensitive comparisons. If // the culture name is invalid, we'll emit a diagnostic. // - Default to use Invariant Culture if no culture name was passed in. CultureInfo culture = CultureInfo.InvariantCulture; RegexOptions regexOptionsWithPatternOptions; try { regexOptionsWithPatternOptions = regexOptions | RegexParser.ParseOptionsInPattern(pattern, regexOptions); } catch (Exception e) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), e.Message)); } if ((regexOptionsWithPatternOptions & RegexOptions.IgnoreCase) != 0 && !string.IsNullOrEmpty(cultureName)) { if ((regexOptions & RegexOptions.CultureInvariant) != 0) { // User passed in both a culture name and set RegexOptions.CultureInvariant which causes an explicit conflict. return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "cultureName")); } try { culture = CultureInfo.GetCultureInfo(cultureName); } catch (CultureNotFoundException) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "cultureName")); } } // Validate the options const RegexOptions SupportedOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ECMAScript | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.NonBacktracking | RegexOptions.RightToLeft | RegexOptions.Singleline; if ((regexOptions & ~SupportedOptions) != 0) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "options")); } // Validate the timeout if (matchTimeout is 0 or < -1) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "matchTimeout")); } // Parse the input pattern RegexTree regexTree; AnalysisResults analysis; try { regexTree = RegexParser.Parse(pattern, regexOptions | RegexOptions.Compiled, culture); // make sure Compiled is included to get all optimizations applied to it analysis = RegexTreeAnalyzer.Analyze(regexTree); } catch (Exception e) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), e.Message)); } // Determine the namespace the class is declared in, if any string?ns = regexMethodSymbol.ContainingType?.ContainingNamespace?.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)); var regexType = new RegexType( typeDec is RecordDeclarationSyntax rds ? $"{typeDec.Keyword.ValueText} {rds.ClassOrStructKeyword}" : typeDec.Keyword.ValueText, ns ?? string.Empty, $"{typeDec.Identifier}{typeDec.TypeParameterList}"); var regexMethod = new RegexMethod( regexType, methodSyntax, regexMethodSymbol.Name, methodSyntax.Modifiers.ToString(), pattern, regexOptions, matchTimeout, regexTree, analysis); RegexType current = regexType; var parent = typeDec.Parent as TypeDeclarationSyntax; while (parent is not null && IsAllowedKind(parent.Kind())) { current.Parent = new RegexType( parent is RecordDeclarationSyntax rds2 ? $"{parent.Keyword.ValueText} {rds2.ClassOrStructKeyword}" : parent.Keyword.ValueText, ns ?? string.Empty, $"{parent.Identifier}{parent.TypeParameterList}"); current = current.Parent; parent = parent.Parent as TypeDeclarationSyntax; } return(regexMethod);
private static EventSourceClass?GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { const string EventSourceAttribute = "System.Diagnostics.Tracing.EventSourceAttribute"; var classDef = (ClassDeclarationSyntax)context.TargetNode; NamespaceDeclarationSyntax?ns = classDef.Parent as NamespaceDeclarationSyntax; if (ns is null) { if (classDef.Parent is not CompilationUnitSyntax) { // since this generator doesn't know how to generate a nested type... return(null); } } EventSourceClass?eventSourceClass = null; string? nspace = null; foreach (AttributeData attribute in context.TargetSymbol.GetAttributes()) { if (attribute.AttributeClass?.Name != "EventSourceAttribute" || attribute.AttributeClass.ToDisplayString() != EventSourceAttribute) { continue; } nspace ??= ConstructNamespace(ns); string className = classDef.Identifier.ValueText; string name = className; string guid = ""; ImmutableArray <KeyValuePair <string, TypedConstant> > args = attribute.NamedArguments; foreach (KeyValuePair <string, TypedConstant> arg in args) { string argName = arg.Key; string value = arg.Value.Value?.ToString(); switch (argName) { case "Guid": guid = value; break; case "Name": name = value; break; } } if (!Guid.TryParse(guid, out Guid result)) { result = GenerateGuidFromName(name.ToUpperInvariant()); } eventSourceClass = new EventSourceClass(nspace, className, name, result); continue; } return(eventSourceClass); }
// Returns null if nothing to do, Diagnostic if there's an error to report, or RegexType if the type was analyzed successfully. private static object?GetSemanticTargetForGeneration( GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { var methodSyntax = (MethodDeclarationSyntax)context.TargetNode; SemanticModel sm = context.SemanticModel; Compilation compilation = sm.Compilation; INamedTypeSymbol?regexSymbol = compilation.GetBestTypeByMetadataName(RegexName); INamedTypeSymbol?regexGeneratorAttributeSymbol = compilation.GetBestTypeByMetadataName(RegexGeneratorAttributeName); if (regexSymbol is null || regexGeneratorAttributeSymbol is null) { // Required types aren't available return(null); } TypeDeclarationSyntax?typeDec = methodSyntax.Parent as TypeDeclarationSyntax; if (typeDec is null) { return(null); } IMethodSymbol regexMethodSymbol = context.TargetSymbol as IMethodSymbol; if (regexMethodSymbol is null) { return(null); } ImmutableArray <AttributeData>?boundAttributes = regexMethodSymbol.GetAttributes(); if (boundAttributes is null || boundAttributes.Value.Length == 0) { return(null); } bool attributeFound = false; string?pattern = null; int? options = null; int? matchTimeout = null; foreach (AttributeData attributeData in boundAttributes) { if (!SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, regexGeneratorAttributeSymbol)) { continue; } if (attributeData.ConstructorArguments.Any(ca => ca.Kind == TypedConstantKind.Error)) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexGeneratorAttribute, methodSyntax.GetLocation())); } if (pattern is not null) { return(Diagnostic.Create(DiagnosticDescriptors.MultipleRegexGeneratorAttributes, methodSyntax.GetLocation())); } ImmutableArray <TypedConstant> items = attributeData.ConstructorArguments; if (items.Length == 0 || items.Length > 3) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexGeneratorAttribute, methodSyntax.GetLocation())); } attributeFound = true; pattern = items[0].Value as string; if (items.Length >= 2) { options = items[1].Value as int?; if (items.Length == 3) { matchTimeout = items[2].Value as int?; } } } if (!attributeFound) { return(null); } if (pattern is null) { return(Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "(null)")); } if (!regexMethodSymbol.IsPartialDefinition || regexMethodSymbol.IsAbstract || regexMethodSymbol.Parameters.Length != 0 || regexMethodSymbol.Arity != 0 || !SymbolEqualityComparer.Default.Equals(regexMethodSymbol.ReturnType, regexSymbol)) { return(Diagnostic.Create(DiagnosticDescriptors.RegexMethodMustHaveValidSignature, methodSyntax.GetLocation())); } if (typeDec.SyntaxTree.Options is CSharpParseOptions { LanguageVersion : <= LanguageVersion.CSharp10 })