예제 #1
0
        private static void OnCompilationStart(CompilationStartAnalysisContext context)
        {
            // To avoid false positives, as with CA1812 (avoid uninstantiated internal classes), skip any assemblies with InternalsVisibleTo.
            var internalsVisibleToAttributeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesInternalsVisibleToAttribute);

            if (context.Compilation.Assembly.HasAttribute(internalsVisibleToAttributeSymbol))
            {
                return;
            }

            INamedTypeSymbol?comImportAttributeType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeInteropServicesComImportAttribute);

            var candidateTypes = PooledConcurrentSet <INamedTypeSymbol> .GetInstance(SymbolEqualityComparer.Default);

            var baseTypes = PooledConcurrentSet <INamedTypeSymbol> .GetInstance(SymbolEqualityComparer.Default);

            context.RegisterSymbolAction(context =>
            {
                var type = (INamedTypeSymbol)context.Symbol;

                if (type.TypeKind is TypeKind.Class &&
                    !type.IsAbstract &&
                    !type.IsStatic &&
                    !type.IsSealed &&
                    !type.IsExternallyVisible() &&
                    !type.HasAttribute(comImportAttributeType))
                {
                    candidateTypes.Add(type);
                }

                for (INamedTypeSymbol?baseType = type.BaseType; baseType is not null; baseType = baseType.BaseType)
                {
                    baseTypes.Add(baseType.OriginalDefinition);
                }
            }, SymbolKind.NamedType);

            context.RegisterCompilationEndAction(context =>
            {
                foreach (INamedTypeSymbol type in candidateTypes)
                {
                    if (!baseTypes.Contains(type.OriginalDefinition))
                    {
                        context.ReportDiagnostic(type.CreateDiagnostic(Rule, type.Name));
                    }
                }

                candidateTypes.Dispose();
                baseTypes.Dispose();
            });
        }
        private static void OnCompilationStart(CompilationStartAnalysisContext context)
        {
            if (!RequiredSymbols.TryGetSymbols(context.Compilation, out RequiredSymbols symbols))
            {
                return;
            }

            context.RegisterOperationBlockStartAction(OnOperationBlockStart);
            return;

            //  Local functions

            void OnOperationBlockStart(OperationBlockStartAnalysisContext context)
            {
                var invocations = PooledConcurrentSet <IInvocationOperation> .GetInstance();

                context.RegisterOperationAction(context =>
                {
                    var argument = (IArgumentOperation)context.Operation;
                    if (symbols.IsAnySubstringInvocation(argument.Value.WalkDownConversion(c => c.IsImplicit)) && argument.Parent is IInvocationOperation invocation)
                    {
                        invocations.Add(invocation);
                    }
                }, OperationKind.Argument);

                context.RegisterOperationBlockEndAction(context =>
                {
                    foreach (var invocation in invocations)
                    {
                        //  We search for an overload of the invoked member whose signature matches the signature of
                        //  the invoked member, except with ReadOnlySpan<char> substituted in for some of the
                        //  arguments that are Substring invocations.
                        if (!GetBestSpanBasedOverloads(symbols, invocation, context.CancellationToken).IsEmpty)
                        {
                            Diagnostic diagnostic = invocation.CreateDiagnostic(Rule);
                            context.ReportDiagnostic(diagnostic);
                        }
                    }

                    invocations.Free(context.CancellationToken);
                });
            }
        }
