Skip to content

Commit 991f4e0

Browse files
committed
Add Constructor initial IFileSystem
1 parent 9595b33 commit 991f4e0

9 files changed

Lines changed: 301 additions & 19 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Collections.Generic;
2+
using System.IO.Abstractions.Analyzers.Analyzers;
3+
using System.IO.Abstractions.Analyzers.CodeFixes;
4+
using Microsoft.CodeAnalysis;
5+
using Roslyn.Testing.CodeFix;
6+
using Xunit;
7+
8+
namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes
9+
{
10+
public class FileServiceConstructorInitialCodeFixTests :
11+
CSharpCodeFixProviderTest<FileServiceInterfaceInjectionAnalyzer, FileServiceConstructorInitialCodeFix>
12+
{
13+
[Theory]
14+
[InlineData("BeforeFix.txt", "AfterFix.txt")]
15+
public void CodeFix(string sourceBefore, string sourceAfter)
16+
{
17+
var sourceBeforeFix = ReadFile(sourceBefore);
18+
var sourceAfterFix = ReadFile(sourceAfter);
19+
VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true);
20+
}
21+
22+
protected override IEnumerable<MetadataReference> GetAdditionalReferences() => new[]
23+
{
24+
MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location)
25+
};
26+
}
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.IO.Abstractions;
2+
3+
namespace SomeNameSpace
4+
{
5+
public class WithOutFileSystem
6+
{
7+
private readonly IFileSystem _fileSystem;
8+
9+
public WithOutFileSystem()
10+
{
11+
_fileSystem = new FileSystem();
12+
}
13+
14+
public void SomeMethod()
15+
{
16+
const string filePath = "C:\\temp.txt";
17+
18+
if (File.Exists(filePath))
19+
{
20+
File.Delete(filePath);
21+
}
22+
}
23+
}
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.IO;
2+
3+
namespace SomeNameSpace
4+
{
5+
public class WithOutFileSystem
6+
{
7+
public WithOutFileSystem()
8+
{
9+
}
10+
11+
public void SomeMethod()
12+
{
13+
const string filePath = "C:\\temp.txt";
14+
15+
if (File.Exists(filePath))
16+
{
17+
File.Delete(filePath);
18+
}
19+
}
20+
}
21+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using System.Linq;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CodeActions;
6+
using Microsoft.CodeAnalysis.CSharp;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Microsoft.CodeAnalysis.Editing;
9+
using Microsoft.CodeAnalysis.Formatting;
10+
using Microsoft.CodeAnalysis.Simplification;
11+
using SyntaxNode = Microsoft.CodeAnalysis.SyntaxNode;
12+
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
13+
14+
namespace System.IO.Abstractions.Analyzers.CodeActions
15+
{
16+
public class FileServiceConstructorInitialCodeAction : CodeAction
17+
{
18+
private readonly ClassDeclarationSyntax _class;
19+
20+
private readonly Document _document;
21+
22+
public FileServiceConstructorInitialCodeAction(string title, Document document, ClassDeclarationSyntax @class)
23+
{
24+
_class = @class;
25+
_document = document;
26+
Title = title;
27+
}
28+
29+
public override string Title { get; }
30+
31+
public override string EquivalenceKey => Title;
32+
33+
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
34+
{
35+
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
36+
37+
if (!HasFileSystemProperty(_class))
38+
{
39+
editor.InsertMembers(_class,
40+
0,
41+
new SyntaxNode[]
42+
{
43+
CreateFileSystemPropertyDeclaration()
44+
});
45+
}
46+
47+
ConstructorAddParameter(_class, editor);
48+
49+
var compilationUnitSyntax = GetCompilationUnit(_class);
50+
51+
if (compilationUnitSyntax.Usings.Any())
52+
{
53+
editor.ReplaceNode(GetSystemIoUsing(compilationUnitSyntax), GetFileSystemUsing());
54+
}
55+
56+
return editor.GetChangedDocument();
57+
}
58+
59+
private static UsingDirectiveSyntax GetFileSystemUsing()
60+
{
61+
return SF.UsingDirective(SF.ParseName(Constants.FileSystemNameSpace));
62+
}
63+
64+
private static UsingDirectiveSyntax GetSystemIoUsing(CompilationUnitSyntax unit)
65+
{
66+
return unit.Usings.FirstOrDefault(x =>
67+
x.Name.NormalizeWhitespace().ToFullString().Equals(typeof(Path).Namespace));
68+
}
69+
70+
private static FieldDeclarationSyntax CreateFileSystemPropertyDeclaration()
71+
{
72+
return SF.FieldDeclaration(SF.VariableDeclaration(GetFileSystemType())
73+
.WithVariables(SF.SingletonSeparatedList(SF.VariableDeclarator(SF.Identifier(Constants.FieldFileSystemName)))))
74+
.WithModifiers(SF.TokenList(SF.Token(SyntaxKind.PrivateKeyword),
75+
SF.Token(SyntaxKind.ReadOnlyKeyword)));
76+
}
77+
78+
private static ParameterSyntax CreateFileSystemParameterDeclaration()
79+
{
80+
return SF.Parameter(SF.Identifier(Constants.ParameterFileSystemName))
81+
.WithType(GetFileSystemType())
82+
.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.SpecialTypeAnnotation)
83+
.NormalizeWhitespace();
84+
}
85+
86+
private static TypeSyntax GetFileSystemType()
87+
{
88+
return SF.ParseTypeName(Constants.FileSystemInterfaceName);
89+
}
90+
91+
private static ExpressionStatementSyntax CreateAssignmentExpression()
92+
{
93+
return SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
94+
SyntaxFactory.IdentifierName("_fileSystem"),
95+
SyntaxFactory.ObjectCreationExpression(SyntaxFactory.IdentifierName(Constants.FileSystemClassName))
96+
.WithArgumentList(SyntaxFactory.ArgumentList())));
97+
}
98+
99+
private static CompilationUnitSyntax GetCompilationUnit(SyntaxNode node)
100+
{
101+
switch (node)
102+
{
103+
case null:
104+
105+
return null;
106+
case CompilationUnitSyntax compilationUnitSyntax:
107+
108+
return compilationUnitSyntax;
109+
default:
110+
111+
return GetCompilationUnit(node.Parent);
112+
}
113+
}
114+
115+
private static bool HasFileSystemProperty(TypeDeclarationSyntax classDeclaration)
116+
{
117+
return classDeclaration.Members.OfType<PropertyDeclarationSyntax>()
118+
.Any(x => x.Identifier.Text == Constants.FieldFileSystemName && x.Type == GetFileSystemType());
119+
}
120+
121+
private static ConstructorDeclarationSyntax GetConstructor(SyntaxNode classDeclaration)
122+
{
123+
return classDeclaration.ChildNodes().OfType<ConstructorDeclarationSyntax>().FirstOrDefault();
124+
}
125+
126+
private static bool ConstructorHasFileSystemParameter(BaseMethodDeclarationSyntax constructor)
127+
{
128+
return constructor.ParameterList.Parameters
129+
.Any(x => x.Identifier.Text == Constants.ParameterFileSystemName && x.Type == GetFileSystemType());
130+
}
131+
132+
private static bool ConstructorHasAssignmentExpression(BaseMethodDeclarationSyntax constructor)
133+
{
134+
if (constructor.Body == null)
135+
{
136+
return false;
137+
}
138+
139+
return constructor.Body.Statements.OfType<ExpressionStatementSyntax>()
140+
.Any(x => x.IsKind(SyntaxKind.SimpleAssignmentExpression)
141+
&& x.Expression.Contains(SF.IdentifierName(Constants.FieldFileSystemName))
142+
&& x.Expression.Contains(SF.IdentifierName(Constants.ParameterFileSystemName)));
143+
}
144+
145+
private static bool HasConstructor(SyntaxNode classDeclaration)
146+
{
147+
return classDeclaration.ChildNodes().OfType<ConstructorDeclarationSyntax>().Any();
148+
}
149+
150+
private static void ConstructorAddParameter(ClassDeclarationSyntax classDeclaration, SyntaxEditor editor)
151+
{
152+
var constructor = HasConstructor(classDeclaration)
153+
? GetConstructor(classDeclaration)
154+
: SF.ConstructorDeclaration(classDeclaration.Identifier)
155+
.WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword)));
156+
157+
var newConstructor = constructor.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)
158+
.NormalizeWhitespace();
159+
160+
if (!ConstructorHasAssignmentExpression(newConstructor))
161+
{
162+
newConstructor = newConstructor.AddBodyStatements(CreateAssignmentExpression());
163+
}
164+
165+
if (HasConstructor(classDeclaration))
166+
{
167+
editor.ReplaceNode(constructor, newConstructor);
168+
} else
169+
{
170+
editor.InsertBefore(GetMethod(classDeclaration), newConstructor);
171+
}
172+
}
173+
174+
private static MethodDeclarationSyntax GetMethod(ClassDeclarationSyntax classDeclaration)
175+
{
176+
return classDeclaration.ChildNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault();
177+
}
178+
}
179+
}

