Skip to content

Commit

Permalink
[GH-18] - vb implementation of When...Do analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Jul 1, 2018
1 parent b62db62 commit a0dab7e
Show file tree
Hide file tree
Showing 17 changed files with 1,008 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,8 @@ private void AnalyzeInvocation(SyntaxNodeAnalysisContext syntaxNodeContext)
return;
}

var typeSymbol = methodSymbol.TypeArguments.First();

var expressionsForAnalysys = GetExpressionsForAnalysys(syntaxNodeContext, methodSymbol, invocationExpression);

var typeSymbol = methodSymbol.TypeArguments.FirstOrDefault() ?? methodSymbol.ReceiverType;
foreach (var analysedSyntax in expressionsForAnalysys)
{
var symbolInfo = syntaxNodeContext.SemanticModel.GetSymbolInfo(analysedSyntax);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
using NSubstitute.Analyzers.Shared.DiagnosticAnalyzers;

namespace NSubstitute.Analyzers.VisualBasic.DiagnosticAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
internal class NonVirtualSetupWhenAnalyzer : AbstractNonVirtualWhenAnalyzer<SyntaxKind, InvocationExpressionSyntax>
{
public NonVirtualSetupWhenAnalyzer()
: base(new DiagnosticDescriptorsProvider())
{
}

protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression;

protected override IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocationExpressionSyntax)
{
var argumentListArguments = invocationExpressionSyntax.ArgumentList.Arguments;
var argumentSyntax = methodSymbol.MethodKind == MethodKind.ReducedExtension ? argumentListArguments.First() : argumentListArguments.Skip(1).First();
return GetExpressionsForAnalysys(syntaxNodeAnalysisContext, argumentSyntax.GetExpression());
}

private IEnumerable<SyntaxNode> GetExpressionsForAnalysys(SyntaxNodeAnalysisContext syntaxNodeContext, SyntaxNode syntax)
{
SyntaxNode body = null;
switch (syntax)
{
case SingleLineLambdaExpressionSyntax _:
case ExpressionStatementSyntax _:
case LocalDeclarationStatementSyntax _:
case AssignmentStatementSyntax _:
body = syntax;
break;
case MultiLineLambdaExpressionSyntax simpleLambdaExpressionSyntax:
foreach (var syntaxNode in IterateStatements(simpleLambdaExpressionSyntax.Statements))
{
yield return syntaxNode;
}

break;
case MethodBlockSyntax methodBlockSyntax:
foreach (var syntaxNode in IterateStatements(methodBlockSyntax.Statements))
{
yield return syntaxNode;
}

break;
case UnaryExpressionSyntax unaryExpressionSyntax:
foreach (var syntaxNode in GetExpressionsForAnalysys(syntaxNodeContext, unaryExpressionSyntax.Operand))
{
yield return syntaxNode;
}

break;
case IdentifierNameSyntax identifierNameSyntax:
var symbol = syntaxNodeContext.SemanticModel.GetSymbolInfo(identifierNameSyntax);
if (symbol.Symbol != null && symbol.Symbol.Locations.Any())
{
var location = symbol.Symbol.Locations.First();
var syntaxNode = location.SourceTree.GetRoot().FindNode(location.SourceSpan);

SyntaxNode innerNode = null;
if (syntaxNode is MethodStatementSyntax methodStatementSyntax)
{
innerNode = methodStatementSyntax.Parent;
}

innerNode = innerNode ?? syntaxNode;
foreach (var expressionsForAnalysy in GetExpressionsForAnalysys(syntaxNodeContext, innerNode))
{
yield return expressionsForAnalysy;
}
}

break;
}

if (body == null)
{
yield break;
}

var memberAccessExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.SimpleMemberAccessExpression));
var invocationExpressions = body.DescendantNodes().Where(node => node.IsKind(SyntaxKind.InvocationExpression));

