private void AnalyzeCoalesceExpression(SyntaxNodeAnalysisContext context) { var cancellationToken = context.CancellationToken; var semanticModel = context.SemanticModel; var options = (CSharpParseOptions)semanticModel.SyntaxTree.Options; if (options.LanguageVersion < LanguageVersion.CSharp8) { return; } var coalesceExpression = (BinaryExpressionSyntax)context.Node; var option = context.GetOption(CodeStyleOptions2.PreferCompoundAssignment, coalesceExpression.Language); // Bail immediately if the user has disabled this feature. if (!option.Value) { return; } var coalesceLeft = coalesceExpression.Left; var coalesceRight = coalesceExpression.Right; if (!(coalesceRight is ParenthesizedExpressionSyntax parenthesizedExpr)) { return; } if (!(parenthesizedExpr.Expression is 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 AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var syntaxTree = context.SemanticModel.SyntaxTree; var cancellationToken = context.CancellationToken; var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var parenthesizedExpression = (TParenthesizedExpressionSyntax)context.Node; if (!CanRemoveParentheses(parenthesizedExpression, context.SemanticModel, 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 = this.GetSyntaxFactsService(); 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 option = GetLanguageOption(precedence); var preference = optionSet.GetOption(option, parenthesizedExpression.Language); 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()); // Fades the open parentheses character and reports the suggestion. context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetFirstToken().GetLocation(), additionalLocations)); // Generates diagnostic used to squiggle the parenthetical expression. context.ReportDiagnostic(DiagnosticHelper.Create( s_diagnosticWithoutFade, GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken), severity, additionalLocations, properties: null)); // Fades the close parentheses character. context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetLastToken().GetLocation(), additionalLocations)); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var syntaxTree = context.Node.SyntaxTree; var cancellationToken = context.CancellationToken; var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var option = optionSet.GetOption(CodeStyleOptions.PreferCoalesceExpression, conditionalExpression.Language); if (!option.Value) { return; } var syntaxFacts = this.GetSyntaxFactsService(); 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 condition = conditionNode as TBinaryExpressionSyntax; if (condition == null) { return; } var isEquals = IsEquals(condition); var isNotEquals = IsNotEquals(condition); 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; } var semanticModel = context.SemanticModel; 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 AnalyzeNode(SyntaxNodeAnalysisContext context) { var cancellationToken = context.CancellationToken; var node = (TMemberAccessExpressionSyntax)context.Node; var syntaxFacts = GetSyntaxFactsService(); var expr = syntaxFacts.GetExpressionOfMemberAccessExpression(node); if (!(expr is TThisExpressionSyntax)) { return; } var analyzerOptions = context.Options; var syntaxTree = node.SyntaxTree; var optionSet = analyzerOptions.GetAnalyzerOptionSet(syntaxTree, cancellationToken); var model = context.SemanticModel; if (!CanSimplifyTypeNameExpression( model, node, optionSet, out var issueSpan, cancellationToken)) { return; } if (model.SyntaxTree.OverlapsHiddenPosition(issueSpan, cancellationToken)) { return; } var symbolInfo = model.GetSymbolInfo(node, cancellationToken); if (symbolInfo.Symbol == null) { return; } var applicableOption = QualifyMembersHelpers.GetApplicableOptionFromSymbolKind(symbolInfo.Symbol.Kind); var optionValue = optionSet.GetOption(applicableOption, GetLanguageName()); if (optionValue == null) { return; } var severity = optionValue.Notification.Severity; var descriptor = CreateUnnecessaryDescriptor(DescriptorId); if (descriptor == null) { return; } var tree = model.SyntaxTree; 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(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration); builder["OptionLanguage"] = model.Language; var diagnostic = DiagnosticHelper.Create( descriptor, tree.GetLocation(issueSpan), severity, ImmutableArray.Create(node.GetLocation()), builder.ToImmutable()); context.ReportDiagnostic(diagnostic); }
private static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, SyntaxNode declaration, TextSpan diagnosticSpan, ReportDiagnostic severity) => DiagnosticHelper.Create(descriptor, declaration.SyntaxTree.GetLocation(diagnosticSpan), severity, additionalLocations: null, properties: null);
private void AnalyzeInvokedMember( OperationAnalysisContext context, InfoCache infoCache, IOperation instance, IMethodSymbol targetMethodOpt, IOperation argumentValue, IPropertySymbol lengthLikePropertyOpt, CancellationToken cancellationToken) { // look for `s[s.Length - value]` or `s.Get(s.Length- value)`. // Needs to have the one arg for `s.Length - value`, and that arg needs to be // a subtraction. if (instance is null || !IsSubtraction(argumentValue, out var subtraction)) { return; } if (!(subtraction.Syntax is BinaryExpressionSyntax binaryExpression)) { return; } // Only supported on C# 8 and above. var syntaxTree = binaryExpression.SyntaxTree; var parseOptions = (CSharpParseOptions)syntaxTree.Options; if (parseOptions.LanguageVersion < LanguageVersion.CSharp8) { return; } // Don't bother analyzing if the user doesn't like using Index/Range operators. var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet is null) { return; } var option = optionSet.GetOption(CSharpCodeStyleOptions.PreferIndexOperator); if (!option.Value) { return; } // Ok, looks promising. We're indexing in with some subtraction expression. Examine the // type this indexer is in to see if there's another member that takes a System.Index // that we can convert to. // // Also ensure that the left side of the subtraction : `s.Length - value` is actually // getting the length off the same instance we're indexing into. lengthLikePropertyOpt ??= TryGetLengthLikeProperty(infoCache, targetMethodOpt); if (lengthLikePropertyOpt == null || !IsInstanceLengthCheck(lengthLikePropertyOpt, instance, subtraction.LeftOperand)) { return; } // Everything looks good. We can update this to use the System.Index member instead. context.ReportDiagnostic( DiagnosticHelper.Create( Descriptor, binaryExpression.GetLocation(), option.Notification.Severity, ImmutableArray <Location> .Empty, ImmutableDictionary <string, string> .Empty)); }
public void AnalyzeNode(SyntaxNodeAnalysisContext context) { var statement = context.Node; var cancellationToken = context.CancellationToken; var option = context.Options.GetOption(CSharpCodeStyleOptions.PreferBraces, statement.SyntaxTree, cancellationToken); if (option.Value == PreferBracesPreference.None) { return; } var embeddedStatement = statement.GetEmbeddedStatement(); Contract.ThrowIfNull(embeddedStatement); switch (embeddedStatement.Kind()) { case SyntaxKind.Block: // The embedded statement already has braces, which is always allowed. return; case SyntaxKind.IfStatement when statement.Kind() == SyntaxKind.ElseClause: // Constructs like the following are always allowed: // // if (something) // { // } // else if (somethingElse) // <-- 'if' nested in an 'else' clause // { // } return; case SyntaxKind.LockStatement: case SyntaxKind.UsingStatement: case SyntaxKind.FixedStatement: // If we have something like this: // // using (...) // using (...) // { // } // // The first statement needs no block as it formatted with the same indentation. if (statement.Kind() == embeddedStatement.Kind()) { return; } break; } if (option.Value == PreferBracesPreference.WhenMultiline && !IsConsideredMultiLine(statement, embeddedStatement) && !RequiresBracesToMatchContext(statement)) { return; } if (ContainsInterleavedDirective(statement, embeddedStatement, cancellationToken)) { return; } var firstToken = statement.GetFirstToken(); context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, firstToken.GetLocation(), option.Notification.Severity, additionalLocations: null, properties: null, SyntaxFacts.GetText(firstToken.Kind()))); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var conditionalExpression = (TConditionalExpressionSyntax)context.Node; var cancellationToken = context.CancellationToken; var option = context.GetOption(CodeStyleOptions2.PreferCoalesceExpression, conditionalExpression.Language); 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 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 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, INamedTypeSymbol expressionTypeOpt, IMethodSymbol referenceEqualsMethodOpt) { var conditionalExpression = (TConditionalExpressionSyntax)context.Node; if (!ShouldAnalyze(conditionalExpression.SyntaxTree.Options)) { return; } var syntaxTree = conditionalExpression.SyntaxTree; var cancellationToken = context.CancellationToken; var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var option = optionSet.GetOption(CodeStyleOptions.PreferNullPropagation, conditionalExpression.Language); if (!option.Value) { return; } var syntaxFacts = this.GetSyntaxFactsService(); syntaxFacts.GetPartsOfConditionalExpression( conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode); conditionNode = syntaxFacts.WalkDownParentheses(conditionNode); var conditionIsNegated = false; if (syntaxFacts.IsLogicalNotExpression(conditionNode)) { conditionIsNegated = true; conditionNode = syntaxFacts.WalkDownParentheses( syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode)); } var isEqualityLikeCondition = TryAnalyzeCondition( context, syntaxFacts, referenceEqualsMethodOpt, conditionNode, out var conditionPartToCheck, out var isEquals); if (!isEqualityLikeCondition) { 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 semanticFacts = GetSemanticFactsService(); var semanticModel = context.SemanticModel; var whenPartMatch = GetWhenPartMatch(syntaxFacts, semanticFacts, 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 INamedTypeSymbol namedType) || namedType.ConstructedFrom.SpecialType != SpecialType.core_Option_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 (semanticFacts.IsInExpressionTree(semanticModel, conditionNode, expressionTypeOpt, 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.core_Option_T; if (whenPartIsNullable) { properties = properties.Add(UseNullPropagationConstants.WhenPartIsNullable, ""); } context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, conditionalExpression.GetLocation(), option.Notification.Severity, locations, properties)); }
private void AnalyzeOperation(OperationAnalysisContext context, IOperation operation, IOperation instanceOperation) { // this is a static reference so we don't care if it's qualified if (instanceOperation == null) { return; } // if we're not referencing `this.` or `Me.` (e.g., a parameter, local, etc.) if (instanceOperation.Kind != OperationKind.InstanceReference) { return; } // We shouldn't qualify if it is inside a property pattern if (context.Operation.Parent.Kind == OperationKind.PropertySubpattern) { return; } // Initializer lists are IInvocationOperation which if passed to GetApplicableOptionFromSymbolKind // will incorrectly fetch the options for method call. // We still want to handle InstanceReferenceKind.ContainingTypeInstance if ((instanceOperation as IInstanceReferenceOperation)?.ReferenceKind == InstanceReferenceKind.ImplicitReceiver) { return; } // If we can't be qualified (e.g., because we're already qualified with `base.`), we're done. if (!CanMemberAccessBeQualified(context.ContainingSymbol, instanceOperation.Syntax)) { return; } // if we can't find a member then we can't do anything. Also, we shouldn't qualify // accesses to static members. if (IsStaticMemberOrIsLocalFunction(operation)) { return; } if (!(instanceOperation.Syntax is TSimpleNameSyntax simpleName)) { return; } var syntaxTree = context.Operation.Syntax.SyntaxTree; var cancellationToken = context.CancellationToken; var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var applicableOption = QualifyMembersHelpers.GetApplicableOptionFromSymbolKind(operation); var optionValue = optionSet.GetOption(applicableOption, context.Operation.Syntax.Language); var shouldOptionBePresent = optionValue.Value; var severity = optionValue.Notification.Severity; if (!shouldOptionBePresent || severity == ReportDiagnostic.Suppress) { return; } if (!IsAlreadyQualifiedMemberAccess(simpleName)) { context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, GetLocation(operation), severity, additionalLocations: null, properties: null)); } }
protected bool TrySimplifyTypeNameExpression(SemanticModel model, SyntaxNode node, AnalyzerOptions analyzerOptions, out Diagnostic diagnostic, CancellationToken cancellationToken) { diagnostic = default; var syntaxTree = node.SyntaxTree; var optionSet = analyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return(false); } if (!CanSimplifyTypeNameExpressionCore( model, node, optionSet, out var issueSpan, out var diagnosticId, out var inDeclaration, cancellationToken)) { return(false); } if (model.SyntaxTree.OverlapsHiddenPosition(issueSpan, cancellationToken)) { return(false); } PerLanguageOption <CodeStyleOption <bool> > option; DiagnosticDescriptor descriptor; ReportDiagnostic severity; switch (diagnosticId) { case IDEDiagnosticIds.SimplifyNamesDiagnosticId: descriptor = s_descriptorSimplifyNames; severity = descriptor.DefaultSeverity.ToReportDiagnostic(); break; case IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId: descriptor = s_descriptorSimplifyMemberAccess; severity = descriptor.DefaultSeverity.ToReportDiagnostic(); break; case IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId: option = inDeclaration ? CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration : CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess; descriptor = s_descriptorPreferBuiltinOrFrameworkType; var optionValue = optionSet.GetOption(option, GetLanguageName()); severity = optionValue.Notification.Severity; break; default: throw ExceptionUtilities.UnexpectedValue(diagnosticId); } if (descriptor == null) { return(false); } var tree = model.SyntaxTree; var builder = ImmutableDictionary.CreateBuilder <string, string>(); builder["OptionName"] = nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); // TODO: need the actual one builder["OptionLanguage"] = model.Language; diagnostic = DiagnosticHelper.Create(descriptor, tree.GetLocation(issueSpan), severity, additionalLocations: null, builder.ToImmutable()); return(true); }
private void AnalyzeOperation(OperationAnalysisContext context, IOperation operation, IOperation?instanceOperation) { // this is a static reference so we don't care if it's qualified if (instanceOperation == null) { return; } // if we're not referencing `this.` or `Me.` (e.g., a parameter, local, etc.) if (instanceOperation.Kind != OperationKind.InstanceReference) { return; } // We shouldn't qualify if it is inside a property pattern if (context.Operation.Parent?.Kind == OperationKind.PropertySubpattern) { return; } // Initializer lists are IInvocationOperation which if passed to GetApplicableOptionFromSymbolKind // will incorrectly fetch the options for method call. // We still want to handle InstanceReferenceKind.ContainingTypeInstance if ((instanceOperation as IInstanceReferenceOperation)?.ReferenceKind == InstanceReferenceKind.ImplicitReceiver) { return; } // If we can't be qualified (e.g., because we're already qualified with `base.`), we're done. if (!CanMemberAccessBeQualified(context.ContainingSymbol, instanceOperation.Syntax)) { return; } // if we can't find a member then we can't do anything. Also, we shouldn't qualify // accesses to static members. if (IsStaticMemberOrIsLocalFunction(operation)) { return; } if (instanceOperation.Syntax is not TSimpleNameSyntax simpleName) { return; } var symbolKind = operation switch { IMemberReferenceOperation memberReferenceOperation => memberReferenceOperation.Member.Kind, IInvocationOperation invocationOperation => invocationOperation.TargetMethod.Kind, _ => throw ExceptionUtilities.UnexpectedValue(operation), }; var simplifierOptions = context.GetAnalyzerOptions().GetSimplifierOptions(Simplification); if (!simplifierOptions.TryGetQualifyMemberAccessOption(symbolKind, out var optionValue)) { return; } var shouldOptionBePresent = optionValue.Value; var severity = optionValue.Notification.Severity; if (!shouldOptionBePresent || severity == ReportDiagnostic.Suppress) { return; } if (!IsAlreadyQualifiedMemberAccess(simpleName)) { context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, GetLocation(operation), severity, additionalLocations: null, properties: null)); } }
protected override void InitializeWorker(AnalysisContext context) { context.RegisterCompilationStartAction(compilationStartContext => { // State map for fields: // 'isCandidate' : Indicates whether the field is a candidate to be made readonly based on it's options. // 'written' : Indicates if there are any writes to the field outside the constructor and field initializer. var fieldStateMap = new ConcurrentDictionary <IFieldSymbol, (bool isCandidate, bool written)>(); // We register following actions in the compilation: // 1. A symbol action for field symbols to ensure the field state is initialized for every field in // the compilation. // 2. An operation action for field references to detect if a candidate field is written outside // constructor and field initializer, and update field state accordingly. // 3. A symbol start/end action for named types to report diagnostics for candidate fields that were // not written outside constructor and field initializer. compilationStartContext.RegisterSymbolAction(AnalyzeFieldSymbol, SymbolKind.Field); compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { symbolStartContext.RegisterOperationAction(AnalyzeOperation, OperationKind.FieldReference); symbolStartContext.RegisterSymbolEndAction(OnSymbolEnd); }, SymbolKind.NamedType); return; // Local functions. void AnalyzeFieldSymbol(SymbolAnalysisContext symbolContext) { _ = TryGetOrInitializeFieldState((IFieldSymbol)symbolContext.Symbol, symbolContext.Options, symbolContext.CancellationToken); } void AnalyzeOperation(OperationAnalysisContext operationContext) { var fieldReference = (IFieldReferenceOperation)operationContext.Operation; var(isCandidate, written) = TryGetOrInitializeFieldState(fieldReference.Field, operationContext.Options, operationContext.CancellationToken); // Ignore fields that are not candidates or have already been written outside the constructor/field initializer. if (!isCandidate || written) { return; } // Check if this is a field write outside constructor and field initializer, and update field state accordingly. if (IsFieldWrite(fieldReference, operationContext.ContainingSymbol)) { UpdateFieldStateOnWrite(fieldReference.Field); } } void OnSymbolEnd(SymbolAnalysisContext symbolEndContext) { // Report diagnostics for candidate fields that are not written outside constructor and field initializer. var members = ((INamedTypeSymbol)symbolEndContext.Symbol).GetMembers(); foreach (var member in members) { if (member is IFieldSymbol field && fieldStateMap.TryRemove(field, out var value)) { var(isCandidate, written) = value; if (isCandidate && !written) { var option = GetCodeStyleOption(field, symbolEndContext.Options, symbolEndContext.CancellationToken); var diagnostic = DiagnosticHelper.Create( Descriptor, field.Locations[0], option.Notification.Severity, additionalLocations: null, properties: null); symbolEndContext.ReportDiagnostic(diagnostic); } } } } static bool IsCandidateField(IFieldSymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Private && !symbol.IsReadOnly && !symbol.IsConst && !symbol.IsImplicitlyDeclared && symbol.Locations.Length == 1 && symbol.Type.IsMutableValueType() == false && !symbol.IsFixedSizeBuffer; // Method to update the field state for a candidate field written outside constructor and field initializer. void UpdateFieldStateOnWrite(IFieldSymbol field) { Debug.Assert(IsCandidateField(field)); Debug.Assert(fieldStateMap.ContainsKey(field)); fieldStateMap[field] = (isCandidate: true, written: true); } // Method to get or initialize the field state. (bool isCandidate, bool written)TryGetOrInitializeFieldState(IFieldSymbol fieldSymbol, AnalyzerOptions options, CancellationToken cancellationToken) { if (!IsCandidateField(fieldSymbol)) { return(default);
private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) { var node = syntaxContext.Node; var syntaxTree = node.SyntaxTree; // "x is Type y" is only available in C# 7.0 and above. Don't offer this refactoring // in projects targeting a lesser version. if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp7) { return; } var options = syntaxContext.Options; var cancellationToken = syntaxContext.CancellationToken; var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck); if (!styleOption.Value) { // Bail immediately if the user has disabled this feature. return; } var comparison = (BinaryExpressionSyntax)node; var operand = GetNullCheckOperand(comparison.Left, comparison.Right)?.WalkDownParentheses(); if (operand == null) { return; } var semanticModel = syntaxContext.SemanticModel; if (operand.IsKind(SyntaxKind.CastExpression, out CastExpressionSyntax castExpression)) { // Unwrap object cast var castType = semanticModel.GetTypeInfo(castExpression.Type).Type; if (castType.IsObjectType()) { operand = castExpression.Expression; } } if (semanticModel.GetSymbolInfo(comparison).GetAnySymbol().IsUserDefinedOperator()) { return; } if (!TryGetTypeCheckParts(semanticModel, operand, out VariableDeclaratorSyntax declarator, out BinaryExpressionSyntax asExpression, out ILocalSymbol localSymbol)) { return; } var localStatement = declarator.Parent?.Parent; var enclosingBlock = localStatement?.Parent; if (localStatement == null || enclosingBlock == null) { return; } var typeNode = asExpression.Right; var asType = semanticModel.GetTypeInfo(typeNode, cancellationToken).Type; if (asType.IsNullable()) { // Not legal to write "x is int? y" return; } if (asType?.TypeKind == TypeKind.Dynamic) { // Not legal to use dynamic in a pattern. return; } if (!localSymbol.Type.Equals(asType)) { // We have something like: // // BaseType b = x as DerivedType; // if (b != null) { ... } // // It's not necessarily safe to convert this to: // // if (x is DerivedType b) { ... } // // That's because there may be later code that wants to do something like assign a // 'BaseType' into 'b'. As we've now claimed that it must be DerivedType, that // won't work. This might also cause unintended changes like changing overload // resolution. So, we conservatively do not offer the change in a situation like this. return; } // Check if the as operand is ever written up to the point of null check. // // var s = field as string; // field = null; // if (s != null) { ... } // // It's no longer safe to use pattern-matching because 'field is string s' would never be true. // // Additionally, also bail out if the assigned local is referenced (i.e. read/write/nameof) up to the point of null check. // var s = field as string; // MethodCall(flag: s == null); // if (s != null) { ... } // var asOperand = semanticModel.GetSymbolInfo(asExpression.Left, cancellationToken).Symbol; var localStatementStart = localStatement.SpanStart; var comparisonSpanStart = comparison.SpanStart; foreach (var descendentNode in enclosingBlock.DescendantNodes()) { var descendentNodeSpanStart = descendentNode.SpanStart; if (descendentNodeSpanStart <= localStatementStart) { continue; } if (descendentNodeSpanStart >= comparisonSpanStart) { break; } if (descendentNode.IsKind(SyntaxKind.IdentifierName, out IdentifierNameSyntax identifierName)) { // Check if this is a 'write' to the asOperand. if (identifierName.Identifier.ValueText == asOperand?.Name && asOperand.Equals(semanticModel.GetSymbolInfo(identifierName, cancellationToken).Symbol) && identifierName.IsWrittenTo()) { return; } // Check is a reference of any sort (i.e. read/write/nameof) to the local. if (identifierName.Identifier.ValueText == localSymbol.Name) { return; } } } if (!Analyzer.CanSafelyConvertToPatternMatching( semanticModel, localSymbol, comparison, operand, localStatement, enclosingBlock, cancellationToken)) { return; } // Looks good! var additionalLocations = ImmutableArray.Create( declarator.GetLocation(), comparison.GetLocation(), asExpression.GetLocation()); // Put a diagnostic with the appropriate severity on the declaration-statement itself. syntaxContext.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, localStatement.GetLocation(), styleOption.Notification.Severity, additionalLocations, properties: null)); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var outermostUsing = (UsingStatementSyntax)context.Node; var syntaxTree = context.Node.SyntaxTree; var options = (CSharpParseOptions)syntaxTree.Options; if (options.LanguageVersion < LanguageVersion.CSharp8) { return; } if (!(outermostUsing.Parent is BlockSyntax parentBlock)) { // Don't offer on a using statement that is parented by another using statement. // We'll just offer on the topmost using statement. return; } var innermostUsing = outermostUsing; // Check that all the immediately nested usings are convertible as well. // We don't want take a sequence of nested-using and only convert some of them. for (var current = outermostUsing; current != null; current = current.Statement as UsingStatementSyntax) { innermostUsing = current; if (current.Declaration == null) { return; } } // Verify that changing this using-statement into a using-declaration will not // change semantics. if (!PreservesSemantics(parentBlock, outermostUsing, innermostUsing)) { return; } var cancellationToken = context.CancellationToken; var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var option = optionSet.GetOption(CSharpCodeStyleOptions.PreferSimpleUsingStatement); if (!option.Value) { return; } // Good to go! context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, outermostUsing.UsingKeyword.GetLocation(), option.Notification.Severity, additionalLocations: ImmutableArray.Create(outermostUsing.GetLocation()), properties: null)); }
private void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol expressionTypeOpt) { var syntaxTree = context.Operation.Syntax.SyntaxTree; if (!IsSupported(syntaxTree.Options)) { return; } var cancellationToken = context.CancellationToken; var throwOperation = (IThrowOperation)context.Operation; var throwStatementSyntax = throwOperation.Syntax; var compilation = context.Compilation; var semanticModel = compilation.GetSemanticModel(throwStatementSyntax.SyntaxTree); var ifOperation = GetContainingIfOperation( semanticModel, throwOperation, cancellationToken); // This throw statement isn't parented by an if-statement. Nothing to // do here. if (ifOperation == null) { return; } if (ifOperation.WhenFalse != null) { // Can't offer this if the 'if-statement' has an 'else-clause'. return; } var options = context.Options; var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var option = optionSet.GetOption(CodeStyleOptions.PreferThrowExpression, throwStatementSyntax.Language); if (!option.Value) { return; } var semanticFacts = GetSemanticFactsService(); if (semanticFacts.IsInExpressionTree(semanticModel, throwStatementSyntax, expressionTypeOpt, cancellationToken)) { return; } var containingBlock = semanticModel.GetOperation(ifOperation.Syntax.Parent, cancellationToken) as IBlockOperation; if (containingBlock == null) { return; } if (!TryDecomposeIfCondition(ifOperation, out var localOrParameter)) { return; } if (!TryFindAssignmentExpression(containingBlock, ifOperation, localOrParameter, out var expressionStatement, out var assignmentExpression)) { return; } if (!localOrParameter.GetSymbolType().CanAddNullCheck()) { return; } // We found an assignment using this local/parameter. Now, just make sure there // were no intervening accesses between the check and the assignment. if (ValueIsAccessed( semanticModel, ifOperation, containingBlock, localOrParameter, expressionStatement, assignmentExpression)) { return; } // Ok, there were no intervening writes or accesses. This check+assignment can be simplified. var allLocations = ImmutableArray.Create( ifOperation.Syntax.GetLocation(), throwOperation.Exception.Syntax.GetLocation(), assignmentExpression.Value.Syntax.GetLocation()); context.ReportDiagnostic( DiagnosticHelper.Create(Descriptor, throwStatementSyntax.GetLocation(), option.Notification.Severity, additionalLocations: allLocations, properties: null)); // Fade out the rest of the if that surrounds the 'throw' exception. var tokenBeforeThrow = throwStatementSyntax.GetFirstToken().GetPreviousToken(); var tokenAfterThrow = throwStatementSyntax.GetLastToken().GetNextToken(); context.ReportDiagnostic( Diagnostic.Create(UnnecessaryWithSuggestionDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds( ifOperation.Syntax.SpanStart, tokenBeforeThrow.Span.End)), additionalLocations: allLocations)); if (ifOperation.Syntax.Span.End > tokenAfterThrow.Span.Start) { context.ReportDiagnostic( Diagnostic.Create(UnnecessaryWithSuggestionDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds( tokenAfterThrow.Span.Start, ifOperation.Syntax.Span.End)), additionalLocations: allLocations)); } }
private void AnalyzeConditionalExpression(SyntaxNodeAnalysisContext context) { var semanticModel = context.SemanticModel; var syntaxTree = semanticModel.SyntaxTree; var options = context.Options; var cancellationToken = context.CancellationToken; var styleOption = options.GetOption( CodeStyleOptions.PreferSimplifiedBooleanExpressions, semanticModel.Language, syntaxTree, cancellationToken); if (!styleOption.Value) { // Bail immediately if the user has disabled this feature. return; } var conditionalExpression = (TConditionalExpressionSyntax)context.Node; this.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( this.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); } }
internal static Diagnostic CreateDiagnostic(SemanticModel model, OptionSet optionSet, TextSpan issueSpan, string diagnosticId, bool inDeclaration) { PerLanguageOption <CodeStyleOption <bool> > option; DiagnosticDescriptor descriptor; ReportDiagnostic severity; switch (diagnosticId) { case IDEDiagnosticIds.SimplifyNamesDiagnosticId: descriptor = s_descriptorSimplifyNames; severity = descriptor.DefaultSeverity.ToReportDiagnostic(); break; case IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId: descriptor = s_descriptorSimplifyMemberAccess; severity = descriptor.DefaultSeverity.ToReportDiagnostic(); break; case IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId: option = inDeclaration ? CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration : CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess; descriptor = s_descriptorPreferBuiltinOrFrameworkType; var optionValue = optionSet.GetOption(option, model.Language); severity = optionValue.Notification.Severity; break; default: throw ExceptionUtilities.UnexpectedValue(diagnosticId); } var tree = model.SyntaxTree; var builder = ImmutableDictionary.CreateBuilder <string, string>(); builder["OptionName"] = nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); // TODO: need the actual one builder["OptionLanguage"] = model.Language; var diagnostic = DiagnosticHelper.Create(descriptor, tree.GetLocation(issueSpan), severity, additionalLocations: null, builder.ToImmutable()); #if LOG var sourceText = tree.GetText(); sourceText.GetLineAndOffset(issueSpan.Start, out var startLineNumber, out var startOffset); sourceText.GetLineAndOffset(issueSpan.End, out var endLineNumber, out var endOffset); var logLine = tree.FilePath + "," + startLineNumber + "\t" + diagnosticId + "\t" + inDeclaration + "\t"; var leading = sourceText.ToString(TextSpan.FromBounds( sourceText.Lines[startLineNumber].Start, issueSpan.Start)); var mid = sourceText.ToString(issueSpan); var trailing = sourceText.ToString(TextSpan.FromBounds( issueSpan.End, sourceText.Lines[endLineNumber].End)); var contents = leading + "[|" + s_newlinePattern.Replace(mid, " ") + "|]" + trailing; logLine += contents + "\r\n"; lock (_logGate) { File.AppendAllText(_logFile, logLine); } #endif return(diagnostic); }
private void ProcessBlockLikeStatement(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, SyntaxNode block) { // Don't examine broken blocks. var endToken = block.GetLastToken(); if (endToken.IsMissing) { return; } // If the close brace itself doesn't have a newline, then ignore this. This is a case of series of // statements on the same line. if (!endToken.TrailingTrivia.Any()) { return; } if (!_syntaxFacts.IsEndOfLineTrivia(endToken.TrailingTrivia.Last())) { return; } // Grab whatever comes after the close brace. If it's not the start of a statement, ignore it. var nextToken = endToken.GetNextToken(); var nextTokenContainingStatement = nextToken.Parent?.FirstAncestorOrSelf <TExecutableStatementSyntax>(); if (nextTokenContainingStatement == null) { return; } if (nextToken != nextTokenContainingStatement.GetFirstToken()) { return; } // There has to be at least a blank line between the end of the block and the start of the next statement. foreach (var trivia in nextToken.LeadingTrivia) { // If there's a blank line between the brace and the next token, we're all set. if (_syntaxFacts.IsEndOfLineTrivia(trivia)) { return; } if (_syntaxFacts.IsWhitespaceTrivia(trivia)) { continue; } // got something that wasn't whitespace. Bail out as we don't want to place any restrictions on this code. return; } context.ReportDiagnostic(DiagnosticHelper.Create( this.Descriptor, GetDiagnosticLocation(block), severity, additionalLocations: ImmutableArray.Create(nextToken.GetLocation()), properties: null)); }
private void AnalyzeUnusedValueAssignments( OperationBlockAnalysisContext context, bool isComputingUnusedParams, PooledHashSet <SymbolUsageResult> symbolUsageResultsBuilder, out bool hasBlockWithAllUsedSymbolWrites) { hasBlockWithAllUsedSymbolWrites = false; foreach (var operationBlock in context.OperationBlocks) { if (!ShouldAnalyze(operationBlock, context.OwningSymbol)) { continue; } // First perform the fast, aggressive, imprecise operation-tree based analysis. // This analysis might flag some "used" symbol writes as "unused", but will not miss reporting any truly unused symbol writes. // This initial pass helps us reduce the number of methods for which we perform the slower second pass. // We perform the first fast pass only if there are no delegate creations/lambda methods. // This is due to the fact that tracking which local/parameter points to which delegate creation target // at any given program point needs needs flow analysis (second pass). if (!_hasDelegateCreationOrAnonymousFunction) { var resultFromOperationBlockAnalysis = SymbolUsageAnalysis.Run(operationBlock, context.OwningSymbol, context.CancellationToken); if (!resultFromOperationBlockAnalysis.HasUnreadSymbolWrites()) { // Assert that even slow pass (dataflow analysis) would have yielded no unused symbol writes. Debug.Assert(!SymbolUsageAnalysis.Run(context.GetControlFlowGraph(operationBlock), context.OwningSymbol, context.CancellationToken) .HasUnreadSymbolWrites()); hasBlockWithAllUsedSymbolWrites = true; continue; } } // Now perform the slower, precise, CFG based dataflow analysis to identify the actual unused symbol writes. var controlFlowGraph = context.GetControlFlowGraph(operationBlock); var symbolUsageResult = SymbolUsageAnalysis.Run(controlFlowGraph, context.OwningSymbol, context.CancellationToken); symbolUsageResultsBuilder.Add(symbolUsageResult); foreach (var(symbol, unreadWriteOperation) in symbolUsageResult.GetUnreadSymbolWrites()) { if (unreadWriteOperation == null) { // Null operation is used for initial write for the parameter from method declaration. // So, the initial value of the parameter is never read in this operation block. // However, we do not report this as an unused parameter here as a different operation block // might be reading the initial parameter value. // For example, a constructor with both a constructor initializer and body will have two different operation blocks // and a parameter must be unused across both these blocks to be marked unused. // However, we do report unused parameters for local function here. // Local function parameters are completely scoped to this operation block, and should be reported per-operation block. var unusedParameter = (IParameterSymbol)symbol; if (isComputingUnusedParams && unusedParameter.ContainingSymbol.IsLocalFunction()) { var hasReference = symbolUsageResult.SymbolsRead.Contains(unusedParameter); bool shouldReport; switch (unusedParameter.RefKind) { case RefKind.Out: // Do not report out parameters of local functions. // If they are unused in the caller, we will flag the // out argument at the local function callsite. shouldReport = false; break; case RefKind.Ref: // Report ref parameters only if they have no read/write references. // Note that we always have one write for the parameter input value from the caller. shouldReport = !hasReference && symbolUsageResult.GetSymbolWriteCount(unusedParameter) == 1; break; default: shouldReport = true; break; } if (shouldReport) { _symbolStartAnalyzer.ReportUnusedParameterDiagnostic(unusedParameter, hasReference, context.ReportDiagnostic, context.Options, context.CancellationToken); } } continue; } if (ShouldReportUnusedValueDiagnostic(symbol, unreadWriteOperation, symbolUsageResult, out var properties)) { var diagnostic = DiagnosticHelper.Create(s_valueAssignedIsUnusedRule, _symbolStartAnalyzer._compilationAnalyzer.GetDefinitionLocationToFade(unreadWriteOperation), _options.UnusedValueAssignmentSeverity, additionalLocations: null, properties, symbol.Name); context.ReportDiagnostic(diagnostic); } } } return; // Local functions. bool ShouldReportUnusedValueDiagnostic( ISymbol symbol, IOperation unreadWriteOperation, SymbolUsageResult resultFromFlowAnalysis, out ImmutableDictionary <string, string> properties) { Debug.Assert(!(symbol is ILocalSymbol local) || !local.IsRef); properties = null; // Bail out in following cases: // 1. End user has configured the diagnostic to be suppressed. // 2. Symbol has error type, hence the diagnostic could be noised // 3. Static local symbols. Assignment to static locals // is not unnecessary as the assigned value can be used on the next invocation. // 4. Ignore special discard symbol names (see https://github.com/dotnet/roslyn/issues/32923). if (_options.UnusedValueAssignmentSeverity == ReportDiagnostic.Suppress || symbol.GetSymbolType().IsErrorType() || (symbol.IsStatic && symbol.Kind == SymbolKind.Local) || IsSymbolWithSpecialDiscardName(symbol)) { return(false); } // Flag to indicate if the symbol has no reads. var isUnusedLocalAssignment = symbol is ILocalSymbol localSymbol && !resultFromFlowAnalysis.SymbolsRead.Contains(localSymbol); var isRemovableAssignment = IsRemovableAssignmentWithoutSideEffects(unreadWriteOperation); if (isUnusedLocalAssignment && !isRemovableAssignment && _options.UnusedValueAssignmentPreference == UnusedValuePreference.UnusedLocalVariable) { // Meets current user preference of using unused local symbols for storing computation result. // Skip reporting diagnostic. return(false); } properties = s_propertiesMap[(_options.UnusedValueAssignmentPreference, isUnusedLocalAssignment, isRemovableAssignment)]; return(true); }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var outermostUsing = (UsingStatementSyntax)context.Node; var syntaxTree = context.Node.SyntaxTree; var options = (CSharpParseOptions)syntaxTree.Options; if (options.LanguageVersion < LanguageVersion.CSharp8) { return; } if (outermostUsing.Parent is not BlockSyntax parentBlock) { // Don't offer on a using statement that is parented by another using statement. // We'll just offer on the topmost using statement. return; } var innermostUsing = outermostUsing; // Check that all the immediately nested usings are convertible as well. // We don't want take a sequence of nested-using and only convert some of them. for (var current = outermostUsing; current != null; current = current.Statement as UsingStatementSyntax) { innermostUsing = current; if (current.Declaration == null) { return; } } // Verify that changing this using-statement into a using-declaration will not // change semantics. if (!PreservesSemantics(parentBlock, outermostUsing, innermostUsing)) { return; } var cancellationToken = context.CancellationToken; // Converting a using-statement to a using-variable-declaration will cause the using's // variables to now be pushed up to the parent block's scope. This is also true for any // local variables in the innermost using's block. These may then collide with other // variables in the block, causing an error. Check for that and bail if this happens. if (CausesVariableCollision( context.SemanticModel, parentBlock, outermostUsing, innermostUsing, cancellationToken)) { return; } var option = context.GetCSharpAnalyzerOptions().PreferSimpleUsingStatement; if (!option.Value) { return; } // Good to go! context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, outermostUsing.UsingKeyword.GetLocation(), option.Notification.Severity, additionalLocations: ImmutableArray.Create(outermostUsing.GetLocation()), properties: null)); }
private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasInvalidOperation) { // We bail out reporting diagnostics for named types which have any invalid operations, i.e. erroneous code. // We do so to ensure that we don't report false positives during editing scenarios in the IDE, where the user // is still editing code and fixing unresolved references to symbols, such as overload resolution errors. if (hasInvalidOperation) { return; } // Report diagnostics for unused candidate members. var first = true; PooledHashSet <ISymbol> symbolsReferencedInDocComments = null; try { var namedType = (INamedTypeSymbol)symbolEndContext.Symbol; foreach (var member in namedType.GetMembers()) { // Check if the underlying member is neither read nor a readable reference to the member is taken. // If so, we flag the member as either unused (never written) or unread (written but not read). if (TryRemove(member, out var valueUsageInfo) && !valueUsageInfo.ContainsReadOrReadableRef()) { Debug.Assert(IsCandidateSymbol(member)); Debug.Assert(!member.IsImplicitlyDeclared); if (first) { // Bail out if there are syntax errors in any of the declarations of the containing type. // Note that we check this only for the first time that we report an unused or unread member for the containing type. if (HasSyntaxErrors(namedType, symbolEndContext.CancellationToken)) { return; } // Compute the set of candidate symbols referenced in all the documentation comments within the named type declarations. // This set is computed once and used for all the iterations of the loop. symbolsReferencedInDocComments = GetCandidateSymbolsReferencedInDocComments(namedType, symbolEndContext.Compilation, symbolEndContext.CancellationToken); first = false; } // Report IDE0051 or IDE0052 based on whether the underlying member has any Write/WritableRef/NonReadWriteRef references or not. var rule = !valueUsageInfo.ContainsWriteOrWritableRef() && !valueUsageInfo.ContainsNonReadWriteRef() && !symbolsReferencedInDocComments.Contains(member) ? _removeUnusedMembersRule : _removeUnreadMembersRule; var effectiveSeverity = rule.GetEffectiveSeverity(symbolEndContext.Compilation.Options); // Most of the members should have a single location, except for partial methods. // We report the diagnostic on the first location of the member. var diagnostic = DiagnosticHelper.Create( rule, member.Locations[0], effectiveSeverity, additionalLocations: null, properties: null, member.ContainingType.Name, member.Name); symbolEndContext.ReportDiagnostic(diagnostic); } } } finally { symbolsReferencedInDocComments?.Free(); } return; }
private void AnalyzeNode( SyntaxNodeAnalysisContext context, INamedTypeSymbol ienumerableType ) { if (!AreCollectionInitializersSupported(context)) { return; } var semanticModel = context.SemanticModel; var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node; var language = objectCreationExpression.Language; var cancellationToken = context.CancellationToken; var option = context.GetOption(CodeStyleOptions2.PreferCollectionInitializer, language); 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 = ObjectCreationExpressionAnalyzer < 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()); var severity = option.Notification.Severity; context.ReportDiagnostic( DiagnosticHelper.Create( Descriptor, objectCreationExpression.GetLocation(), severity, additionalLocations: locations, properties: null ) ); FadeOutCode(context, matches.Value, locations); }
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 OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasUnsupportedOperation) { if (hasUnsupportedOperation) { return; } if (symbolEndContext.Symbol.GetAttributes().Any(a => a.AttributeClass == _structLayoutAttributeType)) { // Bail out for types with 'StructLayoutAttribute' as the ordering of the members is critical, // and removal of unused members might break semantics. return; } // Report diagnostics for unused candidate members. var first = true; PooledHashSet <ISymbol> symbolsReferencedInDocComments = null; ArrayBuilder <string> debuggerDisplayAttributeArguments = null; try { var namedType = (INamedTypeSymbol)symbolEndContext.Symbol; foreach (var member in namedType.GetMembers()) { // Check if the underlying member is neither read nor a readable reference to the member is taken. // If so, we flag the member as either unused (never written) or unread (written but not read). if (TryRemove(member, out var valueUsageInfo) && !valueUsageInfo.IsReadFrom()) { Debug.Assert(IsCandidateSymbol(member)); Debug.Assert(!member.IsImplicitlyDeclared); if (first) { // Bail out if there are syntax errors in any of the declarations of the containing type. // Note that we check this only for the first time that we report an unused or unread member for the containing type. if (HasSyntaxErrors(namedType, symbolEndContext.CancellationToken)) { return; } // Compute the set of candidate symbols referenced in all the documentation comments within the named type declarations. // This set is computed once and used for all the iterations of the loop. symbolsReferencedInDocComments = GetCandidateSymbolsReferencedInDocComments(namedType, symbolEndContext.Compilation, symbolEndContext.CancellationToken); // Compute the set of string arguments to DebuggerDisplay attributes applied to any symbol within the named type declaration. // These strings may have an embedded reference to the symbol. // This set is computed once and used for all the iterations of the loop. debuggerDisplayAttributeArguments = GetDebuggerDisplayAttributeArguments(namedType); first = false; } // Simple heuristic for members referenced in DebuggerDisplayAttribute's string argument: // bail out if any of the DebuggerDisplay string arguments contains the member name. // In future, we can consider improving this heuristic to parse the embedded expression // and resolve symbol references. if (debuggerDisplayAttributeArguments.Any(arg => arg.Contains(member.Name))) { continue; } // Report IDE0051 or IDE0052 based on whether the underlying member has any Write/WritableRef/NonReadWriteRef references or not. var rule = !valueUsageInfo.IsWrittenTo() && !valueUsageInfo.IsNameOnly() && !symbolsReferencedInDocComments.Contains(member) ? s_removeUnusedMembersRule : s_removeUnreadMembersRule; // Do not flag write-only properties that are not read. // Write-only properties are assumed to have side effects // visible through other means than a property getter. if (rule == s_removeUnreadMembersRule && member is IPropertySymbol property && property.IsWriteOnly) { continue; } // Most of the members should have a single location, except for partial methods. // We report the diagnostic on the first location of the member. var diagnostic = DiagnosticHelper.CreateWithMessage( rule, member.Locations[0], rule.GetEffectiveSeverity(symbolEndContext.Compilation.Options), additionalLocations: null, properties: null, GetMessage(rule, member)); symbolEndContext.ReportDiagnostic(diagnostic); } } } finally { symbolsReferencedInDocComments?.Free(); debuggerDisplayAttributeArguments?.Free(); } return; }
private void ProcessMemberDeclaration( SyntaxTreeAnalysisContext context, SyntaxGenerator generator, CodeStyleOption <AccessibilityModifiersRequired> option, MemberDeclarationSyntax member) { if (member.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration)) { ProcessMembers(context, generator, option, namespaceDeclaration.Members); } // If we have a class or struct, recurse inwards. if (member.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) || member.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration)) { ProcessMembers(context, generator, option, typeDeclaration.Members); } #if false // Add this once we have the language version for C# that supports accessibility // modifiers on interface methods. if (option.Value == AccessibilityModifiersRequired.Always && member.IsKind(SyntaxKind.InterfaceDeclaration, out typeDeclaration)) { // Only recurse into an interface if the user wants accessibility modifiers on ProcessTypeDeclaration(context, generator, option, typeDeclaration); } #endif // Have to have a name to report the issue on. var name = member.GetNameToken(); if (name.Kind() == SyntaxKind.None) { return; } // Certain members never have accessibility. Don't bother reporting on them. if (!generator.CanHaveAccessibility(member)) { return; } // This analyzer bases all of its decisions on the accessibility var accessibility = generator.GetAccessibility(member); // Omit will flag any accesibility values that exist and are default // The other options will remove or ignore accessibility var isOmit = option.Value == AccessibilityModifiersRequired.OmitIfDefault; if (isOmit) { if (accessibility == Accessibility.NotApplicable) { return; } var parentKind = member.Parent.Kind(); switch (parentKind) { // Check for default modifiers in namespace and outside of namespace case SyntaxKind.CompilationUnit: case SyntaxKind.NamespaceDeclaration: { // Default is internal if (accessibility != Accessibility.Internal) { return; } } break; case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: { // Inside a type, default is private if (accessibility != Accessibility.Private) { return; } } break; default: return; // Unknown parent kind, don't do anything } } else { // Mode is always, so we have to flag missing modifiers if (accessibility != Accessibility.NotApplicable) { return; } } // Have an issue to flag, either add or remove. Report issue to user. var additionalLocations = ImmutableArray.Create(member.GetLocation()); context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, name.GetLocation(), option.Notification.Severity, additionalLocations: additionalLocations, properties: null)); }
private void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol?expressionTypeOpt) { var cancellationToken = context.CancellationToken; var throwOperation = (IThrowOperation)context.Operation; if (throwOperation.Exception == null) { return; } var throwStatementSyntax = throwOperation.Syntax; var semanticModel = context.Operation.SemanticModel; Contract.ThrowIfNull(semanticModel); var ifOperation = GetContainingIfOperation( semanticModel, throwOperation, cancellationToken); // This throw statement isn't parented by an if-statement. Nothing to // do here. if (ifOperation == null) { return; } if (ifOperation.WhenFalse != null) { // Can't offer this if the 'if-statement' has an 'else-clause'. return; } var option = PreferThrowExpressionStyle(context); if (!option.Value) { return; } if (IsInExpressionTree(semanticModel, throwStatementSyntax, expressionTypeOpt, cancellationToken)) { return; } if (ifOperation.Parent is not IBlockOperation containingBlock) { return; } if (!TryDecomposeIfCondition(ifOperation, out var localOrParameter)) { return; } if (!TryFindAssignmentExpression(containingBlock, ifOperation, localOrParameter, out var expressionStatement, out var assignmentExpression)) { return; } if (!localOrParameter.GetSymbolType().CanAddNullCheck()) { return; } // We found an assignment using this local/parameter. Now, just make sure there // were no intervening accesses between the check and the assignment. if (ValueIsAccessed( semanticModel, ifOperation, containingBlock, localOrParameter, expressionStatement, assignmentExpression)) { return; } // Ok, there were no intervening writes or accesses. This check+assignment can be simplified. var allLocations = ImmutableArray.Create( ifOperation.Syntax.GetLocation(), throwOperation.Exception.Syntax.GetLocation(), assignmentExpression.Value.Syntax.GetLocation()); context.ReportDiagnostic( DiagnosticHelper.Create(Descriptor, throwStatementSyntax.GetLocation(), option.Notification.Severity, additionalLocations: allLocations, properties: null)); }
private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext, INamedTypeSymbol expressionTypeOpt) { var options = syntaxContext.Options; var syntaxTree = syntaxContext.Node.SyntaxTree; var cancellationToken = syntaxContext.CancellationToken; var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; } var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction); if (!styleOption.Value) { // Bail immediately if the user has disabled this feature. return; } // Local functions are only available in C# 7.0 and above. Don't offer this refactoring // in projects targeting a lesser version. if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp7) { return; } var severity = styleOption.Notification.Severity; var anonymousFunction = (AnonymousFunctionExpressionSyntax)syntaxContext.Node; var semanticModel = syntaxContext.SemanticModel; if (!CheckForPattern(semanticModel, anonymousFunction, cancellationToken, out var localDeclaration)) { return; } if (localDeclaration.Declaration.Variables.Count != 1) { return; } if (!(localDeclaration.Parent is BlockSyntax block)) { return; } // If there are compiler error on the declaration we can't reliably // tell that the refactoring will be accurate, so don't provide any // code diagnostics if (localDeclaration.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) { return; } var local = semanticModel.GetDeclaredSymbol(localDeclaration.Declaration.Variables[0], cancellationToken); if (local == null) { return; } var delegateType = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken).ConvertedType as INamedTypeSymbol; if (!delegateType.IsDelegateType() || delegateType.DelegateInvokeMethod == null || !CanReplaceDelegateWithLocalFunction(delegateType, localDeclaration, semanticModel, cancellationToken)) { return; } if (!CanReplaceAnonymousWithLocalFunction(semanticModel, expressionTypeOpt, local, block, anonymousFunction, out var referenceLocations, cancellationToken)) { return; } // Looks good! var additionalLocations = ImmutableArray.Create( localDeclaration.GetLocation(), anonymousFunction.GetLocation()); additionalLocations = additionalLocations.AddRange(referenceLocations); if (severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) < ReportDiagnostic.Hidden) { // If the diagnostic is not hidden, then just place the user visible part // on the local being initialized with the lambda. syntaxContext.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, localDeclaration.Declaration.Variables[0].Identifier.GetLocation(), severity, additionalLocations, properties: null)); } else { // If the diagnostic is hidden, place it on the entire construct. syntaxContext.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, localDeclaration.GetLocation(), severity, additionalLocations, properties: null)); var anonymousFunctionStatement = anonymousFunction.GetAncestor <StatementSyntax>(); if (localDeclaration != anonymousFunctionStatement) { syntaxContext.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, anonymousFunctionStatement.GetLocation(), severity, additionalLocations, properties: null)); } } }
private Diagnostic?TryGetDiagnostic( Compilation compilation, ISymbol symbol, AnalyzerOptions options, ConcurrentDictionary <Guid, ConcurrentDictionary <string, string?> > idToCachedResult, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(symbol.Name)) { return(null); } if (symbol is IMethodSymbol methodSymbol && methodSymbol.IsEntryPoint(compilation.TaskType(), compilation.TaskOfTType())) { return(null); } if (ShouldIgnore(symbol)) { return(null); } if (symbol.IsSymbolWithSpecialDiscardName()) { return(null); } var sourceTree = symbol.Locations.FirstOrDefault()?.SourceTree; if (sourceTree == null) { return(null); } var namingPreferences = options.GetAnalyzerOptions(sourceTree).NamingPreferences; var namingStyleRules = namingPreferences.Rules; if (!namingStyleRules.TryGetApplicableRule(symbol, out var applicableRule) || applicableRule.EnforcementLevel == ReportDiagnostic.Suppress) { return(null); } var cache = idToCachedResult.GetOrAdd(applicableRule.NamingStyle.ID, s_createCache); if (!cache.TryGetValue(symbol.Name, out var failureReason)) { if (applicableRule.NamingStyle.IsNameCompliant(symbol.Name, out failureReason)) { failureReason = null; } cache.TryAdd(symbol.Name, failureReason); } if (failureReason == null) { return(null); } var builder = ImmutableDictionary.CreateBuilder <string, string?>(); builder[nameof(NamingStyle)] = applicableRule.NamingStyle.CreateXElement().ToString(); builder["OptionName"] = nameof(NamingStyleOptions.NamingPreferences); builder["OptionLanguage"] = compilation.Language; return(DiagnosticHelper.Create(Descriptor, symbol.Locations.First(), applicableRule.EnforcementLevel, additionalLocations: null, builder.ToImmutable(), failureReason)); }
private void ProcessUnreachableDiagnostic( SemanticModelAnalysisContext context, SyntaxNode root, TextSpan sourceSpan, bool fadeOutCode ) { var node = root.FindNode(sourceSpan); // Note: this approach works as the language only supports the concept of // unreachable statements. If we ever get unreachable subexpressions, then // we'll need to revise this code accordingly. var firstUnreachableStatement = node.FirstAncestorOrSelf <StatementSyntax>(); if ( firstUnreachableStatement == null || firstUnreachableStatement.SpanStart != sourceSpan.Start ) { return; } // At a high level, we can think about us wanting to fade out a "section" of unreachable // statements. However, the compiler only reports the first statement in that "section". // We want to figure out what other statements are in that section and fade them all out // along with the first statement. This is made somewhat tricky due to the fact that // subsequent sibling statements possibly being reachable due to explicit gotos+labels. // // On top of this, an unreachable section might not be contiguous. This is possible // when there is unreachable code that contains a local function declaration in-situ. // This is legal, and the local function declaration may be called from other reachable code. // // As such, it's not possible to just get first unreachable statement, and the last, and // then report that whole region as unreachable. Instead, when we are told about an // unreachable statement, we simply determine which other statements are also unreachable // and bucket them into contiguous chunks. // // We then fade each of these contiguous chunks, while also having each diagnostic we // report point back to the first unreachable statement so that we can easily determine // what to remove if the user fixes the issue. (The fix itself has to go recompute this // as the total set of statements to remove may be larger than the actual faded code // that that diagnostic corresponds to). // Get the location of this first unreachable statement. It will be given to all // the diagnostics we create off of this single compiler diagnostic so that we always // know how to find it regardless of which of our diagnostics the user invokes the // fix off of. var firstStatementLocation = root.SyntaxTree.GetLocation( firstUnreachableStatement.FullSpan ); // 'additionalLocations' is how we always pass along the locaiton of the first unreachable // statement in this group. var additionalLocations = ImmutableArray.Create(firstStatementLocation); if (fadeOutCode) { context.ReportDiagnostic( DiagnosticHelper.CreateWithLocationTags( Descriptor, firstStatementLocation, ReportDiagnostic.Default, additionalLocations: ImmutableArray <Location> .Empty, additionalUnnecessaryLocations: additionalLocations ) ); } else { context.ReportDiagnostic( Diagnostic.Create(Descriptor, firstStatementLocation, additionalLocations) ); } var sections = RemoveUnreachableCodeHelpers.GetSubsequentUnreachableSections( firstUnreachableStatement ); foreach (var section in sections) { var span = TextSpan.FromBounds( section[0].FullSpan.Start, section.Last().FullSpan.End ); var location = root.SyntaxTree.GetLocation(span); var additionalUnnecessaryLocations = ImmutableArray <Location> .Empty; // Mark subsequent sections as being 'cascaded'. We don't need to actually process them // when doing a fix-all as they'll be scooped up when we process the fix for the first // section. if (fadeOutCode) { additionalUnnecessaryLocations = ImmutableArray.Create(location); } context.ReportDiagnostic( DiagnosticHelper.CreateWithLocationTags( Descriptor, location, ReportDiagnostic.Default, additionalLocations, additionalUnnecessaryLocations, s_subsequentSectionProperties ) ); } }