private void AnalyzeNode(SyntaxNodeAnalysisContext context) { var cancellationToken = context.CancellationToken; var node = context.Node; var semanticModel = context.SemanticModel; if (node.Parent is not TMemberAccessExpressionSyntax memberAccessExpression) { return; } var simplifierOptions = context.GetAnalyzerOptions().GetSimplifierOptions(Simplification); if (!this.Simplifier.ShouldSimplifyThisMemberAccessExpression( memberAccessExpression, semanticModel, simplifierOptions, out var thisExpression, out var severity, cancellationToken)) { return; } var builder = ImmutableDictionary.CreateBuilder <string, string?>(); // used so we can provide a link in the preview to the options page. This value is // hard-coded there to be the one that will go to the code-style page. builder["OptionName"] = nameof(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration); builder["OptionLanguage"] = semanticModel.Language; context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, thisExpression.GetLocation(), severity, ImmutableArray.Create(memberAccessExpression.GetLocation()), builder.ToImmutable())); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var option = context.GetAnalyzerOptions().PreferIsNullCheckOverReferenceEqualityMethod; if (!option.Value) { return; } var binaryExpression = (BinaryExpressionSyntax)context.Node; var semanticModel = context.SemanticModel; if (!IsObjectCastAndNullCheck(semanticModel, binaryExpression.Left, binaryExpression.Right) && !IsObjectCastAndNullCheck(semanticModel, binaryExpression.Right, binaryExpression.Left)) { return; } var severity = option.Notification.Severity; var properties = binaryExpression.Kind() == SyntaxKind.EqualsExpression ? s_properties : s_NegatedProperties; context.ReportDiagnostic( DiagnosticHelper.Create( Descriptor, binaryExpression.GetLocation(), severity, additionalLocations: null, properties)); }
private void ReportDiagnosticsIfNeeded(NameEqualsSyntax nameEquals, SyntaxNodeAnalysisContext context) { if (!nameEquals.Parent.IsKind(SyntaxKind.AnonymousObjectMemberDeclarator, out AnonymousObjectMemberDeclaratorSyntax? anonCtor)) { return; } var preference = context.GetAnalyzerOptions().PreferInferredAnonymousTypeMemberNames; if (!preference.Value || !CSharpInferredMemberNameSimplifier.CanSimplifyAnonymousTypeMemberName(anonCtor)) { return; } // Create a normal diagnostic var fadeSpan = TextSpan.FromBounds(nameEquals.Name.SpanStart, nameEquals.EqualsToken.Span.End); context.ReportDiagnostic( DiagnosticHelper.CreateWithLocationTags( Descriptor, nameEquals.GetLocation(), preference.Notification.Severity, additionalLocations: ImmutableArray <Location> .Empty, additionalUnnecessaryLocations: ImmutableArray.Create(context.Node.SyntaxTree.GetLocation(fadeSpan)))); }
private void ReportDiagnosticsIfNeeded(NameColonSyntax nameColon, SyntaxNodeAnalysisContext context) { if (!nameColon.Parent.IsKind(SyntaxKind.Argument, out ArgumentSyntax? argument)) { return; } var syntaxTree = context.Node.SyntaxTree; var parseOptions = (CSharpParseOptions)syntaxTree.Options; var preference = context.GetAnalyzerOptions().PreferInferredTupleNames; if (!preference.Value || !CSharpInferredMemberNameSimplifier.CanSimplifyTupleElementName(argument, parseOptions)) { return; } // Create a normal diagnostic var fadeSpan = TextSpan.FromBounds(nameColon.Name.SpanStart, nameColon.ColonToken.Span.End); context.ReportDiagnostic( DiagnosticHelper.CreateWithLocationTags( Descriptor, nameColon.GetLocation(), preference.Notification.Severity, additionalLocations: ImmutableArray <Location> .Empty, additionalUnnecessaryLocations: ImmutableArray.Create(syntaxTree.GetLocation(fadeSpan)))); }
private void AnalyzeCoalesceExpression(SyntaxNodeAnalysisContext context) { var cancellationToken = context.CancellationToken; var semanticModel = context.SemanticModel; var coalesceExpression = (BinaryExpressionSyntax)context.Node; var option = context.GetAnalyzerOptions().PreferCompoundAssignment; // Bail immediately if the user has disabled this feature. if (!option.Value) { return; } var coalesceLeft = coalesceExpression.Left; var coalesceRight = coalesceExpression.Right; if (coalesceRight is not ParenthesizedExpressionSyntax parenthesizedExpr) { return; } if (parenthesizedExpr.Expression is not AssignmentExpressionSyntax assignment) { return; } if (assignment.Kind() != SyntaxKind.SimpleAssignmentExpression) { return; } // have x ?? (y = z) // ensure that 'x' and 'y' are suitably equivalent. var syntaxFacts = CSharpSyntaxFacts.Instance; if (!syntaxFacts.AreEquivalent(coalesceLeft, assignment.Left)) { return; } // Syntactically looks promising. But we can only safely do this if 'expr' // is side-effect-free since we will be changing the number of times it is // executed from twice to once. if (!UseCompoundAssignmentUtilities.IsSideEffectFree( syntaxFacts, coalesceLeft, semanticModel, cancellationToken)) { return; } // Good match. context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, coalesceExpression.OperatorToken.GetLocation(), option.Notification.Severity, additionalLocations: ImmutableArray.Create(coalesceExpression.GetLocation()), properties: null)); }
private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ienumerableType) { var semanticModel = context.SemanticModel; var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node; var language = objectCreationExpression.Language; var cancellationToken = context.CancellationToken; var option = context.GetAnalyzerOptions().PreferCollectionInitializer; if (!option.Value) { // not point in analyzing if the option is off. return; } // Object creation can only be converted to collection initializer if it // implements the IEnumerable type. var objectType = context.SemanticModel.GetTypeInfo(objectCreationExpression, cancellationToken); if (objectType.Type == null || !objectType.Type.AllInterfaces.Contains(ienumerableType)) { return; } var matches = UseCollectionInitializerAnalyzer <TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TInvocationExpressionSyntax, TExpressionStatementSyntax, TVariableDeclaratorSyntax> .Analyze( semanticModel, GetSyntaxFacts(), objectCreationExpression, cancellationToken); if (matches == null || matches.Value.Length == 0) { return; } var containingStatement = objectCreationExpression.FirstAncestorOrSelf <TStatementSyntax>(); if (containingStatement == null) { return; } var nodes = ImmutableArray.Create <SyntaxNode>(containingStatement).AddRange(matches.Value); var syntaxFacts = GetSyntaxFacts(); if (syntaxFacts.ContainsInterleavedDirective(nodes, cancellationToken)) { return; } var locations = ImmutableArray.Create(objectCreationExpression.GetLocation()); context.ReportDiagnostic(DiagnosticHelper.Create( s_descriptor, objectCreationExpression.GetFirstToken().GetLocation(), option.Notification.Severity, additionalLocations: locations, properties: null)); FadeOutCode(context, matches.Value, locations); }
protected void AnalyzeSyntax( SyntaxNodeAnalysisContext context, INamedTypeSymbol ienumerableType, INamedTypeSymbol ienumerableOfTType) { var semanticModel = context.SemanticModel; var cancellationToken = context.CancellationToken; if (context.Node is not TForEachStatementSyntax node) { return; } var option = context.GetAnalyzerOptions().ForEachExplicitCastInSource; Contract.ThrowIfFalse(option.Value is ForEachExplicitCastInSourcePreference.Always or ForEachExplicitCastInSourcePreference.WhenStronglyTyped); if (semanticModel.GetOperation(node, cancellationToken) is not IForEachLoopOperation loopOperation) { return; } if (loopOperation.LoopControlVariable is not IVariableDeclaratorOperation variableDeclarator || variableDeclarator.Symbol.Type is not ITypeSymbol iterationType) { return; } var syntaxFacts = this.SyntaxFacts; var collectionType = semanticModel.GetTypeInfo(syntaxFacts.GetExpressionOfForeachStatement(node), cancellationToken).Type; if (collectionType is null) { return; } var(conversion, collectionElementType) = GetForEachInfo(semanticModel, node); // Don't bother checking conversions that are problematic for other reasons. The user will already have a // compiler error telling them the foreach is in error. if (!conversion.Exists) { return; } // If the conversion was implicit, then everything is ok. Implicit conversions are safe and do not throw at runtime. if (conversion.IsImplicit) { return; } // An implicit legal conversion still shows up as explicit conversion in the object model. But this is fine // to keep as is since being an implicit-conversion means the API indicates it should always be safe to // happen at runtime. if (conversion.IsUserDefined && conversion.MethodSymbol is { Name : WellKnownMemberNames.ImplicitConversionName })
private void AnalyzeNode(SyntaxNodeAnalysisContext context) { var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node; var language = objectCreationExpression.Language; var option = context.GetAnalyzerOptions().PreferObjectInitializer; if (!option.Value) { // not point in analyzing if the option is off. return; } var syntaxFacts = GetSyntaxFacts(); var matches = UseNamedMemberInitializerAnalyzer <TExpressionSyntax, TStatementSyntax, TObjectCreationExpressionSyntax, TMemberAccessExpressionSyntax, TAssignmentStatementSyntax, TVariableDeclaratorSyntax> .Analyze( context.SemanticModel, syntaxFacts, objectCreationExpression, context.CancellationToken); if (matches == null || matches.Value.Length == 0) { return; } var containingStatement = objectCreationExpression.FirstAncestorOrSelf <TStatementSyntax>(); if (containingStatement == null) { return; } if (!IsValidContainingStatement(containingStatement)) { return; } var nodes = ImmutableArray.Create <SyntaxNode>(containingStatement).AddRange(matches.Value.Select(m => m.Statement)); if (syntaxFacts.ContainsInterleavedDirective(nodes, context.CancellationToken)) { return; } var locations = ImmutableArray.Create(objectCreationExpression.GetLocation()); context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, objectCreationExpression.GetFirstToken().GetLocation(), option.Notification.Severity, locations, properties: null)); FadeOutCode(context, matches.Value, locations); }
protected void AnalyzeNode(SyntaxNodeAnalysisContext context) { var options = context.GetAnalyzerOptions(); // if the user never prefers this style, do not analyze at all. // we don't know the context of the node yet, so check all predefined type option preferences and bail early. if (!IsFrameworkTypePreferred(options.PreferPredefinedTypeKeywordInDeclaration) && !IsFrameworkTypePreferred(options.PreferPredefinedTypeKeywordInMemberAccess)) { return; } var predefinedTypeNode = (TPredefinedTypeSyntax)context.Node; // check if the predefined type is replaceable with an equivalent framework type. if (!IsPredefinedTypeReplaceableWithFrameworkType(predefinedTypeNode)) { return; } // check we have a symbol so that the fixer can generate the right type syntax from it. if (context.SemanticModel.GetSymbolInfo(predefinedTypeNode, context.CancellationToken).Symbol is not ITypeSymbol) { return; } // earlier we did a context insensitive check to see if this style was preferred in *any* context at all. // now, we have to make a context sensitive check to see if options settings for our context requires us to report a diagnostic. var optionValue = IsInMemberAccessOrCrefReferenceContext(predefinedTypeNode) ? options.PreferPredefinedTypeKeywordInMemberAccess : options.PreferPredefinedTypeKeywordInDeclaration; if (IsFrameworkTypePreferred(optionValue)) { context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, predefinedTypeNode.GetLocation(), optionValue.Notification.Severity, additionalLocations: null, PreferFrameworkTypeConstants.Properties)); }
private void AnalyzeSyntax( SyntaxNodeAnalysisContext context, INamedTypeSymbol?expressionTypeOpt, IMethodSymbol?referenceEqualsMethodOpt) { var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var option = context.GetAnalyzerOptions().PreferNullPropagation; if (!option.Value) { return; } var syntaxFacts = GetSyntaxFacts(); syntaxFacts.GetPartsOfConditionalExpression( conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode); conditionNode = syntaxFacts.WalkDownParentheses(conditionNode); whenTrueNode = syntaxFacts.WalkDownParentheses(whenTrueNode); whenFalseNode = syntaxFacts.WalkDownParentheses(whenFalseNode); var conditionIsNegated = false; if (syntaxFacts.IsLogicalNotExpression(conditionNode)) { conditionIsNegated = true; conditionNode = syntaxFacts.WalkDownParentheses( syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode)); } if (!TryAnalyzeCondition( context, syntaxFacts, referenceEqualsMethodOpt, conditionNode, out var conditionPartToCheck, out var isEquals)) { return; } if (conditionIsNegated) { isEquals = !isEquals; } // Needs to be of the form: // x == null ? null : ... or // x != null ? ... : null; if (isEquals && !syntaxFacts.IsNullLiteralExpression(whenTrueNode)) { return; } if (!isEquals && !syntaxFacts.IsNullLiteralExpression(whenFalseNode)) { return; } var whenPartToCheck = isEquals ? whenFalseNode : whenTrueNode; var semanticModel = context.SemanticModel; var whenPartMatch = GetWhenPartMatch(syntaxFacts, semanticModel, conditionPartToCheck, whenPartToCheck); if (whenPartMatch == null) { return; } // ?. is not available in expression-trees. Disallow the fix in that case. var type = semanticModel.GetTypeInfo(conditionalExpression).Type; if (type?.IsValueType == true) { if (type is not INamedTypeSymbol namedType || namedType.ConstructedFrom.SpecialType != SpecialType.System_Nullable_T) { // User has something like: If(str is nothing, nothing, str.Length) // In this case, converting to str?.Length changes the type of this from // int to int? return; } // But for a nullable type, such as If(c is nothing, nothing, c.nullable) // converting to c?.nullable doesn't affect the type } if (IsInExpressionTree(semanticModel, conditionNode, expressionTypeOpt, context.CancellationToken)) { return; } var locations = ImmutableArray.Create( conditionalExpression.GetLocation(), conditionPartToCheck.GetLocation(), whenPartToCheck.GetLocation()); var properties = ImmutableDictionary <string, string?> .Empty; var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; if (whenPartIsNullable) { properties = properties.Add(UseNullPropagationConstants.WhenPartIsNullable, ""); } context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, conditionalExpression.GetLocation(), option.Notification.Severity, locations, properties)); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var cancellationToken = context.CancellationToken; var option = context.GetAnalyzerOptions().PreferCoalesceExpression; if (!option.Value) { return; } var syntaxFacts = GetSyntaxFacts(); syntaxFacts.GetPartsOfConditionalExpression( conditionalExpression, out var conditionNode, out var whenTrueNodeHigh, out var whenFalseNodeHigh); conditionNode = syntaxFacts.WalkDownParentheses(conditionNode); var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh); var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh); var notHasValueExpression = false; if (syntaxFacts.IsLogicalNotExpression(conditionNode)) { notHasValueExpression = true; conditionNode = syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode); } if (conditionNode is not TMemberAccessExpression conditionMemberAccess) { return; } syntaxFacts.GetPartsOfMemberAccessExpression(conditionMemberAccess, out var conditionExpression, out var conditionSimpleName); syntaxFacts.GetNameAndArityOfSimpleName(conditionSimpleName, out var conditionName, out _); if (conditionName != nameof(Nullable <int> .HasValue)) { return; } var whenPartToCheck = notHasValueExpression ? whenFalseNodeLow : whenTrueNodeLow; if (whenPartToCheck is not TMemberAccessExpression whenPartMemberAccess) { return; } syntaxFacts.GetPartsOfMemberAccessExpression(whenPartMemberAccess, out var whenPartExpression, out var whenPartSimpleName); syntaxFacts.GetNameAndArityOfSimpleName(whenPartSimpleName, out var whenPartName, out _); if (whenPartName != nameof(Nullable <int> .Value)) { return; } if (!syntaxFacts.AreEquivalent(conditionExpression, whenPartExpression)) { return; } // Syntactically this looks like something we can simplify. Make sure we're // actually looking at something Nullable (and not some type that uses a similar // syntactic pattern). var semanticModel = context.SemanticModel; var nullableType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Nullable <>).FullName !); if (nullableType == null) { return; } var type = semanticModel.GetTypeInfo(conditionExpression, cancellationToken); if (!nullableType.Equals(type.Type?.OriginalDefinition)) { return; } var whenPartToKeep = notHasValueExpression ? whenTrueNodeHigh : whenFalseNodeHigh; var locations = ImmutableArray.Create( conditionalExpression.GetLocation(), conditionExpression.GetLocation(), whenPartToKeep.GetLocation()); context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, conditionalExpression.GetLocation(), option.Notification.Severity, locations, properties: null)); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol referenceEqualsMethod, bool unconstraintedGenericSupported) { var cancellationToken = context.CancellationToken; var semanticModel = context.SemanticModel; var option = context.GetAnalyzerOptions().PreferIsNullCheckOverReferenceEqualityMethod; if (!option.Value) { return; } var invocation = context.Node; var syntaxFacts = GetSyntaxFacts(); var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocation); var nameNode = syntaxFacts.IsIdentifierName(expression) ? expression : syntaxFacts.IsSimpleMemberAccessExpression(expression) ? syntaxFacts.GetNameOfMemberAccessExpression(expression) : null; if (!syntaxFacts.IsIdentifierName(nameNode)) { return; } syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out _); if (!syntaxFacts.StringComparer.Equals(name, nameof(ReferenceEquals))) { return; } var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); if (arguments.Count != 2) { return; } if (!MatchesPattern(syntaxFacts, arguments[0], arguments[1]) && !MatchesPattern(syntaxFacts, arguments[1], arguments[0])) { return; } var symbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol; if (!referenceEqualsMethod.Equals(symbol)) { return; } var properties = ImmutableDictionary <string, string?> .Empty.Add( UseIsNullConstants.Kind, UseIsNullConstants.ReferenceEqualsKey); var genericParameterSymbol = GetGenericParameterSymbol(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken); if (genericParameterSymbol != null) { if (genericParameterSymbol.IsValueType) { // 'is null' would generate error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead. // '== null' would generate error CS0019: Operator '==' cannot be applied to operands of type 'T' and '<null>' // 'Is Nothing' would generate error BC30020: 'Is' operator does not accept operands of type 'T'. Operands must be reference or nullable types. return; } // HasReferenceTypeConstraint returns false for base type constraint. // IsReferenceType returns true. if (!genericParameterSymbol.IsReferenceType && !unconstraintedGenericSupported) { // Needs special casing for C# as long as // 'is null' over unconstrained generic is implemented in C# 8. properties = properties.Add(UseIsNullConstants.UnconstrainedGeneric, ""); } } var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); var negated = syntaxFacts.IsLogicalNotExpression(invocation.Parent); if (negated) { properties = properties.Add(UseIsNullConstants.Negated, ""); } var severity = option.Notification.Severity; context.ReportDiagnostic( DiagnosticHelper.Create( Descriptor, nameNode.GetLocation(), severity, additionalLocations, properties)); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var cancellationToken = context.CancellationToken; var parenthesizedExpression = (TParenthesizedExpressionSyntax)context.Node; if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel, cancellationToken, out var precedence, out var clarifiesPrecedence)) { return; } // Do not remove parentheses from these expressions when there are different kinds // between the parent and child of the parenthesized expr.. This is because removing // these parens can significantly decrease readability and can confuse many people // (including several people quizzed on Roslyn). For example, most people see // "1 + 2 << 3" as "1 + (2 << 3)", when it's actually "(1 + 2) << 3". To avoid // making code bases more confusing, we just do not touch parens for these constructs // unless both the child and parent have the same kinds. switch (precedence) { case PrecedenceKind.Shift: case PrecedenceKind.Bitwise: case PrecedenceKind.Coalesce: var syntaxFacts = GetSyntaxFacts(); var child = syntaxFacts.GetExpressionOfParenthesizedExpression(parenthesizedExpression); var parentKind = parenthesizedExpression.Parent?.RawKind; var childKind = child.RawKind; if (parentKind != childKind) { return; } // Ok to remove if it was the exact same kind. i.e. ```(a | b) | c``` // not ok to remove if kinds changed. i.e. ```(a + b) << c``` break; } var options = context.GetAnalyzerOptions(); var preference = GetLanguageOption(options, precedence); if (preference.Notification.Severity == ReportDiagnostic.Suppress) { // User doesn't care about these parens. So nothing for us to do. return; } if (preference.Value == ParenthesesPreference.AlwaysForClarity && clarifiesPrecedence) { // User wants these parens if they clarify precedence, and these parens // clarify precedence. So keep these around. return; } // either they don't want unnecessary parentheses, or they want them only for // clarification purposes and this does not make things clear. Debug.Assert(preference.Value == ParenthesesPreference.NeverIfUnnecessary || !clarifiesPrecedence); var severity = preference.Notification.Severity; var additionalLocations = ImmutableArray.Create( parenthesizedExpression.GetLocation()); var additionalUnnecessaryLocations = ImmutableArray.Create( parenthesizedExpression.GetFirstToken().GetLocation(), parenthesizedExpression.GetLastToken().GetLocation()); context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags( Descriptor, AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer <TLanguageKindEnum, TParenthesizedExpressionSyntax> .GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken), severity, additionalLocations, additionalUnnecessaryLocations)); }
private void AnalyzeIfStatement( SyntaxNodeAnalysisContext context, IMethodSymbol?referenceEqualsMethod) { var cancellationToken = context.CancellationToken; var option = context.GetAnalyzerOptions().PreferNullPropagation; if (!option.Value) { return; } var syntaxFacts = GetSyntaxFacts(); var ifStatement = (TIfStatementSyntax)context.Node; // The true-statement if the if-statement has to be a statement of the form `<expr1>.Name(...)`; if (!TryGetPartsOfIfStatement(ifStatement, out var condition, out var trueStatement)) { return; } if (trueStatement is not TExpressionStatementSyntax expressionStatement) { return; } // Now see if the `if (<condition>)` looks like an appropriate null check. if (!TryAnalyzeCondition(context, syntaxFacts, referenceEqualsMethod, condition, out var conditionPartToCheck, out var isEquals)) { return; } // Ok, we have `if (<expr2> == null)` or `if (<expr2> != null)` (or some similar form of that. `conditionPartToCheck` will be `<expr2>` here. // We only support `if (<expr2> != null)`. Fail out if we have the alternate form. if (isEquals) { return; } var semanticModel = context.SemanticModel; var whenPartMatch = GetWhenPartMatch( syntaxFacts, semanticModel, conditionPartToCheck, (TExpressionSyntax)syntaxFacts.GetExpressionOfExpressionStatement(expressionStatement), cancellationToken); if (whenPartMatch == null) { return; } // can't use ?. on a pointer var whenPartType = semanticModel.GetTypeInfo(whenPartMatch, cancellationToken).Type; if (whenPartType is IPointerTypeSymbol) { return; } var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; var properties = whenPartIsNullable ? s_whenPartIsNullableProperties : ImmutableDictionary <string, string?> .Empty; context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, ifStatement.GetFirstToken().GetLocation(), option.Notification.Severity, ImmutableArray.Create( ifStatement.GetLocation(), trueStatement.GetLocation(), whenPartMatch.GetLocation()), properties)); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var cancellationToken = context.CancellationToken; var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var option = context.GetAnalyzerOptions().PreferCoalesceExpression; if (!option.Value) { return; } var syntaxFacts = GetSyntaxFacts(); syntaxFacts.GetPartsOfConditionalExpression( conditionalExpression, out var conditionNode, out var whenTrueNodeHigh, out var whenFalseNodeHigh); conditionNode = syntaxFacts.WalkDownParentheses(conditionNode); var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh); var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh); if (conditionNode is not TBinaryExpressionSyntax condition) { return; } var syntaxKinds = syntaxFacts.SyntaxKinds; var isEquals = syntaxKinds.ReferenceEqualsExpression == condition.RawKind; var isNotEquals = syntaxKinds.ReferenceNotEqualsExpression == condition.RawKind; if (!isEquals && !isNotEquals) { return; } syntaxFacts.GetPartsOfBinaryExpression(condition, out var conditionLeftHigh, out var conditionRightHigh); var conditionLeftLow = syntaxFacts.WalkDownParentheses(conditionLeftHigh); var conditionRightLow = syntaxFacts.WalkDownParentheses(conditionRightHigh); var conditionLeftIsNull = syntaxFacts.IsNullLiteralExpression(conditionLeftLow); var conditionRightIsNull = syntaxFacts.IsNullLiteralExpression(conditionRightLow); if (conditionRightIsNull && conditionLeftIsNull) { // null == null nothing to do here. return; } if (!conditionRightIsNull && !conditionLeftIsNull) { return; } if (!syntaxFacts.AreEquivalent( conditionRightIsNull ? conditionLeftLow : conditionRightLow, isEquals ? whenFalseNodeLow : whenTrueNodeLow)) { return; } // Coalesce expression cannot be target typed. So if we had a ternary that was target typed // that means the individual parts themselves had no best common type, which would not work // for a coalesce expression. var semanticModel = context.SemanticModel; if (IsTargetTyped(semanticModel, conditionalExpression, cancellationToken)) { return; } var conditionType = semanticModel.GetTypeInfo( conditionLeftIsNull ? conditionRightLow : conditionLeftLow, cancellationToken).Type; if (conditionType != null && !conditionType.IsReferenceType) { // Note: it is intentional that we do not support nullable types here. If you have: // // int? x; // var z = x == null ? y : x; // // then that's not the same as: x ?? y. ?? will unwrap the nullable, producing a // int and not an int? like we have in the above code. // // Note: we could look for: x == null ? y : x.Value, and simplify that in the future. return; } var conditionPartToCheck = conditionRightIsNull ? conditionLeftHigh : conditionRightHigh; var whenPartToCheck = isEquals ? whenTrueNodeHigh : whenFalseNodeHigh; var locations = ImmutableArray.Create( conditionalExpression.GetLocation(), conditionPartToCheck.GetLocation(), whenPartToCheck.GetLocation()); context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, conditionalExpression.GetLocation(), option.Notification.Severity, locations, properties: null)); }
private void AnalyzeConditionalExpression(SyntaxNodeAnalysisContext context) { var styleOption = context.GetAnalyzerOptions().PreferSimplifiedBooleanExpressions; if (!styleOption.Value) { // Bail immediately if the user has disabled this feature. return; } var semanticModel = context.SemanticModel; var cancellationToken = context.CancellationToken; var conditionalExpression = (TConditionalExpressionSyntax)context.Node; SyntaxFacts.GetPartsOfConditionalExpression( conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode); var condition = (TExpressionSyntax)conditionNode; var whenTrue = (TExpressionSyntax)whenTrueNode; var whenFalse = (TExpressionSyntax)whenFalseNode; // Only offer when everything is a basic boolean type. That way we don't have to worry // about any sort of subtle cases with implicit or bool conversions. if (!IsSimpleBooleanType(condition) || !IsSimpleBooleanType(whenTrue) || !IsSimpleBooleanType(whenFalse)) { return; } var whenTrue_isTrue = IsTrue(whenTrue); var whenTrue_isFalse = IsFalse(whenTrue); var whenFalse_isTrue = IsTrue(whenFalse); var whenFalse_isFalse = IsFalse(whenFalse); if (whenTrue_isTrue && whenFalse_isFalse) { // c ? true : false => c ReportDiagnostic(s_takeCondition); } else if (whenTrue_isFalse && whenFalse_isTrue) { // c ? false : true => !c ReportDiagnostic(s_negateCondition); } else if (whenTrue_isFalse && whenFalse_isFalse) { // c ? false : false => c && false // Note: this is a slight optimization over the when `c ? false : wf` // case below. It allows to generate `c && false` instead of `!c && false` ReportDiagnostic(s_takeConditionAndWhenFalse); } else if (whenTrue_isTrue) { // c ? true : wf => c || wf ReportDiagnostic(s_takeConditionOrWhenFalse); } else if (whenTrue_isFalse) { // c ? false : wf => !c && wf ReportDiagnostic(s_negateConditionAndWhenFalse); } else if (whenFalse_isTrue) { // c ? wt : true => !c or wt ReportDiagnostic(s_negateConditionOrWhenTrue); } else if (whenFalse_isFalse) { // c ? wt : false => c && wt ReportDiagnostic(s_takeConditionAndWhenTrue); } return; // local functions void ReportDiagnostic(ImmutableDictionary <string, string?> properties) => context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, conditionalExpression.GetLocation(), styleOption.Notification.Severity, additionalLocations: null, properties)); bool IsSimpleBooleanType(TExpressionSyntax node) { var typeInfo = semanticModel.GetTypeInfo(node, cancellationToken); var conversion = GetConversion(semanticModel, node, cancellationToken); return (conversion.MethodSymbol == null && typeInfo.Type?.SpecialType == SpecialType.System_Boolean && typeInfo.ConvertedType?.SpecialType == SpecialType.System_Boolean); } bool IsTrue(TExpressionSyntax node) => IsBoolValue(node, true); bool IsFalse(TExpressionSyntax node) => IsBoolValue(node, false); bool IsBoolValue(TExpressionSyntax node, bool value) { var constantValue = semanticModel.GetConstantValue(node, cancellationToken); return(constantValue.HasValue && constantValue.Value is bool b && b == value); } }