Skip to content
This repository was archived by the owner on Jul 12, 2022. It is now read-only.

Commit 98e13fa

Browse files
committed
Abstracted out writing to the console
Added the IFormatLogger interface to abstract away the direct access to the Console type. This is a library that can be hosted in a variety of environments hence it can't write directly to the console. It needs an abstraction the host can use to decide how to display the output. This also made the unit test output clean again.
1 parent 0020995 commit 98e13fa

10 files changed

+115
-34
lines changed

src/Microsoft.DotNet.CodeFormatting.Tests/Rules/CombinationTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ static CombinationTest()
2525
public CombinationTest()
2626
{
2727
s_formattingEngine.CopyrightHeader = ImmutableArray.Create("", "// header");
28+
s_formattingEngine.FormatLogger = new EmptyFormatLogger();
2829
}
2930

3031
protected override async Task<Document> RewriteDocumentAsync(Document document)

src/Microsoft.DotNet.CodeFormatting.Tests/Rules/UsesXunitForTestsFormattingRuleTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class UsesXunitForTestsFormattingRuleTests : GlobalSemanticRuleTestBase
1515
{
1616
internal override IGlobalSemanticFormattingRule Rule
1717
{
18-
get { return new Rules.UsesXunitForTestsFormattingRule(); }
18+
get { return new Rules.UsesXunitForTestsFormattingRule(new Options()); }
1919
}
2020

2121
[Fact]

src/Microsoft.DotNet.CodeFormatting/FormattingEngine.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ namespace Microsoft.DotNet.CodeFormatting
1313
{
1414
public static class FormattingEngine
1515
{
16-
private const string RuleTypeNotFoundError = "The specified rule type was not found: ";
17-
1816
public static IFormattingEngine Create(IEnumerable<string> ruleTypes, IEnumerable<string> filenames)
1917
{
2018
var catalog = new AssemblyCatalog(typeof(FormattingEngine).Assembly);
@@ -55,11 +53,12 @@ public static IFormattingEngine Create(IEnumerable<string> ruleTypes, IEnumerabl
5553
}
5654

5755
var engine = container.GetExportedValue<IFormattingEngine>();
56+
var consoleFormatLogger = new ConsoleFormatLogger();
5857

5958
// Need to do this after the catalog is queried, otherwise the lambda won't have been run
6059
foreach (var notFoundRuleType in notFoundRuleTypes)
6160
{
62-
(RuleTypeNotFoundError + notFoundRuleType).WriteConsoleError(1, "");
61+
consoleFormatLogger.WriteErrorLine("The specified rule type was not found: {0}", notFoundRuleType);
6362
}
6463

6564
return engine;

src/Microsoft.DotNet.CodeFormatting/FormattingEngineImplementation.cs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ public ImmutableArray<string> CopyrightHeader
3434
set { _options.CopyrightHeader = value; }
3535
}
3636

37+
public IFormatLogger FormatLogger
38+
{
39+
get { return _options.FormatLogger; }
40+
set { _options.FormatLogger = value; }
41+
}
42+
3743
public bool Verbose
3844
{
3945
get { return _verbose; }
@@ -77,7 +83,7 @@ private async Task FormatAsync(Workspace workspace, IReadOnlyList<DocumentId> do
7783
watch.Stop();
7884

7985
await SaveChanges(solution, originalSolution, cancellationToken);
80-
Console.WriteLine("Total time {0}", watch.Elapsed);
86+
FormatLogger.WriteLine("Total time {0}", watch.Elapsed);
8187
}
8288

8389
internal async Task<Solution> FormatCoreAsync(Solution originalSolution, IReadOnlyList<DocumentId> documentIds, CancellationToken cancellationToken)
@@ -126,7 +132,7 @@ private async Task<bool> ShouldBeProcessedAsync(Document document)
126132
var fileInfo = new FileInfo(document.FilePath);
127133
if (!fileInfo.Exists || fileInfo.IsReadOnly)
128134
{
129-
Console.WriteLine("warning: skipping document '{0}' because it {1}.",
135+
FormatLogger.WriteLine("warning: skipping document '{0}' because it {1}.",
130136
document.FilePath,
131137
fileInfo.IsReadOnly ? "is read-only" : "does not exist");
132138
return false;
@@ -163,12 +169,12 @@ private void EndDocument(Document document)
163169
_watch.Stop();
164170
if (_verbose && _watch.Elapsed.TotalSeconds > 1)
165171
{
166-
Console.WriteLine();
167-
Console.WriteLine(" {0} {1} seconds", document.Name, _watch.Elapsed.TotalSeconds);
172+
FormatLogger.WriteLine();
173+
FormatLogger.WriteLine(" {0} {1} seconds", document.Name, _watch.Elapsed.TotalSeconds);
168174
}
169175
else
170176
{
171-
Console.Write(".");
177+
FormatLogger.Write(".");
172178
}
173179
}
174180

@@ -180,7 +186,7 @@ private void EndDocument(Document document)
180186
/// </summary>
181187
private async Task<Solution> RunSyntaxPass(Solution originalSolution, IReadOnlyList<DocumentId> documentIds, CancellationToken cancellationToken)
182188
{
183-
Console.WriteLine("Syntax Pass");
189+
FormatLogger.WriteLine("Syntax Pass");
184190

185191
var currentSolution = originalSolution;
186192
foreach (var documentId in documentIds)
@@ -202,7 +208,7 @@ private async Task<Solution> RunSyntaxPass(Solution originalSolution, IReadOnlyL
202208
}
203209
}
204210

205-
Console.WriteLine();
211+
FormatLogger.WriteLine();
206212
return currentSolution;
207213
}
208214

@@ -218,19 +224,19 @@ private SyntaxNode RunSyntaxPass(SyntaxNode root)
218224

219225
private async Task<Solution> RunLocalSemanticPass(Solution solution, IReadOnlyList<DocumentId> documentIds, CancellationToken cancellationToken)
220226
{
221-
Console.WriteLine("Local Semantic Pass");
227+
FormatLogger.WriteLine("Local Semantic Pass");
222228
foreach (var localSemanticRule in _localSemanticRules)
223229
{
224230
solution = await RunLocalSemanticPass(solution, documentIds, localSemanticRule, cancellationToken);
225231
}
226232

227-
Console.WriteLine();
233+
FormatLogger.WriteLine();
228234
return solution;
229235
}
230236

231237
private async Task<Solution> RunLocalSemanticPass(Solution originalSolution, IReadOnlyList<DocumentId> documentIds, ILocalSemanticFormattingRule localSemanticRule, CancellationToken cancellationToken)
232238
{
233-
Console.WriteLine(" {0}", localSemanticRule.GetType().Name);
239+
FormatLogger.WriteLine(" {0}", localSemanticRule.GetType().Name);
234240
var currentSolution = originalSolution;
235241
foreach (var documentId in documentIds)
236242
{
@@ -251,25 +257,25 @@ private async Task<Solution> RunLocalSemanticPass(Solution originalSolution, IRe
251257
}
252258
}
253259

254-
Console.WriteLine();
260+
FormatLogger.WriteLine();
255261
return currentSolution;
256262
}
257263

258264
private async Task<Solution> RunGlobalSemanticPass(Solution solution, IReadOnlyList<DocumentId> documentIds, CancellationToken cancellationToken)
259265
{
260-
Console.WriteLine("Global Semantic Pass");
266+
FormatLogger.WriteLine("Global Semantic Pass");
261267
foreach (var globalSemanticRule in _globalSemanticRules)
262268
{
263269
solution = await RunGlobalSemanticPass(solution, documentIds, globalSemanticRule, cancellationToken);
264270
}
265271

266-
Console.WriteLine();
272+
FormatLogger.WriteLine();
267273
return solution;
268274
}
269275

270276
private async Task<Solution> RunGlobalSemanticPass(Solution solution, IReadOnlyList<DocumentId> documentIds, IGlobalSemanticFormattingRule globalSemanticRule, CancellationToken cancellationToken)
271277
{
272-
Console.WriteLine(" {0}", globalSemanticRule.GetType().Name);
278+
FormatLogger.WriteLine(" {0}", globalSemanticRule.GetType().Name);
273279
foreach (var documentId in documentIds)
274280
{
275281
var document = solution.GetDocument(documentId);
@@ -284,7 +290,7 @@ private async Task<Solution> RunGlobalSemanticPass(Solution solution, IReadOnlyL
284290
EndDocument(document);
285291
}
286292

287-
Console.WriteLine();
293+
FormatLogger.WriteLine();
288294
return solution;
289295
}
290296
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Microsoft.DotNet.CodeFormatting
8+
{
9+
internal interface IFormatLogger
10+
{
11+
void Write(string format, params object[] args);
12+
void WriteLine(string format, params object[] args);
13+
void WriteLine();
14+
void WriteErrorLine(string format, params object[] args);
15+
}
16+
17+
/// <summary>
18+
/// This implementation will forward all output directly to the console.
19+
/// </summary>
20+
internal sealed class ConsoleFormatLogger : IFormatLogger
21+
{
22+
public void Write(string format, params object[] args)
23+
{
24+
Console.Write(format, args);
25+
}
26+
27+
public void WriteLine(string format, params object[] args)
28+
{
29+
Console.WriteLine(format, args);
30+
}
31+
32+
public void WriteErrorLine(string format, params object[] args)
33+
{
34+
Console.Write("Error: ");
35+
Console.WriteLine(format, args);
36+
}
37+
38+
public void WriteLine()
39+
{
40+
Console.WriteLine();
41+
}
42+
}
43+
44+
/// <summary>
45+
/// This implementation just ignores all output from the formatter. It's useful
46+
/// for unit testing purposes.
47+
/// </summary>
48+
internal sealed class EmptyFormatLogger : IFormatLogger
49+
{
50+
public void Write(string format, params object[] args)
51+
{
52+
53+
}
54+
55+
public void WriteLine(string format, params object[] args)
56+
{
57+
58+
}
59+
60+
public void WriteErrorLine(string format, params object[] argsa)
61+
{
62+
63+
}
64+
65+
public void WriteLine()
66+
{
67+
68+
}
69+
}
70+
}

src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
<Compile Include="Filters\FilenameFilter.cs" />
7575
<Compile Include="FormattingEngine.cs" />
7676
<Compile Include="FormattingEngineImplementation.cs" />
77+
<Compile Include="IFormatLogger.cs" />
7778
<Compile Include="IFormattingEngine.cs" />
7879
<Compile Include="IFormattingFilter.cs" />
7980
<Compile Include="IFormattingRule.cs" />

src/Microsoft.DotNet.CodeFormatting/Options.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ internal sealed class Options
2121
};
2222

2323
internal ImmutableArray<string> CopyrightHeader { get; set; }
24+
internal IFormatLogger FormatLogger { get; set; }
2425

2526
[ImportingConstructor]
2627
internal Options()
2728
{
2829
CopyrightHeader = ImmutableArray.Create(s_defaultCopyrightHeader);
30+
FormatLogger = new ConsoleFormatLogger();
2931
}
3032
}
3133
}

src/Microsoft.DotNet.CodeFormatting/Rules/HasNoCustomCopyrightHeaderFormattingRule.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ internal sealed class HasNoCustomCopyrightHeaderFormattingRule : ISyntaxFormatti
2222
private static string StartMarker { get; set; }
2323
private static string EndMarker { get; set; }
2424

25-
private const string FileNotFoundError = "The specified CopyrightHeader.md file was not found.";
25+
private readonly Options _options;
2626

27-
private const string FileSyntaxError = "There should be exactly 3 lines in CopyrightHeader.md.";
27+
[ImportingConstructor]
28+
internal HasNoCustomCopyrightHeaderFormattingRule(Options options)
29+
{
30+
_options = options;
31+
}
2832

2933
public SyntaxNode Process(SyntaxNode syntaxNode)
3034
{
@@ -114,22 +118,22 @@ private static bool IsEndOfXmlHeader(SyntaxTrivia trivia, out SyntaxTrivia end)
114118
.FirstOrDefault();
115119
}
116120

117-
private static bool SetHeaders()
121+
private bool SetHeaders()
118122
{
119123
var filePath = Path.Combine(
120124
Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)),
121125
"CopyrightHeader.md");
122126

123127
if (!File.Exists(filePath))
124128
{
125-
FileNotFoundError.WriteConsoleError(1, "CopyrightHeader.md");
129+
_options.FormatLogger.WriteErrorLine("The specified CopyrightHeader.md file was not found.");
126130
return false;
127131
}
128132

129133
var lines = File.ReadAllLines(filePath).Where(l => !l.StartsWith("##") && !l.Equals("")).ToArray();
130134
if (lines.Count() != 3)
131135
{
132-
FileSyntaxError.WriteConsoleError(1, "CopyrightHeader.md");
136+
_options.FormatLogger.WriteErrorLine("There should be exactly 3 lines in CopyrightHeader.md.");
133137
return false;
134138
}
135139

src/Microsoft.DotNet.CodeFormatting/Rules/RuleExtensions.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,5 @@ public static Document GetOriginalDocumentWithPreprocessorSymbols(this Document
5151
{
5252
return document.Project.WithParseOptions(new CSharpParseOptions().WithPreprocessorSymbols(preprocessorNamesDefined)).GetDocument(document.Id);
5353
}
54-
55-
public static void WriteConsoleError(this string msg, int lineNo, string documentName)
56-
{
57-
Console.ForegroundColor = ConsoleColor.Red;
58-
Console.WriteLine("Error (Line: " + lineNo + ", " + documentName + ") " + msg);
59-
Console.ResetColor();
60-
}
6154
}
6255
}

