Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<SystemCommandLineExperimentalVersion>0.1.0-alpha-63729-01</SystemCommandLineExperimentalVersion>
<SystemCommandLineRenderingVersion>0.1.0-alpha-63729-01</SystemCommandLineRenderingVersion>
<MicrosoftBuildLocatorVersion>1.2.2</MicrosoftBuildLocatorVersion>
<MicrosoftCodeAnalysisAnalyzerTestingVersion>1.0.0-beta1-63812-02</MicrosoftCodeAnalysisAnalyzerTestingVersion>
<MicrosoftExtensionsDependencyInjectionVersion>2.1.1</MicrosoftExtensionsDependencyInjectionVersion>
<MicrosoftExtensionsLoggingVersion>2.1.1</MicrosoftExtensionsLoggingVersion>
<MicrosoftVisualStudioCodingConventionsVersion>1.1.20180503.2</MicrosoftVisualStudioCodingConventionsVersion>
Expand Down
29 changes: 15 additions & 14 deletions src/CodeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ internal static class CodeFormatter
{
private static readonly ImmutableArray<ICodeFormatter> s_codeFormatters = new ICodeFormatter[]
{
new WhitespaceFormatter()
new WhitespaceFormatter(),
new EndOfFileNewLineFormatter()
}.ToImmutableArray();

public static async Task<WorkspaceFormatResult> FormatWorkspaceAsync(
Expand Down Expand Up @@ -163,7 +164,7 @@ void LogWorkspaceWarnings(object sender, WorkspaceDiagnosticEventArgs args)

private static async Task<Solution> RunCodeFormattersAsync(
Solution solution,
ImmutableArray<(Document, OptionSet)> formattableDocuments,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
ILogger logger,
CancellationToken cancellationToken)
{
Expand All @@ -177,7 +178,7 @@ private static async Task<Solution> RunCodeFormattersAsync(
return formattedSolution;
}

internal static async Task<(int, ImmutableArray<(Document, OptionSet)>)> DetermineFormattableFiles(
internal static async Task<(int, ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)>)> DetermineFormattableFiles(
Solution solution,
string projectPath,
ImmutableHashSet<string> filesToFormat,
Expand All @@ -188,7 +189,7 @@ private static async Task<Solution> RunCodeFormattersAsync(
var optionsApplier = new EditorConfigOptionsApplier();

var fileCount = 0;
var getDocumentsAndOptions = new List<Task<(Document, OptionSet, bool)>>(solution.Projects.Sum(project => project.DocumentIds.Count));
var getDocumentsAndOptions = new List<Task<(Document, OptionSet, ICodingConventionsSnapshot, bool)>>(solution.Projects.Sum(project => project.DocumentIds.Count));

foreach (var project in solution.Projects)
{
Expand All @@ -215,11 +216,11 @@ private static async Task<Solution> RunCodeFormattersAsync(
}

var documentsAndOptions = await Task.WhenAll(getDocumentsAndOptions).ConfigureAwait(false);
var foundEditorConfig = documentsAndOptions.Any(documentAndOptions => documentAndOptions.Item3);
var foundEditorConfig = documentsAndOptions.Any(documentAndOptions => documentAndOptions.Item4);

var addedFilePaths = new HashSet<string>(documentsAndOptions.Length);
var formattableFiles = ImmutableArray.CreateBuilder<(Document, OptionSet)>(documentsAndOptions.Length);
foreach (var (document, options, hasEditorConfig) in documentsAndOptions)
var formattableFiles = ImmutableArray.CreateBuilder<(Document, OptionSet, ICodingConventionsSnapshot)>(documentsAndOptions.Length);
foreach (var (document, options, codingConventions, hasEditorConfig) in documentsAndOptions)
{
if (document is null)
{
Expand All @@ -239,13 +240,13 @@ private static async Task<Solution> RunCodeFormattersAsync(
}

addedFilePaths.Add(document.FilePath);
formattableFiles.Add((document, options));
formattableFiles.Add((document, options, codingConventions));
}

return (fileCount, formattableFiles.ToImmutableArray());
}

private static async Task<(Document, OptionSet, bool)> GetDocumentAndOptions(
private static async Task<(Document, OptionSet, ICodingConventionsSnapshot, bool)> GetDocumentAndOptions(
Project project,
DocumentId documentId,
ImmutableHashSet<string> filesToFormat,
Expand All @@ -258,18 +259,18 @@ private static async Task<Solution> RunCodeFormattersAsync(
// If a files list was passed in, then ignore files not present in the list.
if (!filesToFormat.IsEmpty && !filesToFormat.Contains(document.FilePath))
{
return (null, null, false);
return (null, null, null, false);
}

if (!document.SupportsSyntaxTree)
{
return (null, null, false);
return (null, null, null, false);
}

// Ignore generated code files.
if (await GeneratedCodeUtilities.IsGeneratedCodeAsync(document, cancellationToken).ConfigureAwait(false))
{
return (null, null, false);
return (null, null, null, false);
}

var context = await codingConventionsManager.GetConventionContextAsync(
Expand All @@ -280,11 +281,11 @@ private static async Task<Solution> RunCodeFormattersAsync(
// Check whether an .editorconfig was found for this document.
if (context?.CurrentConventions is null)
{
return (document, options, false);
return (document, options, null, false);
}

options = optionsApplier.ApplyConventions(options, context.CurrentConventions, project.Language);
return (document, options, true);
return (document, options, context.CurrentConventions, true);
}
}
}
13 changes: 8 additions & 5 deletions src/Formatters/DocumentFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.CodingConventions;

namespace Microsoft.CodeAnalysis.Tools.Formatters
{
Expand All @@ -20,7 +21,7 @@ internal abstract class DocumentFormatter : ICodeFormatter
/// </summary>
public async Task<Solution> FormatAsync(
Solution solution,
ImmutableArray<(Document, OptionSet)> formattableDocuments,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
ILogger logger,
CancellationToken cancellationToken)
{
Expand All @@ -34,22 +35,23 @@ public async Task<Solution> FormatAsync(
protected abstract Task<SourceText> FormatFileAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
ILogger logger,
CancellationToken cancellationToken);

/// <summary>
/// Applies formatting and returns the changed <see cref="SourceText"/> for each <see cref="Document"/>.
/// </summary>
private ImmutableArray<(Document, Task<SourceText>)> FormatFiles(
ImmutableArray<(Document, OptionSet)> formattableDocuments,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
ILogger logger,
CancellationToken cancellationToken)
{
var formattedDocuments = ImmutableArray.CreateBuilder<(Document, Task<SourceText>)>(formattableDocuments.Length);

foreach (var (document, options) in formattableDocuments)
foreach (var (document, options, codingConventions) in formattableDocuments)
{
var formatTask = Task.Run(async () => await GetFormattedSourceTextAsync(document, options, logger, cancellationToken).ConfigureAwait(false), cancellationToken);
var formatTask = Task.Run(async () => await GetFormattedSourceTextAsync(document, options, codingConventions, logger, cancellationToken).ConfigureAwait(false), cancellationToken);

formattedDocuments.Add((document, formatTask));
}
Expand All @@ -63,13 +65,14 @@ protected abstract Task<SourceText> FormatFileAsync(
private async Task<SourceText> GetFormattedSourceTextAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
ILogger logger,
CancellationToken cancellationToken)
{
logger.LogTrace(Resources.Formatting_code_file_0, Path.GetFileName(document.FilePath));

var originalSourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var formattedSourceText = await FormatFileAsync(document, options, logger, cancellationToken).ConfigureAwait(false);
var formattedSourceText = await FormatFileAsync(document, options, codingConventions, logger, cancellationToken).ConfigureAwait(false);

return !formattedSourceText.ContentEquals(originalSourceText)
? formattedSourceText
Expand Down
76 changes: 76 additions & 0 deletions src/Formatters/EndOfFileNewlineFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.CodingConventions;

namespace Microsoft.CodeAnalysis.Tools.Formatters
{
internal sealed class EndOfFileNewLineFormatter : DocumentFormatter
{
protected override async Task<SourceText> FormatFileAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
ILogger logger,
CancellationToken cancellationToken)
{
if (!codingConventions.TryGetConventionValue("insert_final_newline", out bool insertFinalNewline))
{
return await document.GetTextAsync(cancellationToken);
}

var endOfLine = codingConventions.TryGetConventionValue("end_of_line", out string endOfLineOption)
? GetEndOfLine(endOfLineOption)
: Environment.NewLine;

var sourceText = await document.GetTextAsync(cancellationToken);
var lastLine = sourceText.Lines.Last();

var hasFinalNewLine = lastLine.Span.IsEmpty;

if (insertFinalNewline && !hasFinalNewLine)
{
var finalNewLineSpan = new TextSpan(lastLine.End, 0);
var addNewLineChange = new TextChange(finalNewLineSpan, endOfLine);
sourceText = sourceText.WithChanges(addNewLineChange);
}
else if (!insertFinalNewline && hasFinalNewLine)
{
// In the case of empty files where there is a single empty line, there is nothing to remove.
while (sourceText.Lines.Count > 1 && hasFinalNewLine)
{
var lineBeforeLast = sourceText.Lines[sourceText.Lines.Count - 2];
var finalNewLineSpan = new TextSpan(lineBeforeLast.End, lineBeforeLast.EndIncludingLineBreak - lineBeforeLast.End);
var removeNewLineChange = new TextChange(finalNewLineSpan, string.Empty);
sourceText = sourceText.WithChanges(removeNewLineChange);

lastLine = sourceText.Lines.Last();
hasFinalNewLine = lastLine.Span.IsEmpty;
}
}

return sourceText;
}

private string GetEndOfLine(string endOfLineOption)
{
switch (endOfLineOption)
{
case "lf":
return "\n";
case "cr":
return "\r";
case "crlf":
return "\r\n";
default:
return Environment.NewLine;
}
}
}
}
3 changes: 2 additions & 1 deletion src/Formatters/ICodeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.CodingConventions;

namespace Microsoft.CodeAnalysis.Tools.Formatters
{
Expand All @@ -15,7 +16,7 @@ internal interface ICodeFormatter
/// </summary>
Task<Solution> FormatAsync(
Solution solution,
ImmutableArray<(Document, OptionSet)> formattableDocuments,
ImmutableArray<(Document, OptionSet, ICodingConventionsSnapshot)> formattableDocuments,
ILogger logger,
CancellationToken cancellationToken);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Formatters/WhitespaceFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.CodingConventions;

namespace Microsoft.CodeAnalysis.Tools.Formatters
{
Expand All @@ -17,6 +18,7 @@ internal sealed class WhitespaceFormatter : DocumentFormatter
protected override async Task<SourceText> FormatFileAsync(
Document document,
OptionSet options,
ICodingConventionsSnapshot codingConventions,
ILogger logger,
CancellationToken cancellationToken)
{
Expand Down
90 changes: 90 additions & 0 deletions tests/Extensions/ExportProviderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Composition;
using System.Composition.Hosting.Core;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.Composition;

namespace Microsoft.CodeAnalysis.Tools.Tests
{
internal static class ExportProviderExtensions
{
public static CompositionContext AsCompositionContext(this ExportProvider exportProvider)
{
return new CompositionContextShim(exportProvider);
}

private class CompositionContextShim : CompositionContext
{
private readonly ExportProvider _exportProvider;

public CompositionContextShim(ExportProvider exportProvider)
{
_exportProvider = exportProvider;
}

public override bool TryGetExport(CompositionContract contract, out object export)
{
var importMany = contract.MetadataConstraints.Contains(new KeyValuePair<string, object>("IsImportMany", true));
var (contractType, metadataType) = GetContractType(contract.ContractType, importMany);

if (metadataType != null)
{
var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods()
where method.Name == nameof(ExportProvider.GetExports)
where method.IsGenericMethod && method.GetGenericArguments().Length == 2
where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string)
select method).Single();
var parameterizedMethod = methodInfo.MakeGenericMethod(contractType, metadataType);
export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName });
}
else
{
var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods()
where method.Name == nameof(ExportProvider.GetExports)
where method.IsGenericMethod && method.GetGenericArguments().Length == 1
where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string)
select method).Single();
var parameterizedMethod = methodInfo.MakeGenericMethod(contractType);
export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName });
}

return true;
}

private (Type exportType, Type metadataType) GetContractType(Type contractType, bool importMany)
{
if (importMany && contractType.IsConstructedGenericType)
{
if (contractType.GetGenericTypeDefinition() == typeof(IList<>)
|| contractType.GetGenericTypeDefinition() == typeof(ICollection<>)
|| contractType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
contractType = contractType.GenericTypeArguments[0];
}
}

if (contractType.IsConstructedGenericType)
{
if (contractType.GetGenericTypeDefinition() == typeof(Lazy<>))
{
return (contractType.GenericTypeArguments[0], null);
}
else if (contractType.GetGenericTypeDefinition() == typeof(Lazy<,>))
{
return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1]);
}
else
{
throw new NotSupportedException();
}
}

throw new NotSupportedException();
}
}
}
}
Loading