diff --git a/src/Buildalyzer/AnalyzerManager.cs b/src/Buildalyzer/AnalyzerManager.cs index 7d4eb565..b78adf3b 100644 --- a/src/Buildalyzer/AnalyzerManager.cs +++ b/src/Buildalyzer/AnalyzerManager.cs @@ -39,12 +39,14 @@ public class AnalyzerManager : IAnalyzerManager public SolutionFile SolutionFile { get; } + public List CompilerOptionsParsers { get; } + public AnalyzerManager(AnalyzerManagerOptions options = null) : this(null, options) { } - public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions options = null) + public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions? options = null) { options ??= new AnalyzerManagerOptions(); LoggerFactory = options.LoggerFactory; @@ -58,13 +60,15 @@ public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions options = foreach (ProjectInSolution projectInSolution in SolutionFile.ProjectsInOrder) { if (!SupportedProjectTypes.Contains(projectInSolution.ProjectType) - || (options?.ProjectFilter != null && !options.ProjectFilter(projectInSolution))) + || (options.ProjectFilter != null && !options.ProjectFilter(projectInSolution))) { continue; } GetProject(projectInSolution.AbsolutePath, projectInSolution); } } + + CompilerOptionsParsers = options.CompilerOptionsParsers; } public void SetGlobalProperty(string key, string value) diff --git a/src/Buildalyzer/AnalyzerManagerOptions.cs b/src/Buildalyzer/AnalyzerManagerOptions.cs index 12f1a627..c1ab407c 100644 --- a/src/Buildalyzer/AnalyzerManagerOptions.cs +++ b/src/Buildalyzer/AnalyzerManagerOptions.cs @@ -30,4 +30,9 @@ public TextWriter LogWriter LoggerFactory.AddProvider(new TextWriterLoggerProvider(value)); } } + + public List CompilerOptionsParsers { get; set; } = [ + CscOptionsParser.Instance, + VbcOptionsParser.Instance, + FscOptionsParser.Instance]; } diff --git a/src/Buildalyzer/AnalyzerResult.cs b/src/Buildalyzer/AnalyzerResult.cs index 7557a966..1d3d06dc 100644 --- a/src/Buildalyzer/AnalyzerResult.cs +++ b/src/Buildalyzer/AnalyzerResult.cs @@ -11,7 +11,7 @@ public class AnalyzerResult : IAnalyzerResult private readonly Dictionary _items = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Guid _projectGuid; - public CompilerCommand CompilerCommand { get; private set; } + public CompilerCommand CompilerCommand { get; internal set; } internal AnalyzerResult(string projectFilePath, AnalyzerManager manager, ProjectAnalyzer analyzer) { @@ -107,26 +107,6 @@ internal void ProcessProject(PropertiesAndItems propertiesAndItems) } } - internal void ProcessCscCommandLine(string commandLine, bool coreCompile) - { - // Some projects can have multiple Csc calls (see #92) so if this is the one inside CoreCompile use it, otherwise use the first - if (string.IsNullOrWhiteSpace(commandLine) || (CompilerCommand != null && !coreCompile)) - { - return; - } - CompilerCommand = Compiler.CommandLine.Parse(new FileInfo(ProjectFilePath).Directory, commandLine, CompilerLanguage.CSharp); - } - - internal void ProcessVbcCommandLine(string commandLine) - { - CompilerCommand = Compiler.CommandLine.Parse(new FileInfo(ProjectFilePath).Directory, commandLine, CompilerLanguage.VisualBasic); - } - - internal void ProcessFscCommandLine(string commandLine) - { - CompilerCommand = Compiler.CommandLine.Parse(new FileInfo(ProjectFilePath).Directory, commandLine, CompilerLanguage.FSharp); - } - private class ProjectItemItemSpecEqualityComparer : IEqualityComparer { public bool Equals(IProjectItem x, IProjectItem y) => x.ItemSpec.Equals(y.ItemSpec, StringComparison.OrdinalIgnoreCase); diff --git a/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs b/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs index c2f8f6d9..d7dceb3e 100644 --- a/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs +++ b/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs @@ -7,5 +7,5 @@ namespace Buildalyzer; public sealed record CSharpCompilerCommand : RoslynBasedCompilerCommand { /// - public override CompilerLanguage Language => CompilerLanguage.CSharp; + public override string Language => "C#"; } \ No newline at end of file diff --git a/src/Buildalyzer/Compiler/Compiler.cs b/src/Buildalyzer/Compiler/Compiler.cs deleted file mode 100644 index c146a34e..00000000 --- a/src/Buildalyzer/Compiler/Compiler.cs +++ /dev/null @@ -1,118 +0,0 @@ -#nullable enable - -using System.IO; -using Buildalyzer.IO; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.VisualBasic; - -namespace Buildalyzer; - -public static class Compiler -{ - public static class CommandLine - { - [Pure] - public static string[]? SplitCommandLineIntoArguments(string? commandLine, CompilerLanguage language) => language switch - { - CompilerLanguage.CSharp => RoslynCommandLineParser.SplitCommandLineIntoArguments(commandLine, "csc.dll", "csc.exe"), - CompilerLanguage.VisualBasic => RoslynCommandLineParser.SplitCommandLineIntoArguments(commandLine, "vbc.dll", "vbc.exe"), - CompilerLanguage.FSharp => FSharpCommandLineParser.SplitCommandLineIntoArguments(commandLine), - _ => throw new NotSupportedException($"The {language} language is not supported."), - }; - - [Pure] - public static CompilerCommand Parse(DirectoryInfo? baseDir, string commandLine, CompilerLanguage language) - { - var tokens = SplitCommandLineIntoArguments(commandLine, language) ?? throw new FormatException("Commandline could not be parsed."); - var location = new FileInfo(tokens[0]); - var args = tokens[1..]; - - return Parse(baseDir?.ToString(), location.Directory?.ToString(), args, language) with - { - Text = commandLine, - CompilerLocation = location, - Arguments = args.ToImmutableArray(), - }; - - static CompilerCommand Parse(string? baseDir, string? root, string[] args, CompilerLanguage language) => language switch - { - CompilerLanguage.CSharp => CSharpParser.Parse(args, baseDir, root), - CompilerLanguage.VisualBasic => VisualBasicParser.Parse(args, baseDir, root), - CompilerLanguage.FSharp => FSharpParser.Parse(args), - _ => throw new NotSupportedException($"The {language} language is not supported."), - }; - } - } - - private static class CSharpParser - { - [Pure] - public static CSharpCompilerCommand Parse(string[] args, string? baseDir, string? root) - { - var arguments = CSharpCommandLineParser.Default.Parse(args, baseDir, root); - var command = new CSharpCompilerCommand() - { - CommandLineArguments = arguments, - }; - return RoslynParser.Enrich(command, arguments); - } - } - - private static class VisualBasicParser - { - [Pure] - public static VisualBasicCompilerCommand Parse(string[] args, string? baseDir, string? root) - { - var arguments = VisualBasicCommandLineParser.Default.Parse(args, baseDir, root); - var command = new VisualBasicCompilerCommand() - { - CommandLineArguments = arguments, - PreprocessorSymbols = arguments.ParseOptions.PreprocessorSymbols.ToImmutableDictionary(), - }; - return RoslynParser.Enrich(command, arguments); - } - } - - private static class FSharpParser - { - [Pure] - public static FSharpCompilerCommand Parse(string[] args) - { - var sourceFiles = args.Where(a => a[0] != '-').Select(IOPath.Parse); - var preprocessorSymbolNames = args.Where(a => a.StartsWith("--define:")).Select(a => a[9..]); - var metadataReferences = args.Where(a => a.StartsWith("-r:")).Select(a => a[3..]); - - return new() - { - MetadataReferences = metadataReferences.ToImmutableArray(), - PreprocessorSymbolNames = preprocessorSymbolNames.ToImmutableArray(), - SourceFiles = sourceFiles.ToImmutableArray(), - }; - } - } - - private static class RoslynParser - { - public static TCommand Enrich(TCommand command, CommandLineArguments arguments) - where TCommand : CompilerCommand - - => command with - { - AnalyzerReferences = arguments.AnalyzerReferences.Select(AsIOPath).ToImmutableArray(), - AnalyzerConfigPaths = arguments.AnalyzerConfigPaths.Select(IOPath.Parse).ToImmutableArray(), - MetadataReferences = arguments.MetadataReferences.Select(m => m.Reference).ToImmutableArray(), - PreprocessorSymbolNames = arguments.ParseOptions.PreprocessorSymbolNames.ToImmutableArray(), - - SourceFiles = arguments.SourceFiles.Select(AsIOPath).ToImmutableArray(), - AdditionalFiles = arguments.AdditionalFiles.Select(AsIOPath).ToImmutableArray(), - EmbeddedFiles = arguments.EmbeddedFiles.Select(AsIOPath).ToImmutableArray(), - }; - } - - [Pure] - internal static IOPath AsIOPath(CommandLineAnalyzerReference file) => IOPath.Parse(file.FilePath); - - [Pure] - internal static IOPath AsIOPath(CommandLineSourceFile file) => IOPath.Parse(file.Path); -} diff --git a/src/Buildalyzer/Compiler/CompilerCommand.cs b/src/Buildalyzer/Compiler/CompilerCommand.cs index 69b6e6aa..b7f30719 100644 --- a/src/Buildalyzer/Compiler/CompilerCommand.cs +++ b/src/Buildalyzer/Compiler/CompilerCommand.cs @@ -10,7 +10,7 @@ namespace Buildalyzer; public abstract record CompilerCommand { /// The compiler lanuague. - public abstract CompilerLanguage Language { get; } + public abstract string Language { get; } /// The original text of the compiler command. public string Text { get; init; } = string.Empty; diff --git a/src/Buildalyzer/Compiler/CompilerLanguage.cs b/src/Buildalyzer/Compiler/CompilerLanguage.cs deleted file mode 100644 index afd76cf7..00000000 --- a/src/Buildalyzer/Compiler/CompilerLanguage.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Buildalyzer; - -/// The compiler language. -public enum CompilerLanguage -{ - /// None. - None = 0, - - /// C#. - CSharp = 1, - - /// VB.NET. - VisualBasic = 2, - - /// F#. - FSharp = 3, -} diff --git a/src/Buildalyzer/Compiler/CscOptionsParser.cs b/src/Buildalyzer/Compiler/CscOptionsParser.cs new file mode 100644 index 00000000..49835bad --- /dev/null +++ b/src/Buildalyzer/Compiler/CscOptionsParser.cs @@ -0,0 +1,52 @@ +using System.IO; +using Buildalyzer.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Buildalyzer; + +/// +/// A parser for the csc compiler options (Roslyn). +/// +public sealed class CscOptionsParser : ICompilerOptionsParser +{ + /// + /// A singleton instance of the parser. + /// + public static CscOptionsParser Instance { get; } = new CscOptionsParser(); + + public string Language => "C#"; + + private CscOptionsParser() + { + } + + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context) => + eventArgs is TaskCommandLineEventArgs cmd + && string.Equals(cmd.TaskName, "Csc", StringComparison.OrdinalIgnoreCase); + + public CompilerCommand? Parse(string commandLine, CompilerOptionsContext context) + { + if (string.IsNullOrWhiteSpace(commandLine) || (!context.IsFirstInvocation && !context.CoreCompile)) + { + return null; + } + + var tokens = RoslynParser.SplitCommandLineIntoArguments(commandLine, "csc.dll", "csc.exe") + ?? throw new FormatException("Commandline could not be parsed."); + var location = new FileInfo(tokens[0]); + var args = tokens[1..]; + + var arguments = CSharpCommandLineParser.Default.Parse(args, context.BaseDirectory?.ToString(), location.Directory?.ToString()); + var command = new CSharpCompilerCommand() + { + CommandLineArguments = arguments, + Text = commandLine, + CompilerLocation = location, + Arguments = args.ToImmutableArray(), + }; + return RoslynParser.Enrich(command, arguments); + } +} diff --git a/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs b/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs index 398b177f..323fd053 100644 --- a/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs +++ b/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs @@ -6,5 +6,5 @@ namespace Buildalyzer; public sealed record FSharpCompilerCommand : CompilerCommand { /// - public override CompilerLanguage Language => CompilerLanguage.FSharp; + public override string Language => "F#"; } diff --git a/src/Buildalyzer/Compiler/FSharpCommandLineParser.cs b/src/Buildalyzer/Compiler/FSharpParser.cs similarity index 97% rename from src/Buildalyzer/Compiler/FSharpCommandLineParser.cs rename to src/Buildalyzer/Compiler/FSharpParser.cs index 54b913bb..a8444a75 100644 --- a/src/Buildalyzer/Compiler/FSharpCommandLineParser.cs +++ b/src/Buildalyzer/Compiler/FSharpParser.cs @@ -2,7 +2,7 @@ namespace Buildalyzer; -internal static class FSharpCommandLineParser +internal static class FSharpParser { [Pure] public static string[]? SplitCommandLineIntoArguments(string? commandLine) diff --git a/src/Buildalyzer/Compiler/FscOptionsParser.cs b/src/Buildalyzer/Compiler/FscOptionsParser.cs new file mode 100644 index 00000000..268a0437 --- /dev/null +++ b/src/Buildalyzer/Compiler/FscOptionsParser.cs @@ -0,0 +1,52 @@ +using System.IO; +using Buildalyzer.IO; +using Microsoft.Build.Framework; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace Buildalyzer; + +/// +/// A parser for the fsc compiler options (F#). +/// +public sealed class FscOptionsParser : ICompilerOptionsParser +{ + /// + /// A singleton instance of the parser. + /// + public static FscOptionsParser Instance { get; } = new FscOptionsParser(); + + public string Language => "F#"; + + private FscOptionsParser() + { + } + + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context) => + eventArgs.SenderName?.Equals("Fsc", StringComparison.OrdinalIgnoreCase) == true + && !string.IsNullOrWhiteSpace(eventArgs.Message) + && context.TargetStack.Any(x => x.TargetName == "CoreCompile") + && context.IsFirstInvocation; + + public CompilerCommand? Parse(string commandLine, CompilerOptionsContext context) + { + var tokens = FSharpParser.SplitCommandLineIntoArguments(commandLine) + ?? throw new FormatException("Commandline could not be parsed."); + + var location = new FileInfo(tokens[0]); + var args = tokens[1..]; + + var sourceFiles = args.Where(a => a[0] != '-').Select(IOPath.Parse); + var preprocessorSymbolNames = args.Where(a => a.StartsWith("--define:")).Select(a => a[9..]); + var metadataReferences = args.Where(a => a.StartsWith("-r:")).Select(a => a[3..]); + + return new FSharpCompilerCommand() + { + MetadataReferences = metadataReferences.ToImmutableArray(), + PreprocessorSymbolNames = preprocessorSymbolNames.ToImmutableArray(), + SourceFiles = sourceFiles.ToImmutableArray(), + Text = commandLine, + CompilerLocation = location, + Arguments = args.ToImmutableArray(), + }; + } +} diff --git a/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs b/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs new file mode 100644 index 00000000..a5d0a358 --- /dev/null +++ b/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs @@ -0,0 +1,59 @@ +using System.IO; +using Microsoft.Build.Framework; + +namespace Buildalyzer; + +/// +/// Parses compiler options from a string. +/// +public interface ICompilerOptionsParser +{ + /// + /// The name of the language that this parser supports. + /// + public string Language { get; } + + /// + /// Checks, if the given invocation is one for the language compiler that this parser supports. + /// + /// The event sender. + /// The build event arguments. + /// Contextual information for the parser. + /// True, if this parser supports the event and should be invoked. + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context); + + /// + /// Parses the compiler options from the given command line. + /// + /// The command line to parse. + /// Contextual information for the parser. + /// The parsed . + CompilerCommand? Parse(string commandLine, CompilerOptionsContext context); +} + +/// +/// Contextual information for parsing compiler options. +/// +public readonly struct CompilerOptionsContext +{ + /// + /// True, if this is the first compiler invocation. + /// False, if one is already found. + /// + public bool IsFirstInvocation { get; init; } + + /// + /// True, if this is a call inside CoreCompile. + /// + public bool CoreCompile { get; init; } + + /// + /// The base directory of the project. + /// + public DirectoryInfo? BaseDirectory { get; init; } + + /// + /// The target stack. + /// + public IReadOnlyCollection TargetStack { get; init; } +} diff --git a/src/Buildalyzer/Compiler/RoslynCommandLineParser.cs b/src/Buildalyzer/Compiler/RoslynCommandLineParser.cs deleted file mode 100644 index dbbed201..00000000 --- a/src/Buildalyzer/Compiler/RoslynCommandLineParser.cs +++ /dev/null @@ -1,28 +0,0 @@ -#nullable enable - -using Microsoft.CodeAnalysis; - -namespace Buildalyzer; - -internal static class RoslynCommandLineParser -{ - [Pure] - public static string[]? SplitCommandLineIntoArguments(string? commandLine, params string[] execs) - => Split(CommandLineParser.SplitCommandLineIntoArguments(commandLine ?? string.Empty, removeHashComments: true).ToArray(), execs); - - [Pure] - private static string[]? Split(string[] args, string[] execs) - { - foreach (var exec in execs) - { - for (var i = 0; i < args.Length - 1; i++) - { - if (args[i].EndsWith(exec, StringComparison.OrdinalIgnoreCase)) - { - return args[i..]; - } - } - } - return null; - } -} diff --git a/src/Buildalyzer/Compiler/RoslynParser.cs b/src/Buildalyzer/Compiler/RoslynParser.cs new file mode 100644 index 00000000..98de04d2 --- /dev/null +++ b/src/Buildalyzer/Compiler/RoslynParser.cs @@ -0,0 +1,49 @@ +#nullable enable + +using Buildalyzer.IO; +using Microsoft.CodeAnalysis; + +namespace Buildalyzer; + +internal static class RoslynParser +{ + public static TCommand Enrich(TCommand command, CommandLineArguments arguments) + where TCommand : CompilerCommand + => command with + { + AnalyzerReferences = arguments.AnalyzerReferences.Select(AsIOPath).ToImmutableArray(), + AnalyzerConfigPaths = arguments.AnalyzerConfigPaths.Select(IOPath.Parse).ToImmutableArray(), + MetadataReferences = arguments.MetadataReferences.Select(m => m.Reference).ToImmutableArray(), + PreprocessorSymbolNames = arguments.ParseOptions.PreprocessorSymbolNames.ToImmutableArray(), + + SourceFiles = arguments.SourceFiles.Select(AsIOPath).ToImmutableArray(), + AdditionalFiles = arguments.AdditionalFiles.Select(AsIOPath).ToImmutableArray(), + EmbeddedFiles = arguments.EmbeddedFiles.Select(AsIOPath).ToImmutableArray(), + }; + + [Pure] + internal static IOPath AsIOPath(CommandLineAnalyzerReference file) => IOPath.Parse(file.FilePath); + + [Pure] + internal static IOPath AsIOPath(CommandLineSourceFile file) => IOPath.Parse(file.Path); + + [Pure] + public static string[]? SplitCommandLineIntoArguments(string? commandLine, params string[] execs) + => Split(CommandLineParser.SplitCommandLineIntoArguments(commandLine ?? string.Empty, removeHashComments: true).ToArray(), execs); + + [Pure] + private static string[]? Split(string[] args, string[] execs) + { + foreach (var exec in execs) + { + for (var i = 0; i < args.Length - 1; i++) + { + if (args[i].EndsWith(exec, StringComparison.OrdinalIgnoreCase)) + { + return args[i..]; + } + } + } + return null; + } +} diff --git a/src/Buildalyzer/Compiler/VbcOptionsParser.cs b/src/Buildalyzer/Compiler/VbcOptionsParser.cs new file mode 100644 index 00000000..e5a4fa7d --- /dev/null +++ b/src/Buildalyzer/Compiler/VbcOptionsParser.cs @@ -0,0 +1,47 @@ +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace Buildalyzer; + +/// +/// A parser for the vbc compiler options (Roslyn). +/// +public sealed class VbcOptionsParser : ICompilerOptionsParser +{ + /// + /// A singleton instance of the parser. + /// + public static VbcOptionsParser Instance { get; } = new VbcOptionsParser(); + + public string Language => "VB.NET"; + + private VbcOptionsParser() + { + } + + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context) => + eventArgs is TaskCommandLineEventArgs cmd + && string.Equals(cmd.TaskName, "Vbc", StringComparison.OrdinalIgnoreCase); + + public CompilerCommand? Parse(string commandLine, CompilerOptionsContext context) + { + var tokens = RoslynParser.SplitCommandLineIntoArguments(commandLine, "vbc.dll", "vbc.exe") + ?? throw new FormatException("Commandline could not be parsed."); + var location = new FileInfo(tokens[0]); + var args = tokens[1..]; + + var arguments = VisualBasicCommandLineParser.Default.Parse(args, context.BaseDirectory?.ToString(), location.Directory?.ToString()); + var command = new VisualBasicCompilerCommand() + { + CommandLineArguments = arguments, + PreprocessorSymbols = arguments.ParseOptions.PreprocessorSymbols.ToImmutableDictionary(), + Text = commandLine, + CompilerLocation = location, + Arguments = args.ToImmutableArray(), + }; + return RoslynParser.Enrich(command, arguments); + } +} diff --git a/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs b/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs index cfc5e89d..517a96d6 100644 --- a/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs +++ b/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs @@ -7,7 +7,7 @@ namespace Buildalyzer; public sealed record VisualBasicCompilerCommand : RoslynBasedCompilerCommand { /// - public override CompilerLanguage Language => CompilerLanguage.VisualBasic; + public override string Language => "VB.NET"; /// public ImmutableDictionary? PreprocessorSymbols { get; init; } diff --git a/src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs b/src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs deleted file mode 100644 index 1f4e6ed6..00000000 --- a/src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Buildalyzer; - -internal static class CompilerLanguageExtensions -{ - /// Represents the as (DEBUG) display string. - [Pure] - public static string Display(this CompilerLanguage language) => language switch - { - CompilerLanguage.CSharp => "C#", - CompilerLanguage.FSharp => "F#", - CompilerLanguage.VisualBasic => "VB.NET", - _ => language.ToString(), - }; -} diff --git a/src/Buildalyzer/Logging/EventProcessor.cs b/src/Buildalyzer/Logging/EventProcessor.cs index 9ac7ed26..67174d96 100644 --- a/src/Buildalyzer/Logging/EventProcessor.cs +++ b/src/Buildalyzer/Logging/EventProcessor.cs @@ -1,4 +1,6 @@ extern alias StructuredLogger; + +using System.IO; using Microsoft.Build.Framework; using Microsoft.Extensions.Logging; @@ -148,26 +150,25 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) AnalyzerResult result = _currentResult.Count == 0 ? null : _currentResult.Peek(); if (result is object) { - // Process the command line arguments for the Fsc task - if (e.SenderName?.Equals("Fsc", StringComparison.OrdinalIgnoreCase) == true - && !string.IsNullOrWhiteSpace(e.Message) - && _targetStack.Any(x => x.TargetName == "CoreCompile") - && result.CompilerCommand is null) - { - result.ProcessFscCommandLine(e.Message); - } - - // Process the command line arguments for the Csc task - if (e is TaskCommandLineEventArgs cmd - && string.Equals(cmd.TaskName, "Csc", StringComparison.OrdinalIgnoreCase)) + CompilerOptionsContext context = new() { - result.ProcessCscCommandLine(cmd.CommandLine, _targetStack.Any(x => x.TargetName == "CoreCompile")); - } + IsFirstInvocation = result.CompilerCommand is null, + CoreCompile = _targetStack.Any(x => x.TargetName == "CoreCompile"), + BaseDirectory = new FileInfo(result.ProjectFilePath).Directory, + TargetStack = _targetStack, + }; - if (e is TaskCommandLineEventArgs cmdVbc && - string.Equals(cmdVbc.TaskName, "Vbc", StringComparison.OrdinalIgnoreCase)) + foreach (ICompilerOptionsParser parser in _manager.CompilerOptionsParsers) { - result.ProcessVbcCommandLine(cmdVbc.CommandLine); + if (parser.IsSupportedInvocation(sender, e, context)) + { + CompilerCommand? command = parser.Parse(e.Message, context); + if (command is not null) + { + result.CompilerCommand = command; + break; + } + } } } } diff --git a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs index 0e882f4a..1a9154ae 100644 --- a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs +++ b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs @@ -39,12 +39,16 @@ public void Parse_CS() + "Startup.cs " + "/warnaserror+:NU1605"; - var command = Buildalyzer.Compiler.CommandLine.Parse(new("."), commandline, CompilerLanguage.CSharp); + var command = CscOptionsParser.Instance.Parse(commandline, new CompilerOptionsContext + { + BaseDirectory = new("."), + IsFirstInvocation = true, + }); command.Should().BeEquivalentTo(new { Text = commandline, - Language = CompilerLanguage.CSharp, + Language = "C#", PreprocessorSymbolNames = new[] { "TRACE", "DEBUG", "NETCOREAPP", "NETCOREAPP3_1", "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER", "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP2_1_OR_GREATER", "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP3_0_OR_GREATER", "NETCOREAPP3_1_OR_GREATER" }, SourceFiles = Files(".\\Program.cs", ".\\Startup.cs"), AnalyzerConfigPaths = Files(".\\code\\buildalyzer\\.editorconfig", ".\\code\\buildalyzer\\tests\\.editorconfig"), @@ -67,12 +71,16 @@ public void Parse_VB() + "\"obj\\Debug\\net6.0\\VisualBasicNetConsoleApp.AssemblyInfo.vb\" " + "/warnaserror+:NU1605"; - var command = Buildalyzer.Compiler.CommandLine.Parse(new("."), commandline, CompilerLanguage.VisualBasic); + var command = VbcOptionsParser.Instance.Parse(commandline, new CompilerOptionsContext + { + BaseDirectory = new("."), + IsFirstInvocation = true, + }); command.Should().BeEquivalentTo(new { Text = commandline, - Language = CompilerLanguage.VisualBasic, + Language = "VB.NET", PreprocessorSymbolNames = new[] { "TRACE", "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP1_0_OR_GREATER", "NET6_0", "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP3_0_OR_GREATER", "_MyType", "NETCOREAPP", "NETCOREAPP2_1_OR_GREATER", "NET6_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER", "CONFIG", "NET", "PLATFORM", "NETCOREAPP3_1_OR_GREATER", "DEBUG", "NET5_0_OR_GREATER", "VBC_VER", "TARGET" }, SourceFiles = Files(".\\Configuration.vb", ".\\Program.vb", ".\\obj\\Debug\\net6.0\\.NETCoreApp,Version=v6.0.AssemblyAttributes.vb", ".\\obj\\Debug\\net6.0\\VisualBasicNetConsoleApp.AssemblyInfo.vb"), AnalyzerReferences = Files("C:\\Program Files\\dotnet\\sdk\\8.0.200\\Sdks\\Microsoft.NET.Sdk\\targets\\..\\analyzers\\Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.dll", "C:\\Program Files\\dotnet\\sdk\\8.0.200\\Sdks\\Microsoft.NET.Sdk\\targets\\..\\analyzers\\Microsoft.CodeAnalysis.NetAnalyzers.dll"), @@ -121,13 +129,17 @@ public void Parse_FSharp() obj\Debug\netcoreapp3.1\FSharpProject.AssemblyInfo.fs Program.fs"; - var command = Buildalyzer.Compiler.CommandLine.Parse(new("."), commandLine, CompilerLanguage.FSharp); + var command = FscOptionsParser.Instance.Parse(commandLine, new CompilerOptionsContext + { + BaseDirectory = new("."), + IsFirstInvocation = true, + }); var options = GetFSharpParsingOptions(commandLine); command.Should().BeEquivalentTo(new { Text = commandLine, - Language = CompilerLanguage.FSharp, + Language = "F#", PreprocessorSymbolNames = new[] { "NETCOREAPP3_1_OR_GREATER", "NETCOREAPP3_0_OR_GREATER", "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP2_1_OR_GREATER", "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER", "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP3_1", "NETCOREAPP", "DEBUG", "TRACE" }, SourceFiles = Files("obj\\Debug\\netcoreapp3.1\\.NETCoreApp,Version=v3.1.AssemblyAttributes.fs", "obj\\Debug\\netcoreapp3.1\\FSharpProject.AssemblyInfo.fs", "Program.fs"), MetadataReferences = Array("C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.CSharp.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.VisualBasic.Core.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.VisualBasic.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.Win32.Primitives.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\mscorlib.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\netstandard.dll"), @@ -141,7 +153,7 @@ public void Parse_FSharp() static FSharpParsingOptions GetFSharpParsingOptions(string commandLine) { var checker = FSharpChecker.Instance; - var result = checker.GetParsingOptionsFromCommandLineArgs(ListModule.OfArray(FSharpCommandLineParser.SplitCommandLineIntoArguments(commandLine)), isInteractive: true, isEditing: false); + var result = checker.GetParsingOptionsFromCommandLineArgs(ListModule.OfArray(FSharpParser.SplitCommandLineIntoArguments(commandLine)), isInteractive: true, isEditing: false); return result.Item1; } }