Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 672183c

Browse files
committed
add ternary operator, closes #7
1 parent 0d926e6 commit 672183c

File tree

6 files changed

+323
-0
lines changed

6 files changed

+323
-0
lines changed

src/CodeCracker/CodeCracker.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
<ItemGroup>
3535
<Compile Include="ArgumentExceptionAnalyzer.cs" />
3636
<Compile Include="ArgumentExceptionCodeFixProvider.cs" />
37+
<Compile Include="TernaryOperatorCodeFixProvider.cs" />
38+
<Compile Include="TernaryOperatorAnalyzer.cs" />
3739
<Compile Include="RethrowExceptionCodeFixProvider.cs" />
3840
<Compile Include="RethrowExceptionAnalyzer.cs" />
3941
<Compile Include="Properties\AssemblyInfo.cs" />
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Linq;
5+
using System.Threading;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
namespace CodeCracker
12+
{
13+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
14+
public class TernaryOperatorAnalyzer : DiagnosticAnalyzer
15+
{
16+
public const string DiagnosticId = "CodeCracker.TernaryOperatorAnalyzer";
17+
internal const string Title = "User ternary operator";
18+
internal const string MessageFormat = "{0}";
19+
internal const string Category = "Syntax";
20+
21+
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
22+
23+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
24+
25+
public override void Initialize(AnalysisContext context)
26+
{
27+
context.RegisterSyntaxNodeAction(Analyzer, SyntaxKind.IfStatement);
28+
}
29+
30+
private void Analyzer(SyntaxNodeAnalysisContext context)
31+
{
32+
var ifStatement = context.Node as IfStatementSyntax;
33+
if (ifStatement == null) return;
34+
if (ifStatement.Else == null) return;
35+
var blockIf = ifStatement.Statement as BlockSyntax;
36+
var blockElse = ifStatement.Else.Statement as BlockSyntax;
37+
if (((blockIf ?? blockElse) == null) ||
38+
(blockIf.Statements.Count == 1 && blockElse.Statements.Count == 1))
39+
{
40+
//add diagnostic, only 1 statement for if and else
41+
//or not one direct statement, but could be one in each block, lets check
42+
var statementInsideIf = ifStatement.Statement is BlockSyntax ? ((BlockSyntax)ifStatement.Statement).Statements.Single() : ifStatement.Statement;
43+
var elseStatement = ifStatement.Else;
44+
var statementInsideElse = elseStatement.Statement is BlockSyntax ? ((BlockSyntax)elseStatement.Statement).Statements.Single() : elseStatement.Statement;
45+
if (statementInsideIf is ReturnStatementSyntax && statementInsideElse is ReturnStatementSyntax)
46+
{
47+
var diagnostic = Diagnostic.Create(Rule, ifStatement.IfKeyword.GetLocation(), "You can use a ternary operator.");
48+
context.ReportDiagnostic(diagnostic);
49+
}
50+
}
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Composition;
5+
using System.Linq;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.CodeActions;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
using Microsoft.CodeAnalysis.Rename;
14+
using Microsoft.CodeAnalysis.Text;
15+
using Microsoft.CodeAnalysis.Formatting;
16+
using Microsoft.CodeAnalysis.Simplification;
17+
18+
namespace CodeCracker
19+
{
20+
[ExportCodeFixProvider("CodeCrackerTernaryOperatorCodeFixProviderCodeFixProvider", LanguageNames.CSharp), Shared]
21+
public class TernaryOperatorCodeFixProvider : CodeFixProvider
22+
{
23+
public sealed override ImmutableArray<string> GetFixableDiagnosticIds()
24+
{
25+
return ImmutableArray.Create(TernaryOperatorAnalyzer.DiagnosticId);
26+
}
27+
28+
public sealed override FixAllProvider GetFixAllProvider()
29+
{
30+
return WellKnownFixAllProviders.BatchFixer;
31+
}
32+
33+
public sealed override async Task ComputeFixesAsync(CodeFixContext context)
34+
{
35+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
36+
var diagnostic = context.Diagnostics.First();
37+
var diagnosticSpan = diagnostic.Location.SourceSpan;
38+
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<IfStatementSyntax>().First();
39+
context.RegisterFix(CodeAction.Create("Change to ternary operator", c => MakeTernaryAsync(context.Document, declaration, c)), diagnostic);
40+
}
41+
42+
private async Task<Document> MakeTernaryAsync(Document document, IfStatementSyntax ifStatement, CancellationToken cancellationToken)
43+
{
44+
//return document;
45+
var statementInsideIf = (ReturnStatementSyntax)(ifStatement.Statement is BlockSyntax ? ((BlockSyntax)ifStatement.Statement).Statements.Single() : ifStatement.Statement);
46+
var elseStatement = ifStatement.Else;
47+
var statementInsideElse = (ReturnStatementSyntax)(elseStatement.Statement is BlockSyntax ? ((BlockSyntax)elseStatement.Statement).Statements.Single() : elseStatement.Statement);
48+
var ternary = SyntaxFactory.ParseStatement("return \{ifStatement.Condition.ToString()} ? \{statementInsideIf.Expression.ToString()} : \{statementInsideElse.Expression.ToString()};")
49+
.WithLeadingTrivia(ifStatement.GetLeadingTrivia())
50+
.WithTrailingTrivia(ifStatement.GetTrailingTrivia())
51+
.WithAdditionalAnnotations(Formatter.Annotation);
52+
var root = await document.GetSyntaxRootAsync();
53+
var newRoot = root.ReplaceNode(ifStatement, ternary);
54+
var newDocument = document.WithSyntaxRoot(newRoot);
55+
return newDocument;
56+
}
57+
}
58+
}

test/CodeCracker.Test/CodeCracker.Test.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
</ItemGroup>
100100
<ItemGroup>
101101
<Compile Include="ArgumentExceptionTests.cs" />
102+
<Compile Include="TernaryOperatorTests.cs" />
102103
<Compile Include="Verifiers\CodeFixVerifier.cs" />
103104
<Compile Include="Verifiers\DiagnosticVerifier.cs" />
104105
<Compile Include="Helpers\CodeFixVerifier.Helper.cs" />
@@ -109,6 +110,7 @@
109110
</ItemGroup>
110111
<ItemGroup>
111112
<None Include="packages.config" />
113+
<Compile Include="Verifiers\DiagnosticVerifier.Extras.cs" />
112114
</ItemGroup>
113115
<ItemGroup>
114116
<ProjectReference Include="..\..\src\CodeCracker\CodeCracker.csproj">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CodeFixes;
3+
using Microsoft.CodeAnalysis.Diagnostics;
4+
using System;
5+
using TestHelper;
6+
using CodeCracker;
7+
using Xunit;
8+
9+
namespace CodeCracker.Test
10+
{
11+
public class TernaryOperatorTests : CodeFixVerifier
12+
{
13+
private const string source = @"
14+
namespace ConsoleApplication1
15+
{
16+
class TypeName
17+
{
18+
public int Foo()
19+
{
20+
var something = true;
21+
if (something)
22+
return 1;
23+
else
24+
return 2;
25+
}
26+
}
27+
}";
28+
29+
[Fact]
30+
public void WhenUsingIfWithoutElseAnalyzerDoesNotCreateDiagnostic()
31+
{
32+
const string sourceWithoutElse = @"
33+
namespace ConsoleApplication1
34+
{
35+
class TypeName
36+
{
37+
public int Foo()
38+
{
39+
var something = true;
40+
if (something)
41+
return 1;
42+
}
43+
}
44+
}";
45+
VerifyCSharpHasNoDiagnostics(sourceWithoutElse);
46+
}
47+
48+
[Fact]
49+
public void WhenUsingIfWithElseButWithBlockWith2StatementsOnIfAnalyzerDoesNotCreateDiagnostic()
50+
{
51+
const string sourceWithoutElse = @"
52+
namespace ConsoleApplication1
53+
{
54+
class TypeName
55+
{
56+
public int Foo()
57+
{
58+
var something = true;
59+
if (something)
60+
{
61+
string a = null;
62+
return 1;
63+
}
64+
else
65+
{
66+
return 2;
67+
}
68+
}
69+
}
70+
}";
71+
VerifyCSharpHasNoDiagnostics(sourceWithoutElse);
72+
}
73+
74+
[Fact]
75+
public void WhenUsingIfWithElseButWithBlockWith2StatementsOnElseAnalyzerDoesNotCreateDiagnostic()
76+
{
77+
const string sourceWithoutElse = @"
78+
namespace ConsoleApplication1
79+
{
80+
class TypeName
81+
{
82+
public int Foo()
83+
{
84+
var something = true;
85+
if (something)
86+
{
87+
return 1;
88+
}
89+
else
90+
{
91+
string a = null;
92+
return 2;
93+
}
94+
}
95+
}
96+
}";
97+
VerifyCSharpHasNoDiagnostics(sourceWithoutElse);
98+
}
99+
100+
[Fact]
101+
public void WhenUsingIfWithElseButWithoutReturnOnElseAnalyzerDoesNotCreateDiagnostic()
102+
{
103+
const string sourceWithoutElse = @"
104+
namespace ConsoleApplication1
105+
{
106+
class TypeName
107+
{
108+
public int Foo()
109+
{
110+
var something = true;
111+
if (something)
112+
{
113+
return 2;
114+
}
115+
else
116+
{
117+
string a = null;
118+
}
119+
return 1;
120+
}
121+
}
122+
}";
123+
VerifyCSharpHasNoDiagnostics(sourceWithoutElse);
124+
}
125+
126+
[Fact]
127+
public void WhenUsingIfWithElseButWithoutReturnOnIfAnalyzerDoesNotCreateDiagnostic()
128+
{
129+
const string sourceWithoutElse = @"
130+
namespace ConsoleApplication1
131+
{
132+
class TypeName
133+
{
134+
public int Foo()
135+
{
136+
var something = true;
137+
if (something)
138+
{
139+
string a = null;
140+
}
141+
else
142+
{
143+
return 2;
144+
}
145+
return 1;
146+
}
147+
}
148+
}";
149+
VerifyCSharpHasNoDiagnostics(sourceWithoutElse);
150+
}
151+
152+
[Fact]
153+
public void WhenUsingIfAndElseWithDirectReturnAnalyzerCreatesDiagnostic()
154+
{
155+
var expected = new DiagnosticResult
156+
{
157+
Id = TernaryOperatorAnalyzer.DiagnosticId,
158+
Message = "You can use a ternary operator.",
159+
Severity = DiagnosticSeverity.Error,
160+
Locations =
161+
new[] {
162+
new DiagnosticResultLocation("Test0.cs", 9, 17)
163+
}
164+
};
165+
166+
VerifyCSharpDiagnostic(source, expected);
167+
}
168+
169+
[Fact]
170+
public void WhenUsingIfAndElseWithDirectReturnChangeToTernaryFix()
171+
{
172+
173+
var fixtest = @"
174+
namespace ConsoleApplication1
175+
{
176+
class TypeName
177+
{
178+
public int Foo()
179+
{
180+
var something = true;
181+
return something ? 1 : 2;
182+
}
183+
}
184+
}";
185+
VerifyCSharpFix(source, fixtest, 0);
186+
}
187+
188+
protected override CodeFixProvider GetCSharpCodeFixProvider()
189+
{
190+
return new TernaryOperatorCodeFixProvider();
191+
}
192+
193+
protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer()
194+
{
195+
return new TernaryOperatorAnalyzer();
196+
}
197+
}
198+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace TestHelper
2+
{
3+
public abstract partial class DiagnosticVerifier
4+
{
5+
protected void VerifyCSharpHasNoDiagnostics(string source)
6+
{
7+
VerifyCSharpDiagnostic(source);
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)