Skip to content

Commit 1d77ddb

Browse files
authored
Features/codefix 9 cs4016 (#20)
* initial codefix + test * feature works
1 parent 4bdd71d commit 1d77ddb

File tree

4 files changed

+243
-0
lines changed

4 files changed

+243
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2020 Andreas Müller
2+
// This file is a part of Amusoft.CodeAnalysis.Analyzers and is licensed under Apache 2.0
3+
// See https://github.com/taori/Amusoft.CodeAnalysis.Analyzers/blob/master/LICENSE for details
4+
5+
using System.Threading.Tasks;
6+
using Microsoft.CodeAnalysis.CSharp.Testing.MSTest;
7+
using Microsoft.CodeAnalysis.Testing;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using static Microsoft.CodeAnalysis.Testing.DiagnosticResult;
10+
using Verifier =
11+
Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeFixVerifier<Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer,
12+
Amusoft.CodeAnalysis.Analyzers.CS4016.FixByUnwrappingTaskFromResult>;
13+
14+
namespace Amusoft.CodeAnalysis.Analyzers.Test.Tests.CS4016
15+
{
16+
[TestClass]
17+
public class FixByUnwrappingTaskFromResultTests
18+
{
19+
[TestMethod]
20+
public async Task EmptySourceNoAction()
21+
{
22+
await Verifier.VerifyCodeFixAsync(string.Empty, string.Empty);
23+
}
24+
25+
[TestMethod]
26+
public async Task FixMethod()
27+
{
28+
var test = @"
29+
using System;
30+
using System.IO;
31+
using System.Linq;
32+
using System.Threading.Tasks;
33+
34+
public class TestClass
35+
{
36+
private string _workspaceFile;
37+
38+
public async Task<Configuration[]> LoadStorageContentAsync()
39+
{
40+
var fileInfo = new FileInfo(_workspaceFile);
41+
if (!fileInfo.Exists)
42+
{
43+
Log.Error($""No configuration storage located at {fileInfo.FullName}."");
44+
return Task.FromResult(Array.Empty<Configuration>());
45+
}
46+
47+
using (var stream = new StreamReader(new FileStream(fileInfo.FullName, FileMode.Open)))
48+
{
49+
try
50+
{
51+
var storage = new Storage();
52+
return Task.FromResult(storage.Configurations.ToArray());
53+
}
54+
catch (Exception e)
55+
{
56+
Log.Error(e);
57+
return Task.FromResult(Array.Empty<Configuration>());
58+
}
59+
}
60+
}
61+
62+
public class Log
63+
{
64+
public static void Error(Exception p0)
65+
{
66+
throw new NotImplementedException();
67+
}
68+
69+
public static void Error(string p0)
70+
{
71+
throw new NotImplementedException();
72+
}
73+
}
74+
75+
public class Storage
76+
{
77+
public Configuration[] Configurations { get; set; }
78+
}
79+
80+
public class Configuration
81+
{
82+
public Guid Id { get; set; }
83+
public string ConfigurationName { get; set; }
84+
}
85+
}";
86+
87+
var fixtest = @"
88+
using System;
89+
using System.IO;
90+
using System.Linq;
91+
using System.Threading.Tasks;
92+
93+
public class TestClass
94+
{
95+
private string _workspaceFile;
96+
97+
public async Task<Configuration[]> LoadStorageContentAsync()
98+
{
99+
var fileInfo = new FileInfo(_workspaceFile);
100+
if (!fileInfo.Exists)
101+
{
102+
Log.Error($""No configuration storage located at {fileInfo.FullName}."");
103+
return Array.Empty<Configuration>();
104+
}
105+
106+
using (var stream = new StreamReader(new FileStream(fileInfo.FullName, FileMode.Open)))
107+
{
108+
try
109+
{
110+
var storage = new Storage();
111+
return storage.Configurations.ToArray();
112+
}
113+
catch (Exception e)
114+
{
115+
Log.Error(e);
116+
return Array.Empty<Configuration>();
117+
}
118+
}
119+
}
120+
121+
public class Log
122+
{
123+
public static void Error(Exception p0)
124+
{
125+
throw new NotImplementedException();
126+
}
127+
128+
public static void Error(string p0)
129+
{
130+
throw new NotImplementedException();
131+
}
132+
}
133+
134+
public class Storage
135+
{
136+
public Configuration[] Configurations { get; set; }
137+
}
138+
139+
public class Configuration
140+
{
141+
public Guid Id { get; set; }
142+
public string ConfigurationName { get; set; }
143+
}
144+
}";
145+
var diagnostics = new[]
146+
{
147+
// Test0.cs(17,11): error CS4016: Since this is an async method, the return expression must be of type 'TestClass.Configuration[]' rather than 'Task<TestClass.Configuration[]>'
148+
DiagnosticResult.CompilerError("CS4016").WithSpan(17, 11, 17, 56)
149+
.WithArguments("TestClass.Configuration[]"),
150+
// Test0.cs(25,12): error CS4016: Since this is an async method, the return expression must be of type 'TestClass.Configuration[]' rather than 'Task<TestClass.Configuration[]>'
151+
DiagnosticResult.CompilerError("CS4016").WithSpan(25, 12, 25, 61)
152+
.WithArguments("TestClass.Configuration[]"),
153+
// Test0.cs(30,12): error CS4016: Since this is an async method, the return expression must be of type 'TestClass.Configuration[]' rather than 'Task<TestClass.Configuration[]>'
154+
DiagnosticResult.CompilerError("CS4016").WithSpan(30, 12, 30, 57)
155+
.WithArguments("TestClass.Configuration[]")
156+
};
157+
158+
await Verifier.VerifyCodeFixAsync(test, diagnostics, fixtest);
159+
}
160+
}
161+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2020 Andreas Müller
2+
// This file is a part of Amusoft.CodeAnalysis.Analyzers and is licensed under Apache 2.0
3+
// See https://github.com/taori/Amusoft.CodeAnalysis.Analyzers/blob/master/LICENSE for details
4+
5+
using System;
6+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
7+
using System.Composition;
8+
using System.IO;
9+
using System.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Amusoft.CodeAnalysis.Analyzers.Shared;
13+
using Microsoft.CodeAnalysis;
14+
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.CSharp;
16+
using Microsoft.CodeAnalysis.CSharp.Syntax;
17+
using Microsoft.CodeAnalysis.Editing;
18+
using Microsoft.CodeAnalysis.Formatting;
19+
using Microsoft.CodeAnalysis.Simplification;
20+
using Microsoft.CodeAnalysis.Text;
21+
22+
namespace Amusoft.CodeAnalysis.Analyzers.CS4016
23+
{
24+
[ExportCodeFixProvider(LanguageNames.CSharp,
25+
Name = "Amusoft.CodeAnalysis.Analyzers.CS4016-FixByUnwrappingTaskFromResult"), Shared]
26+
public class FixByUnwrappingTaskFromResult : SingleDiagnosticDocumentCodeFixProviderBase
27+
{
28+
/// <inheritdoc />
29+
protected override string DiagnosticId { get; } = "CS4016";
30+
31+
/// <inheritdoc />
32+
protected override string GetEquivalenceKey(SyntaxNode rootNode)
33+
{
34+
return $"{DiagnosticId}-FixByUnwrappingTaskFromResult";
35+
}
36+
37+
/// <inheritdoc />
38+
protected override string GetTitle(SyntaxNode rootNode)
39+
{
40+
return Resources.MessageFormat_CS4016_FixByUnwrappingTaskFromResult;
41+
}
42+
43+
/// <inheritdoc />
44+
protected override async Task<Document> GetFixedDiagnosticAsync(Document document, TextSpan span,
45+
CancellationToken cancellationToken)
46+
{
47+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken)
48+
.ConfigureAwait(false);
49+
var diagnosticNode = syntaxRoot.FindNode(span);
50+
51+
if (diagnosticNode is InvocationExpressionSyntax invocationExpression
52+
&& invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression
53+
&& memberAccessExpression.Name.Identifier.Text.Equals("FromResult")
54+
&& memberAccessExpression.Expression is IdentifierNameSyntax nameSyntax
55+
&& nameSyntax.Identifier.Text.Equals("Task"))
56+
{
57+
var argumentSyntax = invocationExpression.ArgumentList.Arguments.FirstOrDefault();
58+
if (argumentSyntax != null)
59+
{
60+
var replaced = syntaxRoot.ReplaceNode(invocationExpression,
61+
argumentSyntax.Expression);
62+
63+
return document.WithSyntaxRoot(replaced);
64+
}
65+
}
66+
67+
return document;
68+
}
69+
}
70+
}

src/Amusoft.CodeAnalysis.Analyzers/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Amusoft.CodeAnalysis.Analyzers/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@
201201
<data name="MessageFormat_CS1998_FixByWrappingInTaskResult" xml:space="preserve">
202202
<value>Wrap method exit points in Task.FromResult()</value>
203203
</data>
204+
<data name="MessageFormat_CS4016_FixByUnwrappingTaskFromResult" xml:space="preserve">
205+
<value>Unwrap Task.FromResult();</value>
206+
</data>
204207
<data name="ResxEntryAnalyzerDescription" xml:space="preserve">
205208
<value />
206209
</data>

0 commit comments

Comments
 (0)