System.IO.Abstractions.Analyzers/CodeActions/FileServiceInterfaceInjectionCodeAction.cs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ namespace System.IO.Abstractions.Analyzers.CodeActions
1515
/// <inheritdoc />
1616
public class FileServiceInterfaceInjectionCodeAction : CodeAction
1717
{
18-
private const string FieldFileSystemName = "_fileSystem";
19-
20-
private const string ParameterFileSystemName = "fileSystem";
21-
2218
private readonly ClassDeclarationSyntax _class;
2319

2420
private readonly Document _document;
@@ -74,29 +70,29 @@ private static UsingDirectiveSyntax GetSystemIoUsing(CompilationUnitSyntax unit)
7470
private static FieldDeclarationSyntax CreateFileSystemPropertyDeclaration()
7571
{
7672
return SF.FieldDeclaration(SF.VariableDeclaration(GetFileSystemType())
77-
.WithVariables(SF.SingletonSeparatedList(SF.VariableDeclarator(SF.Identifier(FieldFileSystemName)))))
73+
.WithVariables(SF.SingletonSeparatedList(SF.VariableDeclarator(SF.Identifier(Constants.FieldFileSystemName)))))
7874
.WithModifiers(SF.TokenList(SF.Token(SyntaxKind.PrivateKeyword),
7975
SF.Token(SyntaxKind.ReadOnlyKeyword)));
8076
}
8177

