Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 EOFNewLineFormatter()
}.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/EOFNewlineFormatter.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 EOFNewLineFormatter : 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