예제 #3
0
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterCompilationStartAction(compilationContext =>
            {
                INamedTypeSymbol?diagnosticDescriptorType = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticDescriptor);
                if (diagnosticDescriptorType == null)
                {
                    return;
                }

                // Try read the additional file containing the allowed categories, and corresponding ID ranges.
                var checkCategoryAndAllowedIds = TryGetCategoryAndAllowedIdsMap(
                    compilationContext.Options.AdditionalFiles,
                    compilationContext.CancellationToken,
                    out AdditionalText? diagnosticCategoryAndIdRangeTextOpt,
                    out ImmutableDictionary <string, ImmutableArray <(string?prefix, int start, int end)> >?categoryAndAllowedIdsMap,
                    out List <Diagnostic>?invalidFileDiagnostics);

                // Try read the additional files containing the shipped and unshipped analyzer releases.
                var isAnalyzerReleaseTracking = TryGetReleaseTrackingData(
                    compilationContext.Options.AdditionalFiles,
                    compilationContext.CancellationToken,
                    out var shippedData,
                    out var unshippedData,
                    out List <Diagnostic>?invalidReleaseFileEntryDiagnostics);

                var idToAnalyzerMap = new ConcurrentDictionary <string, ConcurrentDictionary <string, ConcurrentBag <Location> > >();
                var seenRuleIds     = PooledConcurrentSet <string> .GetInstance();
                compilationContext.RegisterOperationAction(operationAnalysisContext =>
                {
                    var fieldInitializer = (IFieldInitializerOperation)operationAnalysisContext.Operation;
                    if (!TryGetDescriptorCreateMethodAndArguments(fieldInitializer, diagnosticDescriptorType, out var creationMethod, out var creationArguments))
                    {
                        return;
                    }

                    AnalyzeTitle(creationMethod, fieldInitializer, operationAnalysisContext.ReportDiagnostic);
                    AnalyzeHelpLinkUri(operationAnalysisContext, creationArguments, out var helpLink);
                    AnalyzeCustomTags(operationAnalysisContext, creationArguments);
                    var(isEnabledByDefault, defaultSeverity) = GetDefaultSeverityAndEnabledByDefault(operationAnalysisContext.Compilation, creationArguments);

                    if (!TryAnalyzeCategory(operationAnalysisContext, creationArguments, checkCategoryAndAllowedIds,
                                            diagnosticCategoryAndIdRangeTextOpt, categoryAndAllowedIdsMap, out var category, out var allowedIdsInfoList))
                    {
                        allowedIdsInfoList = default;
                    }

                    var analyzerName = fieldInitializer.InitializedFields.First().ContainingType.Name;
                    AnalyzeRuleId(operationAnalysisContext, creationArguments,
                                  isAnalyzerReleaseTracking, shippedData, unshippedData, seenRuleIds, diagnosticCategoryAndIdRangeTextOpt,
                                  category, analyzerName, helpLink, isEnabledByDefault, defaultSeverity, allowedIdsInfoList, idToAnalyzerMap);
                }, OperationKind.FieldInitializer);

                compilationContext.RegisterCompilationEndAction(compilationEndContext =>
                {
                    // Report any invalid additional file diagnostics.
                    if (invalidFileDiagnostics != null)
                    {
                        foreach (var diagnostic in invalidFileDiagnostics)
                        {
                            compilationEndContext.ReportDiagnostic(diagnostic);
                        }
                    }

                    // Report diagnostics for duplicate diagnostic ID used across analyzers.
                    foreach (var kvp in idToAnalyzerMap)
                    {
                        var ruleId = kvp.Key;
                        var analyzerToDescriptorLocationsMap = kvp.Value;
                        if (analyzerToDescriptorLocationsMap.Count <= 1)
                        {
                            // ID used by a single analyzer.
                            continue;
                        }

                        ImmutableSortedSet <string> sortedAnalyzerNames = analyzerToDescriptorLocationsMap.Keys.ToImmutableSortedSet();
                        var skippedAnalyzerName = sortedAnalyzerNames[0];
                        foreach (var analyzerName in sortedAnalyzerNames.Skip(1))
                        {
                            var locations = analyzerToDescriptorLocationsMap[analyzerName];
                            foreach (var location in locations)
                            {
                                // Diagnostic Id '{0}' is already used by analyzer '{1}'. Please use a different diagnostic ID.
                                var diagnostic = Diagnostic.Create(UseUniqueDiagnosticIdRule, location, ruleId, skippedAnalyzerName);
                                compilationEndContext.ReportDiagnostic(diagnostic);
                            }
                        }
                    }

                    // Report analyzer release tracking invalid entry and compilation end diagnostics.
                    if (isAnalyzerReleaseTracking || invalidReleaseFileEntryDiagnostics != null)
                    {
                        RoslynDebug.Assert(shippedData != null);
                        RoslynDebug.Assert(unshippedData != null);

                        ReportAnalyzerReleaseTrackingDiagnostics(invalidReleaseFileEntryDiagnostics, shippedData, unshippedData, seenRuleIds, compilationEndContext);
                    }

                    seenRuleIds.Free();
                });
            });
        }