8278
private static ParameterSyntax CreateFileSystemParameterDeclaration()
8379
{
84-
return SF.Parameter(SF.Identifier(ParameterFileSystemName))
80+
return SF.Parameter(SF.Identifier(Constants.ParameterFileSystemName))
8581
.WithType(GetFileSystemType())
8682
.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.SpecialTypeAnnotation)
8783
.NormalizeWhitespace();
8884
}
8985

9086
private static TypeSyntax GetFileSystemType()
9187
{
92-
return SF.ParseTypeName(Constants.FileSystemName);
88+
return SF.ParseTypeName(Constants.FileSystemInterfaceName);
9389
}
9490

9591
private static ExpressionStatementSyntax CreateAssignmentExpression()
9692
{
9793
return SF.ExpressionStatement(SF.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
98-
SF.IdentifierName(FieldFileSystemName),
99-
SF.IdentifierName(ParameterFileSystemName)));
94+
SF.IdentifierName(Constants.FieldFileSystemName),
95+
SF.IdentifierName(Constants.ParameterFileSystemName)));
10096
}
10197

10298
private static CompilationUnitSyntax GetCompilationUnit(SyntaxNode node)
@@ -118,7 +114,7 @@ private static CompilationUnitSyntax GetCompilationUnit(SyntaxNode node)
118114
private static bool HasFileSystemProperty(TypeDeclarationSyntax classDeclaration)
119115
{
120116
return classDeclaration.Members.OfType<PropertyDeclarationSyntax>()
121-
.Any(x => x.Identifier.Text == FieldFileSystemName && x.Type == GetFileSystemType());
117+
.Any(x => x.Identifier.Text == Constants.FieldFileSystemName && x.Type == GetFileSystemType());
122118
}
123119

