Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added DotNet global tool for code formatting. #29403

Merged
merged 5 commits into from
Oct 10, 2018

Conversation

JoeRobich
Copy link
Member

Tool can be run on a project, solution, or folder containing a
project or solution. Tool pulls formatting configuration from an
.editorconfig if present, otherwise formats with roslyn defaults.


MSBuildEnvironment.ApplyEnvironmentVariables();

return await CodeFormatter.FormatWorkspaceAsync(logger, workspacePath, isSolution, cancellationTokenSource.Token);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this directly calling a fullframework library? Did you try to install the tool and invoke locally? As long as that works it is fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up changing the Workspaces.MSBuild project to multi-target. Only required a couple changes to code that was already conditionally compiling. I've tested on Win10 and Ubuntu 18.04.

.UseParseErrorReporting()
.UseExceptionHandler()
.AddOption(new[] { "-w", "--workspace" }, "The project or solution to format", a => a.WithDefaultValue(() => null).ExactlyOne())
.AddOption(new[] { "-q", "--quiet" }, "Suppresses all output except warnings and errors", a => a.ParseArgumentsAs<bool>((sr) => ArgumentParseResult.Success(true), new ArgumentArityValidator(0, 0)))
Copy link

@jonsequitur jonsequitur Aug 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simpler. Just this should do what you want:

a.ParseArgumentsAs<bool>()

The arity is inferred from the type if you don't specify it.

Also, I think the following will always evaluate as true because you're not parsing the sr value that you're receiving in the lambda:

