diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalysis.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalysis.cs new file mode 100644 index 00000000..067d26ec --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalysis.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using NSubstitute.Analyzers.CSharp.Extensions; +using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; + +namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers +{ + public class SubstituteAnalysis : AbstractSubstituteAnalysis + { + protected override IList GetInvocationArguments(InvocationExpressionSyntax invocationExpression) + { + return invocationExpression.ArgumentList.Arguments.Select(syntax => syntax).ToList(); + } + + // TODO get rid of casts + protected override IList GetParameterExpressionsFromArrayArgument(SyntaxNode syntaxNode) + { + return ((ArgumentSyntax)syntaxNode).Expression.GetParameterExpressionsFromArrayArgument() + .Select(syntax => syntax).ToList(); + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalyzer.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalyzer.cs index 4d17e06f..01be91cd 100644 --- a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalyzer.cs +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using NSubstitute.Analyzers.CSharp.Extensions; using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers @@ -11,6 +12,9 @@ namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class SubstituteAnalyzer : AbstractSubstituteAnalyzer { + private readonly SubstituteAnalysis _substituteAnalysis = new SubstituteAnalysis(); + private readonly SubstituteConstructorMatcher _substituteConstructorMatcher = new SubstituteConstructorMatcher(); + public SubstituteAnalyzer() : base(new DiagnosticDescriptorsProvider()) { @@ -25,12 +29,18 @@ protected override IEnumerable GetTypeOfLikeExpressions(IList< protected override IEnumerable GetArrayInitializerArguments(InvocationExpressionSyntax invocationExpressionSyntax) { - throw new System.NotImplementedException(); + return invocationExpressionSyntax.ArgumentList.Arguments.Skip(1).First().Expression + .GetParameterExpressionsFromArrayArgument(); } protected override ConstructorContext CollectConstructorContext(SubstituteContext substituteContext, ITypeSymbol proxyTypeSymbol) { - throw new System.NotImplementedException(); + return _substituteAnalysis.CollectConstructorContext(substituteContext, proxyTypeSymbol); + } + + protected override bool MatchesInvocation(Compilation semanticModelCompilation, IMethodSymbol ctor, IList constructorContextInvocationParameters) + { + return _substituteConstructorMatcher.MatchesInvocation(semanticModelCompilation, ctor, constructorContextInvocationParameters); } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs new file mode 100644 index 00000000..ac19d88f --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; + +namespace NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers +{ + public class SubstituteConstructorMatcher : AbstractSubstituteConstructorMatcher + { + protected override bool ClasifyConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) + { + var conversion = compilation.ClassifyConversion(source, destination); + + return conversion.Exists && conversion.IsImplicit; + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/NSubstitute.Analyzers.CSharp/Extensions/ExpressionSyntaxExtensions.cs new file mode 100644 index 00000000..1e5ace0f --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/Extensions/ExpressionSyntaxExtensions.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace NSubstitute.Analyzers.CSharp.Extensions +{ + public static class ExpressionSyntaxExtensions + { + private static readonly IList EmptyExpressionList = new ExpressionSyntax[0]; + + public static IList GetParameterExpressionsFromArrayArgument(this ExpressionSyntax expression) + { + InitializerExpressionSyntax initializer = null; + switch (expression.Kind()) + { + case SyntaxKind.ArrayCreationExpression: + initializer = ((ArrayCreationExpressionSyntax)expression).Initializer; + break; + case SyntaxKind.ImplicitArrayCreationExpression: + initializer = ((ImplicitArrayCreationExpressionSyntax)expression).Initializer; + break; + default: + return null; + } + + if (initializer == null) + { + return EmptyExpressionList; + } + + return initializer.Expressions.ToList(); + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractConstructorArgumentsForInterfaceCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractConstructorArgumentsForInterfaceCodeFixProvider.cs index ecbc2ea6..5494ce46 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractConstructorArgumentsForInterfaceCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractConstructorArgumentsForInterfaceCodeFixProvider.cs @@ -1,6 +1,5 @@ using System.Collections.Immutable; using System.Linq; -using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -10,12 +9,7 @@ namespace NSubstitute.Analyzers.Shared.CodeFixProviders { -#if CSHARP - [ExportCodeFixProvider(LanguageNames.CSharp)] -#elif VISUAL_BASIC - [ExportCodeFixProvider(LanguageNames.VisualBasic)] -#endif - public class AbstractConstructorArgumentsForInterfaceCodeFixProvider : CodeFixProvider + internal class AbstractConstructorArgumentsForInterfaceCodeFixProvider : CodeFixProvider { public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractForPartsOfUsedForUnsupportedTypeCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractForPartsOfUsedForUnsupportedTypeCodeFixProvider.cs index f27a113f..939c8705 100644 --- a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractForPartsOfUsedForUnsupportedTypeCodeFixProvider.cs +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractForPartsOfUsedForUnsupportedTypeCodeFixProvider.cs @@ -5,22 +5,10 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -#if CSHARP -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -#elif VISUAL_BASIC -using Microsoft.CodeAnalysis.VisualBasic.Syntax; -using static Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory; -#endif namespace NSubstitute.Analyzers.Shared.CodeFixProviders { -#if CSHARP - [ExportCodeFixProvider(LanguageNames.CSharp)] -#elif VISUAL_BASIC - [ExportCodeFixProvider(LanguageNames.VisualBasic)] -#endif - public class AbstractForPartsOfUsedForUnsupportedTypeCodeFixProvider : CodeFixProvider + internal class AbstractForPartsOfUsedForUnsupportedTypeCodeFixProvider : CodeFixProvider { // no completed task in .net standard private static Task _completedTask = Task.FromResult(1); diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/SubstituteAnalysis.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalysis.cs similarity index 98% rename from src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/SubstituteAnalysis.cs rename to src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalysis.cs index 2293e876..006c5c65 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/SubstituteAnalysis.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalysis.cs @@ -6,7 +6,7 @@ namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers { // TODO remove duplication - public abstract class SubstituteAnalysis + public abstract class AbstractSubstituteAnalysis where TInvocationExpression : SyntaxNode { public ConstructorContext CollectConstructorContext(SubstituteContext substituteContext, ITypeSymbol proxyTypeSymbol) diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalyzer.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalyzer.cs index 20f7bd33..30e865db 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalyzer.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteAnalyzer.cs @@ -45,6 +45,8 @@ public override void Initialize(AnalysisContext context) protected abstract ConstructorContext CollectConstructorContext(SubstituteContext substituteContext, ITypeSymbol proxyTypeSymbol); + protected abstract bool MatchesInvocation(Compilation semanticModelCompilation, IMethodSymbol ctor, IList constructorContextInvocationParameters); + private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext) { var invocationExpression = (TInvocationExpressionSyntax)syntaxNodeContext.Node; @@ -277,7 +279,7 @@ private bool AnalyzeConstructorInvocation(SubstituteContext - SubstituteConstructorMatcher.MatchesInvocation( + MatchesInvocation( substituteContext.SyntaxNodeAnalysisContext.SemanticModel.Compilation, ctor, constructorContext.InvocationParameters) == false)) { diff --git a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteConstructorMatcher.cs similarity index 81% rename from src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs rename to src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteConstructorMatcher.cs index 6070d6e2..b78fd368 100644 --- a/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs +++ b/src/NSubstitute.Analyzers.Shared/DiagnosticAnalyzers/AbstractSubstituteConstructorMatcher.cs @@ -5,10 +5,10 @@ namespace NSubstitute.Analyzers.Shared.DiagnosticAnalyzers { // TODO refactor - public static class SubstituteConstructorMatcher + public abstract class AbstractSubstituteConstructorMatcher { // even though conversion returns that key -> value is convertible it fails on the runtime when runninig through substitute creation - private static readonly Dictionary WellKnownUnsupportedConversions = + protected virtual Dictionary WellKnownUnsupportedConversions { get; } = new Dictionary { [SpecialType.System_Int16] = SpecialType.System_Decimal, @@ -19,7 +19,7 @@ public static class SubstituteConstructorMatcher [SpecialType.System_UInt64] = SpecialType.System_Decimal }; - private static readonly Dictionary> WellKnownSupportedConversions = + protected virtual Dictionary> WellKnownSupportedConversions { get; } = new Dictionary> { [SpecialType.System_Char] = new HashSet @@ -34,7 +34,7 @@ public static class SubstituteConstructorMatcher } }; - public static bool MatchesInvocation(Compilation compilation, IMethodSymbol methodSymbol, IList invocationParameters) + public bool MatchesInvocation(Compilation compilation, IMethodSymbol methodSymbol, IList invocationParameters) { if (methodSymbol.Parameters.Length != invocationParameters.Count) { @@ -46,7 +46,9 @@ public static bool MatchesInvocation(Compilation compilation, IMethodSymbol meth .Count() == methodSymbol.Parameters.Length; } - private static bool IsConvertible(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) + protected abstract bool ClasifyConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination); + + private bool IsConvertible(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) { if (source == null || source.Equals(destination)) { diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalysis.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalysis.cs new file mode 100644 index 00000000..0f892b0a --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalysis.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; +using NSubstitute.Analyzers.VisualBasic.Extensions; + +namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers +{ + public class SubstituteAnalysis : AbstractSubstituteAnalysis + { + protected override IList GetInvocationArguments(InvocationExpressionSyntax invocationExpression) + { + return invocationExpression.ArgumentList.Arguments.Select(syntax => syntax).ToList(); + } + + // TODO get rid of casts + protected override IList GetParameterExpressionsFromArrayArgument(SyntaxNode syntaxNode) + { + return ((ArgumentSyntax)syntaxNode).GetExpression().GetParameterExpressionsFromArrayArgument() + .Select(syntax => syntax).ToList(); + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalyzer.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalyzer.cs index 9c57fce3..51f22eef 100644 --- a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalyzer.cs +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteAnalyzer.cs @@ -5,12 +5,16 @@ using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.CodeAnalysis.VisualBasic.Syntax; using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; +using NSubstitute.Analyzers.VisualBasic.Extensions; namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] internal class SubstituteAnalyzer : AbstractSubstituteAnalyzer { + private readonly SubstituteAnalysis _substituteAnalysis = new SubstituteAnalysis(); + private readonly SubstituteConstructorMatcher _substituteConstructorMatcher = new SubstituteConstructorMatcher(); + public SubstituteAnalyzer() : base(new DiagnosticDescriptorsProvider()) { @@ -25,12 +29,18 @@ protected override IEnumerable GetTypeOfLikeExpressions(IList< protected override IEnumerable GetArrayInitializerArguments(InvocationExpressionSyntax invocationExpressionSyntax) { - throw new System.NotImplementedException(); + return invocationExpressionSyntax.ArgumentList.Arguments.Skip(1).First().GetExpression() + .GetParameterExpressionsFromArrayArgument(); } protected override ConstructorContext CollectConstructorContext(SubstituteContext substituteContext, ITypeSymbol proxyTypeSymbol) { - throw new System.NotImplementedException(); + return _substituteAnalysis.CollectConstructorContext(substituteContext, proxyTypeSymbol); + } + + protected override bool MatchesInvocation(Compilation semanticModelCompilation, IMethodSymbol ctor, IList constructorContextInvocationParameters) + { + return _substituteConstructorMatcher.MatchesInvocation(semanticModelCompilation, ctor, constructorContextInvocationParameters); } } } \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs new file mode 100644 index 00000000..bdfd521f --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/DiagnosticAnalyzers/SubstituteConstructorMatcher.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers; + +namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers +{ + public class SubstituteConstructorMatcher : AbstractSubstituteConstructorMatcher + { + protected override bool ClasifyConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination) + { + var conversion = compilation.ClassifyConversion(source, destination); + + return conversion.Exists && conversion.IsNarrowing; + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.VisualBasic/Extensions/ExpressionSyntaxExtensions.cs b/src/NSubstitute.Analyzers.VisualBasic/Extensions/ExpressionSyntaxExtensions.cs new file mode 100644 index 00000000..3455e792 --- /dev/null +++ b/src/NSubstitute.Analyzers.VisualBasic/Extensions/ExpressionSyntaxExtensions.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace NSubstitute.Analyzers.VisualBasic.Extensions +{ + public static class ExpressionSyntaxExtensions + { + public static IList GetParameterExpressionsFromArrayArgument(this ExpressionSyntax expression) + { + switch (expression.Kind()) + { + case SyntaxKind.ArrayCreationExpression: + var initializer = ((ArrayCreationExpressionSyntax)expression).Initializer; + return initializer.Initializers.ToList(); + case SyntaxKind.CollectionInitializer: + return ((CollectionInitializerSyntax)expression).Initializers.ToList(); + default: + return null; + } + } + } +} \ No newline at end of file