// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
/// Analyzer used to identify local variables that could be declared Const.
public class LocalCouldBeConstAnalyzer : DiagnosticAnalyzer
{
private const string SystemCategory = "System";
public static readonly DiagnosticDescriptor LocalCouldBeConstDescriptor = new DiagnosticDescriptor(
"LocalCouldBeReadOnly",
"Local Could Be Const",
"Local variable is never modified and so could be const.",
SystemCategory,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
/// Gets the set of supported diagnostic descriptors from this analyzer.
public sealed override ImmutableArray SupportedDiagnostics
{
get { return ImmutableArray.Create(LocalCouldBeConstDescriptor); }
}
public sealed override void Initialize(AnalysisContext context)
{
context.RegisterOperationBlockStartAction(
(operationBlockContext) =>
{
IMethodSymbol containingMethod = operationBlockContext.OwningSymbol as IMethodSymbol;
if (containingMethod != null)
{
HashSet mightBecomeConstLocals = new HashSet();
HashSet assignedToLocals = new HashSet();
operationBlockContext.RegisterOperationAction(
(operationContext) =>
{
IAssignmentExpression assignment = (IAssignmentExpression)operationContext.Operation;
AssignTo(assignment.Target, assignedToLocals, mightBecomeConstLocals);
},
OperationKind.AssignmentExpression,
OperationKind.CompoundAssignmentExpression,
OperationKind.IncrementExpression);
operationBlockContext.RegisterOperationAction(
(operationContext) =>
{
IInvocationExpression invocation = (IInvocationExpression)operationContext.Operation;
foreach (IArgument argument in invocation.ArgumentsInParameterOrder)
{
if (argument.Parameter.RefKind == RefKind.Out || argument.Parameter.RefKind == RefKind.Ref)
{
AssignTo(argument.Value, assignedToLocals, mightBecomeConstLocals);
}
}
},
OperationKind.InvocationExpression);
operationBlockContext.RegisterOperationAction(
(operationContext) =>
{
IVariableDeclarationStatement declaration = (IVariableDeclarationStatement)operationContext.Operation;
foreach (IVariableDeclaration variable in declaration.Variables)
{
ILocalSymbol local = variable.Variable;
if (!local.IsConst && !assignedToLocals.Contains(local))
{
var localType = local.Type;
if ((!localType.IsReferenceType || localType.SpecialType == SpecialType.System_String) && localType.SpecialType != SpecialType.None)
{
if (variable.InitialValue != null && variable.InitialValue.ConstantValue.HasValue)
{
mightBecomeConstLocals.Add(local);
}
}
}
}
},
OperationKind.VariableDeclarationStatement);
operationBlockContext.RegisterOperationBlockEndAction(
(operationBlockEndContext) =>
{
foreach (ILocalSymbol couldBeConstLocal in mightBecomeConstLocals)
{
Report(operationBlockEndContext, couldBeConstLocal, LocalCouldBeConstDescriptor);
}
});
}
});
}
private static void AssignTo(IOperation target, HashSet assignedToLocals, HashSet mightBecomeConstLocals)
{
if (target.Kind == OperationKind.LocalReferenceExpression)
{
ILocalSymbol targetLocal = ((ILocalReferenceExpression)target).Local;
assignedToLocals.Add(targetLocal);
mightBecomeConstLocals.Remove(targetLocal);
}
else if (target.Kind == OperationKind.FieldReferenceExpression)
{
IFieldReferenceExpression fieldReference = (IFieldReferenceExpression)target;
if (fieldReference.Instance != null && fieldReference.Instance.Type.IsValueType)
{
AssignTo(fieldReference.Instance, assignedToLocals, mightBecomeConstLocals);
}
}
}
private void Report(OperationBlockAnalysisContext context, ILocalSymbol local, DiagnosticDescriptor descriptor)
{
context.ReportDiagnostic(Diagnostic.Create(descriptor, local.Locations.FirstOrDefault()));
}
}
}