// rather ugly but prevents reporting two times the same thing
// as VB syntax is based on statements, you can't access body of method directly
if (invocationExpressions.Any())
{
foreach (var invocationExpression in invocationExpressions)
{
yield return invocationExpression;
}
}
else if (memberAccessExpressions.Any())
{
foreach (var memberAccessExpression in memberAccessExpressions)
{
yield return memberAccessExpression;
}
}

IEnumerable<SyntaxNode> IterateStatements(IEnumerable<StatementSyntax> statements)
{
return statements.SelectMany(statement => GetExpressionsForAnalysys(syntaxNodeContext, statement));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
<TargetFramework>netstandard1.3</TargetFramework>
<PackageTargetFallback>portable45-net45+win8</PackageTargetFallback>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
Expand All @@ -22,7 +22,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Update="NetStandard.Library" Version="$(NetStandardImplicitPackageVersion)" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="1.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="2.8.2" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />
Expand Down
33 changes: 30 additions & 3 deletions src/NSubstitute.Analyzers.VisualBasic/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/NSubstitute.Analyzers.VisualBasic/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -248,5 +248,17 @@
<data name="SubstituteConstructorArgumentsForDelegateMemberTitle" xml:space="preserve">
<value>Can not provide constructor arguments when substituting for a delegate.</value>
<comment>The title of the diagnostic.</comment>
</data>
<data name="NonVirtualWhenSetupSpecificationDescription" xml:space="preserve">
<value>Non-overridable members can not be intercepted.</value>
<comment>An optional longer localizable description of the diagnostic.</comment>
</data>
<data name="NonVirtualWhenSetupSpecificationMessageFormat" xml:space="preserve">
<value>Member {0} can not be intercepted. Only interface members and overrideable, overriding, and must override members can be intercepted.</value>
<comment>The format-able message the diagnostic displays.</comment>
</data>
<data name="NonVirtualWhenSetupSpecificationTitle" xml:space="preserve">
<value>Non-overridable setup specification</value>
<comment>The title of the diagnostic.</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.NonVirtualS
{
public abstract class NonVirtualSetupWhenDiagnosticVerifier : CSharpDiagnosticVerifier, INonVirtualSetupWhenDiagnosticVerifier
{
public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualMethod(string whenAction, int expectedColumn);
public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualMethod(string whenAction, int expectedLine, int expectedColumn);

public abstract Task ReportsNoDiagnostics_WhenSettingValueForVirtualMethod(string whenAction);

public abstract Task ReportsNoDiagnostics_WhenSettingValueForNonSealedOverrideMethod(string whenAction);

public abstract Task ReportsNoDiagnostics_WhenSettingValueForDelegate(string whenAction);

public abstract Task ReportsDiagnostics_WhenSettingValueForSealedOverrideMethod(string whenAction, int expectedColumn);
public abstract Task ReportsDiagnostics_WhenSettingValueForSealedOverrideMethod(string whenAction, int expectedLine, int expectedColumn);

public abstract Task ReportsNoDiagnostics_WhenSettingValueForAbstractMethod(string whenAction);

Expand All @@ -31,11 +31,11 @@ public abstract class NonVirtualSetupWhenDiagnosticVerifier : CSharpDiagnosticVe

public abstract Task ReportsNoDiagnostics_WhenUsingUnfortunatelyNamedMethod(string whenAction);

public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualProperty(string whenAction, int expectedColumn);
public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualProperty(string whenAction, int expectedLine, int expectedColumn);

public abstract Task ReportsNoDiagnostics_WhenSettingValueForVirtualProperty(string whenAction);

public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualIndexer(string whenAction, int expectedColumn);
public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualIndexer(string whenAction, int expectedLine, int expectedColumn);

public abstract Task ReportsDiagnostics_WhenSettingValueForNonVirtualMember_InLocalFunction();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ namespace NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.NonVirtualS
public class WhenAsExtensionMethodTests : NonVirtualSetupWhenDiagnosticVerifier
{
[Theory]
[InlineData("sub => sub.Bar()", 36)]
[InlineData("delegate(Foo sub) { sub.Bar(); }", 49)]
[InlineData("sub => { sub.Bar(); }", 38)]
public override async Task ReportsDiagnostics_WhenSettingValueForNonVirtualMethod(string whenAction, int expectedColumn)
[InlineData("sub => sub.Bar()", 19, 36)]
[InlineData("delegate(Foo sub) { sub.Bar(); }", 19, 49)]
[InlineData("sub => { sub.Bar(); }", 19, 38)]
public override async Task ReportsDiagnostics_WhenSettingValueForNonVirtualMethod(string whenAction, int expectedLine, int expectedColumn)
{
var source = $@"using NSubstitute;
Expand Down Expand Up @@ -43,7 +43,7 @@ public void Test()
Message = "Member Bar can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.",
Locations = new[]
{
new DiagnosticResultLocation(19, expectedColumn)
new DiagnosticResultLocation(expectedLine, expectedColumn)
}
};

Expand Down Expand Up @@ -142,10 +142,10 @@ public void Test()
}

[Theory]
[InlineData("sub => sub.Bar()", 36)]
[InlineData("delegate(Foo2 sub) { sub.Bar(); }", 50)]
[InlineData("sub => { sub.Bar(); }", 38)]
public override async Task ReportsDiagnostics_WhenSettingValueForSealedOverrideMethod(string whenAction, int expectedColumn)
[InlineData("sub => sub.Bar()", 24, 36)]
[InlineData("delegate(Foo2 sub) { sub.Bar(); }", 24, 50)]
[InlineData("sub => { sub.Bar(); }", 24, 38)]
public override async Task ReportsDiagnostics_WhenSettingValueForSealedOverrideMethod(string whenAction, int expectedLine, int expectedColumn)
{
var source = $@"using NSubstitute;
Expand Down Expand Up @@ -182,7 +182,7 @@ public void Test()
Message = "Member Bar can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.",
Locations = new[]
{
new DiagnosticResultLocation(24, expectedColumn)
new DiagnosticResultLocation(expectedLine, expectedColumn)
}
};

Expand Down Expand Up @@ -395,9 +395,9 @@ public void Test()
}

[Theory]
[InlineData("sub => { var x = sub.Bar; }", 46)]
[InlineData("delegate(Foo sub) { var x = sub.Bar; }", 57)]
public override async Task ReportsDiagnostics_WhenSettingValueForNonVirtualProperty(string whenAction, int expectedColumn)
[InlineData("sub => { var x = sub.Bar; }", 16, 46)]
[InlineData("delegate(Foo sub) { var x = sub.Bar; }", 16, 57)]
public override async Task ReportsDiagnostics_WhenSettingValueForNonVirtualProperty(string whenAction, int expectedLine, int expectedColumn)
{
var source = $@"using NSubstitute;
Expand Down Expand Up @@ -425,7 +425,7 @@ public void Test()
Message = "Member Bar can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.",
Locations = new[]
{
new DiagnosticResultLocation(16, expectedColumn)
new DiagnosticResultLocation(expectedLine, expectedColumn)
}
};

Expand Down Expand Up @@ -461,9 +461,9 @@ public void Test()
}

[Theory]
[InlineData("sub => { var x = sub[1]; }", 46)]
[InlineData("delegate(Foo sub) { var x = sub[1]; }", 57)]
public override async Task ReportsDiagnostics_WhenSettingValueForNonVirtualIndexer(string whenAction, int expectedColumn)
[InlineData("sub => { var x = sub[1]; }", 16, 46)]
[InlineData("delegate(Foo sub) { var x = sub[1]; }", 16, 57)]
public override async Task ReportsDiagnostics_WhenSettingValueForNonVirtualIndexer(string whenAction, int expectedLine, int expectedColumn)
{
var source = $@"using NSubstitute;
Expand Down Expand Up @@ -491,7 +491,7 @@ public void Test()
Message = "Member this[] can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.",
Locations = new[]
{
new DiagnosticResultLocation(16, expectedColumn)
new DiagnosticResultLocation(expectedLine, expectedColumn)
}
};

Expand Down
Loading

0 comments on commit a0dab7e

Please sign in to comment.