a => a.ParseArgumentsAs<bool>((sr) => ArgumentParseResult.Success(true)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted an arity of 0 for those options since I was treating them as simple flags. This gives the behavior that my tool could be invoked like dotnet format -v .\SomePath\ and verbose would be set and the .\SomePath\ would get interpreted as the workspace. Just using a.ParseArgumentsAs<bool>() would try to interpret .\SomePath\ as a bool and error.

Thankfully it doesn't always return true. It has the desired behavior of only being true when the option is present.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default behavior for bool is that the argument can be supplied or not (e.g. an arity of 0 - 1), and either way will work:

format -q
format -q true
format -q false

I'm wondering if you found a bug. I'll try to repro.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indeed a bug in the parser.

@JoeRobich JoeRobich requested a review from a team as a code owner August 21, 2018 01:08
{
internal class CodeFormatter
{
public static async Task<int> FormatWorkspaceAsync(ILogger logger, string workspacePath, bool isSolution, CancellationToken token)
Copy link
Contributor

@jmarolf jmarolf Aug 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't workspacePath be solutionOrProjectPath?

{ "AlwaysCompileMarkupFilesInSeparateDomain", bool.FalseString },

// Use the latest language version to force the full set of available analyzers to run on the project.
{ "LangVersion", "latest" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think we would want to respect the language version of each project. I wouldn't want 7.3 features added if they break my project.

}

var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
if (GeneratedCodeUtilities.IsGeneratedCode(syntaxTree, isCommentTrivia, cancellationToken))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we control this with an option? Some people may want to format generated code.

Copy link
Member

@sharwell sharwell Aug 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should not. Code generators have the option of applying the formatting themselves during the code generation process, but we should assume that once the tool completes the form is finalized.

@@ -0,0 +1,66 @@
using System;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copyright?

else
{
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this code looks like an exact duplicate of code that is above.

}
catch (Exception ex)
{
throw new Exception($"Unable to write standard error output when running command {commandInfo.Name}: {ex.Message}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localized message?

var buffer = new char[512];
while (true)
{
var readCount = process.StandardOutput.Read(buffer, 0, 512);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repeat of constant.

{
Name = "where",
Arguments = commandName
}, timeoutInMilliseconds: 1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repeat of constant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the need for a timeout?

{
using (var outputStream = new MemoryStream())
{
using (var outputStreamWriter = new StreamWriter(outputStream))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary indentation.

private static IEnumerable<string> FindSolutionFiles(string basePath) => Directory.EnumerateFileSystemEntries(basePath, "*.sln", SearchOption.TopDirectoryOnly);

private static IEnumerable<string> FindProjectFiles(string basePath) => Directory.EnumerateFileSystemEntries(basePath, "*.*proj", SearchOption.TopDirectoryOnly)
.Where(f => !".xproj".Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repeat of constant.

}
catch (Exception ex)
{
if (ex is TaskCanceledException || ex is OperationCanceledException)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TaskCanceledException is already an OperationCanceledException. So it's redundant to have both checks.

}

logger.LogError(ex.ToString());
logger.LogError((string)"An unexpected error occurred");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cast seems unnecessary.

@@ -0,0 +1,48 @@
dotnet-format
=============
`dotnet-format` is a code formatter for `dotnet` that reads style preferences from your `.editorconfig` file and applies them to a project or solution.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i missed it, but what code actually ensures there is a .editorconfig file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point.

In the absence of an .editorconfig file it will use the default formatting options. This behavior might be useful to some users who do not wish to choose formatting options for their project but simply enforce some level of consistency (like Prettier). Something to consider.

<value>Formatting code files in workspace '{0}'.</value>
</data>
<data name="Warn_UnsupportedProject" xml:space="preserve">
<value>Could not format '{0}'. Format currently supports only C# and VisualBasic projects.</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Visual Basic" is two words.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Having a bit of a "Berenstain Bears" moment here. I guess I always assumed in the same way QuickBasic was one word, that Visual Basic was too and just never noticed otherwise.

@CyrusNajmabadi
Copy link
Member

Dumb question: why does this need to live in the Roslyn repo? It seems like it would be fine living elsewhere.

@@ -268,6 +271,8 @@
https://myget.org/F/vs-editor/api/v3/index.json;
https://vside.myget.org/F/vssdk/api/v3/index.json;
https://vside.myget.org/F/vs-impl/api/v3/index.json;
https://dotnet.myget.org/F/system-commandline/api/v3/index.json;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra blank line.

// AppDomains will likely not work due to https://github.com/Microsoft/MSBuildLocator/issues/16.
{ "AlwaysCompileMarkupFilesInSeparateDomain", bool.FalseString },

// Use the latest language version to force the full set of available analyzers to run on the project.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need analyzers running if this is just updating code formatting?

@JoeRobich JoeRobich changed the base branch from master to dev16.0.x September 19, 2018 21:36
Tool can be run on a project, solution, or folder containing a
project or solution. Tool pulls formatting configuration from an
.editorconfig if present, otherwise formats with roslyn defaults.
src/Tools/dotnet-format/dotnet-format.csproj Show resolved Hide resolved
src/Tools/dotnet-format/README.md Outdated Show resolved Hide resolved
src/Tools/dotnet-format/README.md Outdated Show resolved Hide resolved
src/Tools/dotnet-format/Program.cs Outdated Show resolved Hide resolved
src/Tools/dotnet-format/Program.cs Outdated Show resolved Hide resolved
src/Tools/dotnet-format/MSBuild/CommandUtility.cs Outdated Show resolved Hide resolved

// LICENSING NOTE: The license for this file is from the originating
// source and not the general https://github.com/dotnet/roslyn license.
// See https://github.com/Microsoft/MSBuildLocator/blob/6631a6dbf9be72b2426e260c99dc0f345e79b8e5/src/MSBuildLocator/MSBuildLocator.cs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ What would be the downside if we used MSBuild Locator via PackageReference for now, and sent a pull request to that project to add x-plat support instead of coding it all here in a forked copy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like they have an open PR to support the .net core use case. I can add a comment that links to the PR and recommends moving to use their package once it gets in.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, that PR hit some significant roadblocks. Have you reached out to the right folks on the .NET Core SDK and MSBuild teams to determine whether the solution here is the right one? I think the MSBuild Locator PR has pretty much stalled out. At a minimum, I'd start with @nguerrera.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were really two things with the locator PR:

  1. It doesn't work on Mac (nor all flavors of Linux). This is a technical issue about how it is assuming it can P/Invoke to already loaded hostfxr.dll. This does not apply to the alternate approach of parsing dotnet -info.

  2. It ties the locator to implementation details of the CLI. This applies to both approaches. See Support for locating .NET Core instances microsoft/MSBuildLocator#33 (comment)

As on the locator PR, I can let (2) go here if you agree to update this code if/when the layout changes. We do not consider the current placement of msbuild dlls in the CLI as a contract for tools outside the CLI.

src/Tools/dotnet-format/MSBuild/MSBuildEnvironment.cs Outdated Show resolved Hide resolved
src/Tools/dotnet-format/MSBuild/MSBuildEnvironment.cs Outdated Show resolved Hide resolved
@JoeRobich
Copy link
Member Author

JoeRobich commented Sep 20, 2018

@wli3, Not a lot has changed, but if you can just confirm that things still look OK I would be grateful.

@sharwell sharwell dismissed their stale review September 20, 2018 01:59

All feedback addressed

@MarcoRossignoli
Copy link
Member

MarcoRossignoli commented Sep 24, 2018

Is there an ETA for release?
Could be useful to have --verifyonly and --filter, i could use it as CI policy for our devs parsing output or return value.

/cc @JoeRobich @jaredpar

@JoeRobich
Copy link
Member Author

@MarcoRossignoli, If the request is to fail a build based on formatting issues, then there is a second PR for a Formatting Analyzer that you will be able to install through NuGet. You could configure the severity of that analyzer to fail your build and give you error locations.

@MarcoRossignoli
Copy link
Member

@JoeRobich thank's!I'll take a look!

Copy link
Member

@sharwell sharwell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pull request needs to be rewritten to avoid including dotnet-format.2.11.0-dev.nupkg in the source control history.


namespace Microsoft.CodeAnalysis.Tools.CodeFormatter
{
internal class CodeFormatter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 static class

private readonly IConsole _console;
private readonly LogLevel _logLevel;

private readonly IReadOnlyDictionary<LogLevel, ConsoleColor> _logLevelColorMap = new Dictionary<LogLevel, ConsoleColor>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 static

private readonly IConsole _console;
private readonly LogLevel _logLevel;

private readonly IReadOnlyDictionary<LogLevel, ConsoleColor> _logLevelColorMap = new Dictionary<LogLevel, ConsoleColor>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 ImmutableDictionary<LogLevel, ConsoleColor>

{
internal class EditorConfigOptionsApplier
{
private IReadOnlyList<(IOption, OptionStorageLocation, MethodInfo)> _formattingOptionsWithStorage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 readonly

{
internal class EditorConfigOptionsApplier
{
private IReadOnlyList<(IOption, OptionStorageLocation, MethodInfo)> _formattingOptionsWithStorage;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 ImmutableArray<>

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ Why not netcoreapp2.0 like our other projects?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, should be netcoreapp2.0 in dev16.0.x.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global tools only support netcoreapp2.1 and above

Copy link
Member

@sharwell sharwell Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global tools only support netcoreapp2.1 and above

😕 Which part of it? I would expect anything that supports netcoreapp2.1 to also support netcoreapp2.0, and also netcoreapp1.0 for that matter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: we are on netcoreapp2.1 now in master and netcoreapp2.0 is actually no longer even supported.

@wli3
Copy link

wli3 commented Oct 5, 2018

For global tools packaging concern, the current state is good. And global tools only support netcoreapp2.1 and up (due to the dependency on the framework dependent apphost added in runtime 2.1)

@JoeRobich
Copy link
Member Author

@sharwell I removed the nupkg from the commit history and addressed the open feedback. Could you give it another look?

@JoeRobich
Copy link
Member Author

Logged ubuntu_16_debug_prtest failure as #30374

@JoeRobich
Copy link
Member Author

retest ubuntu_16_debug_prtest please

Copy link
Member

@DustinCampbell DustinCampbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to handle localized dotnet --info output.


// LICENSING NOTE: The license for this file is from the originating
// source and not the general https://github.com/dotnet/roslyn license.
// See https://github.com/Microsoft/MSBuildLocator/blob/6631a6dbf9be72b2426e260c99dc0f345e79b8e5/src/MSBuildLocator/MSBuildLocator.cs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, that PR hit some significant roadblocks. Have you reached out to the right folks on the .NET Core SDK and MSBuild teams to determine whether the solution here is the right one? I think the MSBuild Locator PR has pretty much stalled out. At a minimum, I'd start with @nguerrera.

src/Tools/dotnet-format/MSBuild/MSBuildEnvironment.cs Outdated Show resolved Hide resolved
THIRD-PARTY-NOTICES.txt Show resolved Hide resolved
@sharwell sharwell dismissed their stale review October 9, 2018 11:37

Issues from review now addressed.

Copy link
Member

@DustinCampbell DustinCampbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! My meta feedback is that we need to get this code into the MSBuildLocator because this PR makes MSBuildWorkspace available to .NET Core applications but those applications still lack the connective tissue to make it work.

Copy link
Contributor

@nguerrera nguerrera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not review the entire PR, only the place where I was mentioned. I approve this with the understanding that we will look for a more permanent home in MSBuildLocator and also that this approval does not mean that the MSBuild.dll location in the SDK cannot be changed in future versions. The tool here (or better, MSBuildLocator) will need to react to such changes.

@JoeRobich JoeRobich merged commit 0f340a5 into dotnet:dev16.0.x Oct 10, 2018
@JoeRobich JoeRobich deleted the add-formatter-global-tool branch January 14, 2019 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.