// 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);
Beispiel #2
0
        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
            })