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

WIP - Add fantomas formatting support #44801

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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
87 changes: 87 additions & 0 deletions src/BuiltInTools/dotnet-format/CodeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.Tools.Formatters;
using Microsoft.CodeAnalysis.Tools.Utilities;
using Microsoft.CodeAnalysis.Tools.Workspaces;
using Microsoft.CodeAnalysis.Tools;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools
Expand Down Expand Up @@ -279,5 +280,91 @@ private static async Task<Solution> RunCodeFormattersAsync(
formattableDocuments.AddRange(sourceGeneratedDocuments);
return (projectFileCount + sourceGeneratedDocuments.Count, formattableDocuments.ToImmutable());
}

public static bool AnyFSharpFiles(string directoryPath)
{
foreach (var file in Directory.EnumerateFiles(directoryPath, "*.*", SearchOption.AllDirectories))
{
if (file.EndsWith(".fs", StringComparison.OrdinalIgnoreCase) ||
file.EndsWith(".fsx", StringComparison.OrdinalIgnoreCase) ||
file.EndsWith(".fsproj", StringComparison.OrdinalIgnoreCase))
{
return true; // Early return if any F# file is found
}
}

return false; // No F# files found after recursive search
}

// using Microsoft.DotNet.Tools.Tool.List; not possible at the moment,
// will replicate records privately with the only data needed atm
private record ToolListJsonContractInternal(string PackageId);
private record ToolListResponseInternal(ToolListJsonContractInternal[] Data);

public static async Task<bool> IsFantomasInstalled()
{
var processStartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "tool list --local --format json",
RedirectStandardOutput = true,
RedirectStandardError = true,
Copy link
Contributor

Choose a reason for hiding this comment

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

RedirectStandardError = true might cause a deadlock if the child process outputs a lot of text to standard error and the parent process doesn't read from process.StandardError.

Copy link
Author

Choose a reason for hiding this comment

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

shall we switch it off? or what's the best solution for this item?

UseShellExecute = false,
CreateNoWindow = true
};

using var process = new Process { StartInfo = processStartInfo };

process.Start();

var outputString = process.StandardOutput.ReadToEnd();

var result =
System.Text.Json.JsonSerializer.Deserialize<ToolListResponseInternal>(
outputString
);

await process.WaitForExitAsync();

return result is not null
&& result.Data.Any(r => r.PackageId.Contains("fantomas", StringComparison.OrdinalIgnoreCase));
Copy link
Author

Choose a reason for hiding this comment

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

deserializing json output

}

public static Task FantomasFormatAsync(FormatOptions formatOptions, ILogger logger)
{
var processStartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"fantomas {formatOptions.WorkspaceFilePath}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = new Process { StartInfo = processStartInfo };
process.Start();
var output = process.StandardOutput.ReadToEnd();

// log fantomas output
logger.LogInformation(output);
Copy link
Author

Choose a reason for hiding this comment

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

log fantomas output


return process.WaitForExitAsync();
}

public static void LogFantomasInstallationInstructions(ILogger logger)
{
var message =
"""
Formatting F# code is not natively supported; however, there is a community project called Fantomas that can format F# code.
You can find more information about Fantomas at https://fsprojects.github.io/fantomas/docs/.
You can install Fantomas via dotnet tools:

> dotnet new tool-manifest [ only if no previous manifest is installed ]
> dotnet tool install fantomas

""";
logger.LogInformation(message);
Copy link
Author

Choose a reason for hiding this comment

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

updated helper message for fantomas installation instructions

}
}
}
13 changes: 13 additions & 0 deletions src/BuiltInTools/dotnet-format/Commands/RootFormatCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ public override async Task<int> InvokeAsync(ParseResult parseResult, Cancellatio

formatOptions = formatOptions with { FixCategory = FixCategory.Whitespace | FixCategory.CodeStyle | FixCategory.Analyzers };

if (CodeFormatter.AnyFSharpFiles(formatOptions.WorkspaceFilePath))
{
var isFantomas = await CodeFormatter.IsFantomasInstalled();
if (!isFantomas)
{
CodeFormatter.LogFantomasInstallationInstructions(logger);
Comment on lines +64 to +67
Copy link
Contributor

Choose a reason for hiding this comment

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

If something attempts to run dotnet format on F# code, but Fantomas is not installed, then I think it should report an error and return a nonzero exit code, so that it is clear it did not work.

Copy link
Author

@jkone27 jkone27 Nov 18, 2024

Choose a reason for hiding this comment

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

actually many times F# and C# project can be mixed within a single solution, so i wouldnt report an hard error? C# code can still be formatted. in my case i often have e.g. F# unit tests or integ tests on aspnet and maybe few F# modules, but most projects are C#

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm… ok, but if the user specifies the path of a F#-only project without Fantomas installed, then that deserves at least a warning, no?

Copy link
Author

Choose a reason for hiding this comment

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

yes then i should log warning instead of info, right?

}
else
{
await CodeFormatter.FantomasFormatAsync(formatOptions, logger).ConfigureAwait(false);
}
}

return await FormatAsync(formatOptions, logger, cancellationToken).ConfigureAwait(false);
}
}
Expand Down
Loading