124120
private static ConstructorDeclarationSyntax GetConstructor(SyntaxNode classDeclaration)
@@ -129,7 +125,7 @@ private static ConstructorDeclarationSyntax GetConstructor(SyntaxNode classDecla
129125
private static bool ConstructorHasFileSystemParameter(BaseMethodDeclarationSyntax constructor)
130126
{
131127
return constructor.ParameterList.Parameters
132-
.Any(x => x.Identifier.Text == ParameterFileSystemName && x.Type == GetFileSystemType());
128+
.Any(x => x.Identifier.Text == Constants.ParameterFileSystemName && x.Type == GetFileSystemType());
133129
}
134130

135131
private static bool ConstructorHasAssignmentExpression(BaseMethodDeclarationSyntax constructor)
@@ -141,8 +137,8 @@ private static bool ConstructorHasAssignmentExpression(BaseMethodDeclarationSynt
141137

142138
return constructor.Body.Statements.OfType<ExpressionStatementSyntax>()
143139
.Any(x => x.IsKind(SyntaxKind.SimpleAssignmentExpression)
144-
&& x.Expression.Contains(SF.IdentifierName(FieldFileSystemName))
145-
&& x.Expression.Contains(SF.IdentifierName(ParameterFileSystemName)));
140+
&& x.Expression.Contains(SF.IdentifierName(Constants.FieldFileSystemName))
141+
&& x.Expression.Contains(SF.IdentifierName(Constants.ParameterFileSystemName)));
146142
}
147143

148144
private static bool HasConstructor(SyntaxNode classDeclaration)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using System.IO.Abstractions.Analyzers.CodeActions;
4+
using System.Threading.Tasks;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CodeFixes;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
9+
namespace System.IO.Abstractions.Analyzers.CodeFixes
10+
{
11+
[Shared]
12+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileServiceConstructorInitialCodeFix))]
13+
public class FileServiceConstructorInitialCodeFix : CodeFixProvider
14+
{
15+
private const string Title = "Create FileSystem in constructor and using System.IO.Abstractions";
16+
17+
public sealed override ImmutableArray<string> FixableDiagnosticIds =>
18+
ImmutableArray.Create(Constants.Io0001);
19+
20+
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
21+
22+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
23+
{
24+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
25+
var classDeclarationSyntax = root.FindNode(context.Span).FirstAncestorOrSelf<ClassDeclarationSyntax>();
26+
27+
context.RegisterCodeFix(new FileServiceConstructorInitialCodeAction(Title,
28+
context.Document,
29+
classDeclarationSyntax),
30+
context.Diagnostics);
31+
}
32+
}
33+
}

System.IO.Abstractions.Analyzers/CodeFixes/FileServiceInterfaceInjectionCodeFix.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Collections.Immutable;
22
using System.Composition;
3-
using System.IO.Abstractions.Analyzers.Analyzers;
43
using System.IO.Abstractions.Analyzers.CodeActions;
54
using System.Threading.Tasks;
65
using Microsoft.CodeAnalysis;
@@ -13,7 +12,7 @@ namespace System.IO.Abstractions.Analyzers.CodeFixes
1312
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileServiceInterfaceInjectionCodeFix))]
1413
public class FileServiceInterfaceInjectionCodeFix : CodeFixProvider
1514
{
16-
private const string Title = "Use System.IO.Abstractions";
15+
private const string Title = "Inject IFileSystem and using System.IO.Abstractions";
1716

1817
public sealed override ImmutableArray<string> FixableDiagnosticIds =>
1918
ImmutableArray.Create(Constants.Io0001);

0 commit comments

Comments
 (0)