예제 #4
0
        public sealed override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterSymbolStartAction(visitEnumSymbol, SymbolKind.NamedType);

            void visitEnumSymbol(SymbolStartAnalysisContext context)
            {
                if (context.Symbol is not INamedTypeSymbol {
                    TypeKind : TypeKind.Enum
                } enumSymbol)
                {
                    return;
                }

                // This dictionary is populated by this thread and then read concurrently.
                // https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=net-5.0#thread-safety
                var membersByValue = PooledDictionary <object, IFieldSymbol> .GetInstance();

                var duplicates = PooledConcurrentSet <IFieldSymbol> .GetInstance(SymbolEqualityComparer.Default);

                foreach (var member in enumSymbol.GetMembers())
                {
                    if (member is not IFieldSymbol {
                        IsImplicitlyDeclared: false, HasConstantValue: true
                    } field)
                    {
                        continue;
                    }

                    var constantValue = field.ConstantValue;
                    if (membersByValue.ContainsKey(constantValue))
                    {
                        // This field is a duplicate. We need to first check
                        // if its initializer is another field on this enum,
                        // and if not give a diagnostic on it.
                        var added = duplicates.Add(field);
                        Debug.Assert(added);
                    }
                    else
                    {
                        membersByValue[constantValue] = field;
                    }
                }

                context.RegisterOperationAction(visitFieldInitializer, OperationKind.FieldInitializer);
                context.RegisterSymbolEndAction(endVisitEnumSymbol);

                void visitFieldInitializer(OperationAnalysisContext context)
                {
                    var initializer = (IFieldInitializerOperation)context.Operation;

                    if (initializer.InitializedFields.Length != 1)
                    {
                        return;
                    }

                    var field = initializer.InitializedFields[0];

                    if (duplicates.Remove(field))
                    {
                        var duplicatedField = membersByValue[field.ConstantValue];
                        if (initializer.Value is not IConversionOperation {
                            Operand : IFieldReferenceOperation {
                                Field : IFieldSymbol referencedField
                            }
                        } ||
                            !SymbolEqualityComparer.Default.Equals(referencedField, duplicatedField))
                        {
                            context.ReportDiagnostic(field.CreateDiagnostic(RuleDuplicatedValue, field.Name, field.ConstantValue, duplicatedField.Name));
                        }
                    }

                    // Check for duplicate usages of an enum field in an initializer consisting of '|' expressions
                    var referencedSymbols = PooledHashSet <IFieldSymbol> .GetInstance(SymbolEqualityComparer.Default);

                    var containingType = field.ContainingType;

                    visitInitializerValue(initializer.Value);
                    referencedSymbols.Free(context.CancellationToken);

                    void visitInitializerValue(IOperation operation)
                    {
                        switch (operation)
                        {
                        case IBinaryOperation {
                                OperatorKind: not BinaryOperatorKind.Or
                        } :
                            // only descend into '|' binary operators, not into '&', '+', ...
                            break;
예제 #5
0
            static void OnSymbolStart(
                SymbolStartAnalysisContext symbolStartContext,
                WellKnownTypeProvider wellKnownTypeProvider,
                ImmutableArray <INamedTypeSymbol> skippedAttributes,
                bool isWebProject)
            {
                // Since property/event accessors cannot be marked static themselves and the associated symbol (property/event)
                // has to be marked static, we want to report the diagnostic on the property/event.
                // So we make a note of the property/event symbols which have at least one accessor with no instance access.
                // At symbol end, we report candidate property/event symbols whose all accessors are candidates to be marked static.
                var propertyOrEventCandidates = PooledConcurrentSet <ISymbol> .GetInstance();

                var accessorCandidates = PooledConcurrentSet <IMethodSymbol> .GetInstance();

                var methodCandidates = PooledConcurrentSet <IMethodSymbol> .GetInstance();

                // Do not flag methods that are used as delegates: https://github.com/dotnet/roslyn-analyzers/issues/1511
                var methodsUsedAsDelegates = PooledConcurrentSet <IMethodSymbol> .GetInstance();

                symbolStartContext.RegisterOperationAction(OnMethodReference, OperationKind.MethodReference);
                symbolStartContext.RegisterOperationBlockStartAction(OnOperationBlockStart);
                symbolStartContext.RegisterSymbolEndAction(OnSymbolEnd);

                return;

                void OnMethodReference(OperationAnalysisContext operationContext)
                {
                    var methodReference = (IMethodReferenceOperation)operationContext.Operation;

                    methodsUsedAsDelegates.Add(methodReference.Method);
                }

                void OnOperationBlockStart(OperationBlockStartAnalysisContext blockStartContext)
                {
#pragma warning disable IDE0083 // Use pattern matching - need new compiler
                    if (!(blockStartContext.OwningSymbol is IMethodSymbol methodSymbol))
#pragma warning restore IDE0083 // Use pattern matching
                    {
                        return;
                    }

                    // Don't run any other check for this method if it isn't a valid analysis context
                    if (!ShouldAnalyze(methodSymbol, wellKnownTypeProvider, skippedAttributes,
                                       blockStartContext.Options, isWebProject, blockStartContext.CancellationToken))
                    {
                        return;
                    }

                    // Don't report methods which have a single throw statement
                    // with NotImplementedException or NotSupportedException
                    if (blockStartContext.IsMethodNotImplementedOrSupported())
                    {
                        return;
                    }

                    bool isInstanceReferenced = false;

                    blockStartContext.RegisterOperationAction(operationContext =>
                    {
                        if (((IInstanceReferenceOperation)operationContext.Operation).ReferenceKind == InstanceReferenceKind.ContainingTypeInstance)
                        {
                            isInstanceReferenced = true;
                        }
                    }, OperationKind.InstanceReference);

                    blockStartContext.RegisterOperationBlockEndAction(blockEndContext =>
                    {
                        if (!isInstanceReferenced)
                        {
                            if (methodSymbol.IsAccessorMethod())
                            {
                                accessorCandidates.Add(methodSymbol);
                                propertyOrEventCandidates.Add(methodSymbol.AssociatedSymbol);
                            }
                            else if (methodSymbol.IsExternallyVisible())
                            {
                                if (!IsOnObsoleteMemberChain(methodSymbol, wellKnownTypeProvider))
                                {
                                    blockEndContext.ReportDiagnostic(methodSymbol.CreateDiagnostic(Rule, methodSymbol.Name));
                                }
                            }
                            else
                            {
                                methodCandidates.Add(methodSymbol);
                            }
                        }
                    });
                }

                void OnSymbolEnd(SymbolAnalysisContext symbolEndContext)
                {
                    foreach (var candidate in methodCandidates)
                    {
                        if (methodsUsedAsDelegates.Contains(candidate))
                        {
                            continue;
                        }

                        if (!IsOnObsoleteMemberChain(candidate, wellKnownTypeProvider))
                        {
                            symbolEndContext.ReportDiagnostic(candidate.CreateDiagnostic(Rule, candidate.Name));
                        }
                    }

                    foreach (var candidatePropertyOrEvent in propertyOrEventCandidates)
                    {
                        var allAccessorsAreCandidates = true;
                        foreach (var accessor in candidatePropertyOrEvent.GetAccessors())
                        {
                            if (!accessorCandidates.Contains(accessor) ||
                                IsOnObsoleteMemberChain(accessor, wellKnownTypeProvider))
                            {
                                allAccessorsAreCandidates = false;
                                break;
                            }
                        }

                        if (allAccessorsAreCandidates)
                        {
                            symbolEndContext.ReportDiagnostic(candidatePropertyOrEvent.CreateDiagnostic(Rule, candidatePropertyOrEvent.Name));
                        }
                    }

                    propertyOrEventCandidates.Free(symbolEndContext.CancellationToken);
                    accessorCandidates.Free(symbolEndContext.CancellationToken);
                    methodCandidates.Free(symbolEndContext.CancellationToken);
                    methodsUsedAsDelegates.Free(symbolEndContext.CancellationToken);
                }
            }
예제 #6
0
        private void OnCompilationStart(CompilationStartAnalysisContext context)
        {
            if (!RequiredSymbols.TryGetSymbols(context.Compilation, out RequiredSymbols symbols))
            {
                return;
            }

            context.RegisterOperationBlockStartAction(OnOperationBlockStart);
            return;

            // Local functions
            void OnOperationBlockStart(OperationBlockStartAnalysisContext context)
            {
                //  Maintain set of all top-most concat operations so we don't report sub-expressions of an
                //  already-reported violation.
                //  We also don't report any diagnostic if the concat operation has too many operands for the span-based
                //  Concat overloads to handle.
                var topMostConcatOperations = PooledConcurrentSet <IBinaryOperation> .GetInstance();

                context.RegisterOperationAction(PopulateTopMostConcatOperations, OperationKind.Binary);
                context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls);

                void PopulateTopMostConcatOperations(OperationAnalysisContext context)
                {
                    //  If the current operation is a string-concatenation operation, walk up to the top-most concat
                    //  operation and add it to the set.
                    var binary = (IBinaryOperation)context.Operation;

                    if (!TryGetTopMostConcatOperation(binary, out var topMostConcatOperation))
                    {
                        return;
                    }

                    topMostConcatOperations.Add(topMostConcatOperation);
                }

                void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context)
                {
                    //  We report diagnostics for all top-most concat operations that contain
                    //  direct or conditional substring invocations when there is an applicable span-based overload of
                    //  the string.Concat method.
                    //  We don't report when the concatenation contains anything other than strings or character literals.
                    foreach (var operation in topMostConcatOperations)
                    {
                        if (ShouldBeReported(operation))
                        {
                            context.ReportDiagnostic(operation.CreateDiagnostic(Rule));
                        }
                    }

                    topMostConcatOperations.Free(context.CancellationToken);
                }
            }

            bool ShouldBeReported(IBinaryOperation topMostConcatOperation)
            {
                var concatOperands = FlattenBinaryOperation(topMostConcatOperation);

                //  Bail if no suitable overload of 'string.Concat' exists.
                if (!symbols.TryGetRoscharConcatMethodWithArity(concatOperands.Length, out _))
                {
                    return(false);
                }

                bool anySubstringInvocations = false;

                foreach (var operand in concatOperands)
                {
                    var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand);
                    switch (value.Type?.SpecialType)
                    {
                    //  Report diagnostics only when operands are exclusively strings and character literals.
                    case SpecialType.System_String:
                    case SpecialType.System_Char when value is ILiteralOperation:
                        if (IsAnyDirectOrConditionalSubstringInvocation(value))
                        {
                            anySubstringInvocations = true;
                        }
                        break;

                    default:
                        return(false);
                    }
                }

                return(anySubstringInvocations);
            }

            bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation)
            {
                if (operation is IConditionalAccessOperation conditionallAccessOperation)
                {
                    operation = conditionallAccessOperation.WhenNotNull;
                }

                return(operation is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod));
            }
        }
