diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/LanguageFeatureHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/LanguageFeatureHelpers.cs index d1da8119d..c0dbfe5ea 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/LanguageFeatureHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/LanguageFeatureHelpers.cs @@ -23,6 +23,17 @@ internal static bool SupportsTuples(this SyntaxNodeAnalysisContext context) return (csharpParseOptions != null) && (csharpParseOptions.LanguageVersion >= LanguageVersionEx.CSharp7); } + /// + /// Checks if the tuple language feature is supported. + /// + /// The analysis context that will be checked. + /// True if tuples are supported by the compiler. + internal static bool SupportsTuples(this OperationAnalysisContext context) + { + var csharpParseOptions = context.Operation.Syntax.SyntaxTree.Options as CSharpParseOptions; + return (csharpParseOptions != null) && (csharpParseOptions.LanguageVersion >= LanguageVersionEx.CSharp7); + } + /// /// Checks if the inferred tuple element names language feature is supported. /// diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldReferenceOperationWrapper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldReferenceOperationWrapper.cs new file mode 100644 index 000000000..715c1044b --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IFieldReferenceOperationWrapper.cs @@ -0,0 +1,81 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using Microsoft.CodeAnalysis; + + internal readonly struct IFieldReferenceOperationWrapper : IOperationWrapper + { + internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation"; + private static readonly Type WrappedType; + + private static readonly Func FieldAccessor; + private static readonly Func IsDeclarationAccessor; + + private readonly IOperation operation; + + static IFieldReferenceOperationWrapper() + { + WrappedType = WrapperHelper.GetWrappedType(typeof(IFieldReferenceOperationWrapper)); + FieldAccessor = LightupHelpers.CreateOperationPropertyAccessor(WrappedType, nameof(Field)); + IsDeclarationAccessor = LightupHelpers.CreateOperationPropertyAccessor(WrappedType, nameof(IsDeclaration)); + } + + private IFieldReferenceOperationWrapper(IOperation operation) + { + this.operation = operation; + } + + public IOperation WrappedOperation => this.operation; + + public ITypeSymbol Type => this.WrappedOperation.Type; + + public IFieldSymbol Field + { + get + { + return FieldAccessor(this.WrappedOperation); + } + } + + public bool IsDeclaration + { + get + { + return IsDeclarationAccessor(this.WrappedOperation); + } + } + + public IOperation Instance => ((IMemberReferenceOperationWrapper)this).Instance; + + public ISymbol Member => ((IMemberReferenceOperationWrapper)this).Member; + + public static explicit operator IFieldReferenceOperationWrapper(IMemberReferenceOperationWrapper wrapper) + => FromOperation(wrapper.WrappedOperation); + + public static implicit operator IMemberReferenceOperationWrapper(IFieldReferenceOperationWrapper wrapper) + => IMemberReferenceOperationWrapper.FromUpcast(wrapper.operation); + + public static IFieldReferenceOperationWrapper FromOperation(IOperation operation) + { + if (operation == null) + { + return default; + } + + if (!IsInstance(operation)) + { + throw new InvalidCastException($"Cannot cast '{operation.GetType().FullName}' to '{WrappedTypeName}'"); + } + + return new IFieldReferenceOperationWrapper(operation); + } + + public static bool IsInstance(IOperation operation) + { + return operation != null && LightupHelpers.CanWrapOperation(operation, WrappedType); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IMemberReferenceOperationWrapper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IMemberReferenceOperationWrapper.cs new file mode 100644 index 000000000..3c5c59941 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/IMemberReferenceOperationWrapper.cs @@ -0,0 +1,76 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Lightup +{ + using System; + using Microsoft.CodeAnalysis; + + internal readonly struct IMemberReferenceOperationWrapper : IOperationWrapper + { + internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Operations.IMemberReferenceOperation"; + private static readonly Type WrappedType; + + private static readonly Func InstanceAccessor; + private static readonly Func MemberAccessor; + + private readonly IOperation operation; + + static IMemberReferenceOperationWrapper() + { + WrappedType = WrapperHelper.GetWrappedType(typeof(IFieldReferenceOperationWrapper)); + InstanceAccessor = LightupHelpers.CreateOperationPropertyAccessor(WrappedType, nameof(Instance)); + MemberAccessor = LightupHelpers.CreateOperationPropertyAccessor(WrappedType, nameof(Member)); + } + + private IMemberReferenceOperationWrapper(IOperation operation) + { + this.operation = operation; + } + + public IOperation WrappedOperation => this.operation; + + public ITypeSymbol Type => this.WrappedOperation.Type; + + public IOperation Instance + { + get + { + return InstanceAccessor(this.WrappedOperation); + } + } + + public ISymbol Member + { + get + { + return MemberAccessor(this.WrappedOperation); + } + } + + public static IMemberReferenceOperationWrapper FromOperation(IOperation operation) + { + if (operation == null) + { + return default; + } + + if (!IsInstance(operation)) + { + throw new InvalidCastException($"Cannot cast '{operation.GetType().FullName}' to '{WrappedTypeName}'"); + } + + return new IMemberReferenceOperationWrapper(operation); + } + + public static bool IsInstance(IOperation operation) + { + return operation != null && LightupHelpers.CanWrapOperation(operation, WrappedType); + } + + internal static IMemberReferenceOperationWrapper FromUpcast(IOperation operation) + { + return new IMemberReferenceOperationWrapper(operation); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/OperationKindEx.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/OperationKindEx.cs index ac22f7fc9..16a7c1570 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/OperationKindEx.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/OperationKindEx.cs @@ -7,6 +7,11 @@ namespace StyleCop.Analyzers.Lightup internal static class OperationKindEx { + /// + /// Indicates an . + /// + public const OperationKind FieldReference = (OperationKind)26; + /// /// Indicates an . /// diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs index f58421c45..8944449ac 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/WrapperHelper.cs @@ -57,6 +57,8 @@ static WrapperHelper() builder.Add(typeof(WhenClauseSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(WhenClauseSyntaxWrapper.WrappedTypeName)); builder.Add(typeof(IArgumentOperationWrapper), codeAnalysisAssembly.GetType(IArgumentOperationWrapper.WrappedTypeName)); + builder.Add(typeof(IFieldReferenceOperationWrapper), codeAnalysisAssembly.GetType(IFieldReferenceOperationWrapper.WrappedTypeName)); + builder.Add(typeof(IMemberReferenceOperationWrapper), codeAnalysisAssembly.GetType(IMemberReferenceOperationWrapper.WrappedTypeName)); builder.Add(typeof(IObjectCreationOperationWrapper), codeAnalysisAssembly.GetType(IObjectCreationOperationWrapper.WrappedTypeName)); builder.Add(typeof(IObjectOrCollectionInitializerOperationWrapper), codeAnalysisAssembly.GetType(IObjectOrCollectionInitializerOperationWrapper.WrappedTypeName)); builder.Add(typeof(ITypeParameterObjectCreationOperationWrapper), codeAnalysisAssembly.GetType(ITypeParameterObjectCreationOperationWrapper.WrappedTypeName)); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1142ReferToTupleElementsByName.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1142ReferToTupleElementsByName.cs index 7b5d1e4e6..cd3499d65 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1142ReferToTupleElementsByName.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1142ReferToTupleElementsByName.cs @@ -26,6 +26,7 @@ internal class SA1142ReferToTupleElementsByName : DiagnosticAnalyzer private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1142MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1142Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources)); + private static readonly Action FieldReferenceOperationAction = HandleFieldReferenceOperation; private static readonly Action SimpleMemberAccessExpressionAction = HandleSimpleMemberAccessExpression; private static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink); @@ -39,7 +40,32 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(SimpleMemberAccessExpressionAction, SyntaxKind.SimpleMemberAccessExpression); + if (LightupHelpers.SupportsIOperation) + { + context.RegisterOperationAction(FieldReferenceOperationAction, OperationKindEx.FieldReference); + } + else + { + context.RegisterSyntaxNodeAction(SimpleMemberAccessExpressionAction, SyntaxKind.SimpleMemberAccessExpression); + } + } + + private static void HandleFieldReferenceOperation(OperationAnalysisContext context) + { + if (!context.SupportsTuples()) + { + return; + } + + var fieldReference = IFieldReferenceOperationWrapper.FromOperation(context.Operation); + + if (CheckFieldName(fieldReference.Field)) + { + var location = fieldReference.WrappedOperation.Syntax is MemberAccessExpressionSyntax memberAccessExpression + ? memberAccessExpression.Name.GetLocation() + : fieldReference.WrappedOperation.Syntax.GetLocation(); + context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); + } } private static void HandleSimpleMemberAccessExpression(SyntaxNodeAnalysisContext context)