src/Microsoft.DotNet.CodeFormatting/Rules/UsesXunitForTestsFormattingRule.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@ internal sealed class UsesXunitForTestsFormattingRule : IGlobalSemanticFormattin
2424
{
2525
private static object _lockObject = new object();
2626
private static HashSet<string> _mstestNamespaces;
27+
private readonly Options _options;
2728

28-
private const string FileNotFoundError = "The MSTestNamespaces.txt file was not found.";
29+
[ImportingConstructor]
30+
internal UsesXunitForTestsFormattingRule(Options options)
31+
{
32+
_options = options;
33+
}
2934

3035
public async Task<Solution> ProcessAsync(Document document, SyntaxNode syntaxNode, CancellationToken cancellationToken)
3136
{
@@ -285,7 +290,7 @@ private static bool IsTestNamespaceType(string docID, string simpleTypeName)
285290
return _mstestNamespaces.Contains(namespaceDocID);
286291
}
287292

288-
private static bool LoadMSTestNamespaces()
293+
private bool LoadMSTestNamespaces()
289294
{
290295
lock (_lockObject)
291296
{
@@ -300,7 +305,7 @@ private static bool LoadMSTestNamespaces()
300305

301306
if (!File.Exists(filePath))
302307
{
303-
FileNotFoundError.WriteConsoleError(1, "MSTestNamespaces.txt");
308+
_options.FormatLogger.WriteErrorLine("The MSTestNamespaces.txt file was not found.");
304309
return false;
305310
}
306311

0 commit comments

Comments
 (0)