예제 #7
0
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(context =>
            {
                if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemString, out INamedTypeSymbol? stringType) ||
                    !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemChar, out INamedTypeSymbol? charType) ||
                    !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison, out INamedTypeSymbol? stringComparisonType))
                {
                    return;
                }

                // First get all the string.IndexOf methods that we are interested in tagging
                var stringIndexOfMethods = stringType
                                           .GetMembers("IndexOf")
                                           .OfType <IMethodSymbol>()
                                           .WhereAsArray(s =>
                                                         s.Parameters.Length <= 2);

                var stringArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(stringType));
                var charArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType));
                var stringAndComparisonTypeArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(stringType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));
                var charAndComparisonTypeArgumentIndexOfMethod = stringIndexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));

                // Check that the contains methods that take 2 parameters exist
                // string.Contains(char) is also .NETStandard2.1+
                var stringContainsMethods = stringType
                                            .GetMembers("Contains")
                                            .OfType <IMethodSymbol>()
                                            .WhereAsArray(s =>
                                                          s.Parameters.Length <= 2);
                var stringAndComparisonTypeArgumentContainsMethod = stringContainsMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(stringType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));
                var charAndComparisonTypeArgumentContainsMethod = stringContainsMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType),
                    ParameterInfo.GetParameterInfo(stringComparisonType));
                var charArgumentContainsMethod = stringContainsMethods.GetFirstOrDefaultMemberWithParameterInfos(
                    ParameterInfo.GetParameterInfo(charType));
                if (stringAndComparisonTypeArgumentContainsMethod == null ||
                    charAndComparisonTypeArgumentContainsMethod == null ||
                    charArgumentContainsMethod == null)
                {
                    return;
                }

                // Roslyn doesn't yet support "FindAllReferences" at a file/block level. So instead, find references to local int variables in this block.
                context.RegisterOperationBlockStartAction(OnOperationBlockStart);
                return;

                void OnOperationBlockStart(OperationBlockStartAnalysisContext context)
                {
                    if (context.OwningSymbol is not IMethodSymbol method)
                    {
                        return;
                    }

                    // Algorithm:
                    // We aim to change string.IndexOf -> string.Contains
                    // 1. We register 1 callback for invocations of IndexOf.
                    //      1a. Check if invocation.Parent is a binary operation we care about (string.IndexOf >= 0 OR string.IndexOf == -1). If so, report a diagnostic and return from the callback.
                    //      1b. Otherwise, check if invocation.Parent is a variable declarator. If so, add the invocation as a potential violation to track into variableNameToOperationsMap.
                    // 2. We register another callback for local references
                    //      2a. If the local reference is not a type int, bail out.
                    //      2b. If the local reference operation's parent is not a binary operation, add it to "localsToBailOut".
                    // 3. In an operation block end, we check if entries in "variableNameToOperationsMap" exist in "localToBailOut". If an entry is NOT present, we report a diagnostic at that invocation.
                    PooledConcurrentSet <ILocalSymbol> localsToBailOut = PooledConcurrentSet <ILocalSymbol> .GetInstance();
                    PooledConcurrentDictionary <ILocalSymbol, IInvocationOperation> variableNameToOperationsMap = PooledConcurrentDictionary <ILocalSymbol, IInvocationOperation> .GetInstance();

                    context.RegisterOperationAction(PopulateLocalReferencesSet, OperationKind.LocalReference);

                    context.RegisterOperationAction(AnalyzeInvocationOperation, OperationKind.Invocation);

                    context.RegisterOperationBlockEndAction(OnOperationBlockEnd);

                    return;

                    // Local Functions
                    void PopulateLocalReferencesSet(OperationAnalysisContext context)
                    {
                        ILocalReferenceOperation localReference = (ILocalReferenceOperation)context.Operation;
                        if (localReference.Local.Type.SpecialType != SpecialType.System_Int32)
                        {
                            return;
                        }

                        var parent = localReference.Parent;
                        if (parent is IBinaryOperation binaryOperation)
                        {
                            var otherOperand = binaryOperation.LeftOperand is ILocalReferenceOperation ? binaryOperation.RightOperand : binaryOperation.LeftOperand;
                            if (CheckOperatorKindAndOperand(binaryOperation, otherOperand))
                            {
                                // Do nothing. This is a valid case to the tagged in the analyzer
                                return;
                            }
                        }
                        localsToBailOut.Add(localReference.Local);
                    }

                    void AnalyzeInvocationOperation(OperationAnalysisContext context)
                    {
                        var invocationOperation = (IInvocationOperation)context.Operation;
                        if (!IsDesiredTargetMethod(invocationOperation.TargetMethod))
                        {
                            return;
                        }

                        var parent = invocationOperation.Parent;
                        if (parent is IBinaryOperation binaryOperation)
                        {
                            var otherOperand = binaryOperation.LeftOperand is IInvocationOperation ? binaryOperation.RightOperand : binaryOperation.LeftOperand;
                            if (CheckOperatorKindAndOperand(binaryOperation, otherOperand))
                            {
                                context.ReportDiagnostic(binaryOperation.CreateDiagnostic(Rule));
                            }
                        }
                        else if (parent is IVariableInitializerOperation variableInitializer)
                        {
                            if (variableInitializer.Parent is IVariableDeclaratorOperation variableDeclaratorOperation)
                            {
                                variableNameToOperationsMap.TryAdd(variableDeclaratorOperation.Symbol, invocationOperation);
                            }
                            else if (variableInitializer.Parent is IVariableDeclarationOperation variableDeclarationOperation && variableDeclarationOperation.Declarators.Length == 1)
                            {
                                variableNameToOperationsMap.TryAdd(variableDeclarationOperation.Declarators[0].Symbol, invocationOperation);
                            }
                        }
                    }

                    static bool CheckOperatorKindAndOperand(IBinaryOperation binaryOperation, IOperation otherOperand)
                    {
                        var operatorKind = binaryOperation.OperatorKind;
                        if (otherOperand.ConstantValue.HasValue && otherOperand.ConstantValue.Value is int intValue)
                        {
                            if ((operatorKind == BinaryOperatorKind.Equals && intValue < 0) ||
                                (operatorKind == BinaryOperatorKind.GreaterThanOrEqual && intValue == 0))
                            {
                                // This is the only case we are targeting in this analyzer
                                return(true);
                            }
                        }
                        return(false);
                    }

                    void OnOperationBlockEnd(OperationBlockAnalysisContext context)
                    {
                        foreach (var variableNameAndLocation in variableNameToOperationsMap)
                        {
                            ILocalSymbol variable = variableNameAndLocation.Key;
                            if (!localsToBailOut.Contains(variable))
                            {
                                context.ReportDiagnostic(variableNameAndLocation.Value.CreateDiagnostic(Rule));
                            }
                        }
                        variableNameToOperationsMap.Free(context.CancellationToken);
                        localsToBailOut.Free(context.CancellationToken);
                    }

                    bool IsDesiredTargetMethod(IMethodSymbol targetMethod) =>
                    targetMethod.Equals(stringArgumentIndexOfMethod) ||
                    targetMethod.Equals(charArgumentIndexOfMethod) ||
                    targetMethod.Equals(stringAndComparisonTypeArgumentIndexOfMethod) ||
                    targetMethod.Equals(charAndComparisonTypeArgumentIndexOfMethod);
                }
            });