提交 3de55924 编写于 作者: M Manish Vasani

Add a new command line compiler switch "/reportanalyzer" to report analyzer...

Add a new command line compiler switch "/reportanalyzer" to report analyzer execution times. Output is grouped by analyzer assemblies and is displayed in descending order of execution times.

NOTE: We do not display the total build time or the ratios between build time and analyzer execution time as the the actual wall clock time for analyzer execution is likely lesser due to multithreaded analyzer execution.
上级 00a59192
......@@ -4407,6 +4407,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
diagnostics.
/errorlog:<file> Specify a file to log all compiler and analyzer
diagnostics.
/reportanalyzer Report additional analyzer information, such as
execution time.
- LANGUAGE -
/checked[+|-] Generate overflow checks
......
......@@ -103,6 +103,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
List<string> features = new List<string>();
string runtimeMetadataVersion = null;
bool errorEndLocation = false;
bool reportAnalyzer = false;
CultureInfo preferredUILang = null;
string touchedFilesPath = null;
var sqmSessionGuid = Guid.Empty;
......@@ -892,6 +893,10 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
errorEndLocation = true;
continue;
case "reportanalyzer":
reportAnalyzer = true;
continue;
case "nostdlib":
case "nostdlib+":
if (value != null)
......@@ -1111,7 +1116,8 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
PrintFullPaths = printFullPaths,
ShouldIncludeErrorEndLocation = errorEndLocation,
PreferredUILang = preferredUILang,
SqmSessionGuid = sqmSessionGuid
SqmSessionGuid = sqmSessionGuid,
ReportAnalyzer = reportAnalyzer
};
}
......
......@@ -6563,6 +6563,32 @@ public void ErrorLineEnd()
Assert.Equal(stringStart, text.Substring(0, stringStart.Length));
}
[Fact]
public void ReportAnalyzer()
{
var parsedArgs1 = DefaultParse(new[] { "a.cs", "/reportanalyzer" }, _baseDirectory);
Assert.True(parsedArgs1.ReportAnalyzer);
var parsedArgs2 = DefaultParse(new[] { "a.cs", "" }, _baseDirectory);
Assert.False(parsedArgs2.ReportAnalyzer);
}
[Fact]
public void ReportAnalyzerOutput()
{
var srcFile = Temp.CreateFile().WriteAllText(@"class C {}");
var srcDirectory = Path.GetDirectoryName(srcFile.Path);
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var csc = new MockCSharpCompiler(null, srcDirectory, new[] { "/reportanalyzer", "/t:library", "/a:" + Assembly.GetExecutingAssembly().Location, srcFile.Path });
var exitCode = csc.Run(outWriter);
Assert.Equal(0, exitCode);
var output = outWriter.ToString();
Assert.Contains(CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader, output, StringComparison.Ordinal);
Assert.Contains(new WarningDiagnosticAnalyzer().ToString(), output, StringComparison.Ordinal);
CleanupAllGeneratedFiles(srcFile.Path);
}
[Fact]
public void ErrorPathsFromLineDirectives()
{
......
......@@ -8,6 +8,7 @@
using System.Threading;
using Microsoft.CodeAnalysis.Collections;
using Roslyn.Utilities;
using System.Collections.Concurrent;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -26,7 +27,8 @@ internal class AnalyzerExecutor
private readonly AnalyzerManager _analyzerManager;
private readonly Func<DiagnosticAnalyzer, bool> _isCompilerAnalyzer;
private readonly ImmutableDictionary<DiagnosticAnalyzer, object> _singleThreadedAnalyzerToGateMap;
private readonly ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan> _analyzerExecutionTimeMapOpt;
private readonly CancellationToken _cancellationToken;
/// <summary>
......@@ -45,6 +47,7 @@ internal class AnalyzerExecutor
/// <param name="singleThreadedAnalyzerToGateMap">Map from non-thread safe analyzers to unique gate objects.
/// All analyzer callbacks for these analyzers will be guarded with a lock on the gate.
/// </param>
/// <param name="logExecutionTime">Flag indicating whether we need to log analyzer execution time.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static AnalyzerExecutor Create(
Compilation compilation,
......@@ -54,9 +57,10 @@ internal class AnalyzerExecutor
Func<DiagnosticAnalyzer, bool> isCompilerAnalyzer,
AnalyzerManager analyzerManager,
ImmutableDictionary<DiagnosticAnalyzer, object> singleThreadedAnalyzerToGateMap = default(ImmutableDictionary<DiagnosticAnalyzer, object>),
bool logExecutionTime = false,
CancellationToken cancellationToken = default(CancellationToken))
{
return new AnalyzerExecutor(compilation, analyzerOptions, addDiagnostic, onAnalyzerException, isCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, cancellationToken);
return new AnalyzerExecutor(compilation, analyzerOptions, addDiagnostic, onAnalyzerException, isCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, logExecutionTime, cancellationToken);
}
/// <summary>
......@@ -67,11 +71,13 @@ internal class AnalyzerExecutor
/// Delegate can do custom tasks such as report the given analyzer exception diagnostic, report a non-fatal watson for the exception, etc.
/// </param>
/// <param name="analyzerManager">Analyzer manager to fetch supported diagnostics.</param>
/// <param name="logExecutionTime">Flag indicating whether we need to log analyzer execution time.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static AnalyzerExecutor CreateForSupportedDiagnostics(
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
AnalyzerManager analyzerManager,
CancellationToken cancellationToken)
bool logExecutionTime = false,
CancellationToken cancellationToken = default(CancellationToken))
{
return new AnalyzerExecutor(
compilation: null,
......@@ -81,6 +87,7 @@ internal class AnalyzerExecutor
singleThreadedAnalyzerToGateMap: ImmutableDictionary<DiagnosticAnalyzer, object>.Empty,
onAnalyzerException: onAnalyzerException,
analyzerManager: analyzerManager,
logExecutionTime: logExecutionTime,
cancellationToken: cancellationToken);
}
......@@ -92,6 +99,7 @@ internal class AnalyzerExecutor
Func<DiagnosticAnalyzer, bool> isCompilerAnalyzer,
AnalyzerManager analyzerManager,
ImmutableDictionary<DiagnosticAnalyzer, object> singleThreadedAnalyzerToGateMap,
bool logExecutionTime,
CancellationToken cancellationToken)
{
_compilation = compilation;
......@@ -101,6 +109,7 @@ internal class AnalyzerExecutor
_isCompilerAnalyzer = isCompilerAnalyzer;
_analyzerManager = analyzerManager;
_singleThreadedAnalyzerToGateMap = singleThreadedAnalyzerToGateMap ?? ImmutableDictionary<DiagnosticAnalyzer, object>.Empty;
_analyzerExecutionTimeMapOpt = logExecutionTime ? new ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan>() : null;
_cancellationToken = cancellationToken;
}
......@@ -108,6 +117,7 @@ internal class AnalyzerExecutor
internal AnalyzerOptions AnalyzerOptions => _analyzerOptions;
internal CancellationToken CancellationToken => _cancellationToken;
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> OnAnalyzerException => _onAnalyzerException;
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes => _analyzerExecutionTimeMapOpt.ToImmutableDictionary();
/// <summary>
/// Executes the <see cref="DiagnosticAnalyzer.Initialize(AnalysisContext)"/> for the given analyzer.
......@@ -481,30 +491,39 @@ internal void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action analyz
{
lock(gate)
{
ExecuteAndCatchIfThrows(analyzer, analyze, _onAnalyzerException, _cancellationToken);
ExecuteAndCatchIfThrows_NoLock(analyzer, analyze);
}
}
else
{
ExecuteAndCatchIfThrows(analyzer, analyze, _onAnalyzerException, _cancellationToken);
ExecuteAndCatchIfThrows_NoLock(analyzer, analyze);
}
}
private static void ExecuteAndCatchIfThrows(
DiagnosticAnalyzer analyzer,
Action analyze,
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
CancellationToken cancellationToken)
private void ExecuteAndCatchIfThrows_NoLock(DiagnosticAnalyzer analyzer, Action analyze)
{
try
{
Stopwatch timer = null;
if (_analyzerExecutionTimeMapOpt != null)
{
timer = Stopwatch.StartNew();
}
analyze();
if (timer != null)
{
timer.Stop();
_analyzerExecutionTimeMapOpt.AddOrUpdate(analyzer, timer.Elapsed, (a, accumulatedTime) => accumulatedTime + timer.Elapsed);
}
}
catch (Exception e) when (!IsCanceled(e, cancellationToken))
catch (Exception e) when (!IsCanceled(e, _cancellationToken))
{
// Diagnostic for analyzer exception.
var diagnostic = GetAnalyzerExceptionDiagnostic(analyzer, e);
onAnalyzerException(e, analyzer, diagnostic);
_onAnalyzerException(e, analyzer, diagnostic);
}
}
......
......@@ -276,7 +276,7 @@ private static void TestDescriptorIsExceptionSafeCore(DiagnosticDescriptor descr
var analyzer = new MyAnalyzer(descriptor);
var exceptionDiagnostics = new List<Diagnostic>();
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = (ex, a, diag) => exceptionDiagnostics.Add(diag);
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance, CancellationToken.None);
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance);
var descriptors = AnalyzerManager.Instance.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor);
Assert.Equal(1, descriptors.Length);
......
......@@ -585,8 +585,7 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens
commandLine.AppendSwitchIfNotNull("/ruleset:", this.CodeAnalysisRuleSet);
commandLine.AppendSwitchIfNotNull("/errorlog:", this.ErrorLog);
commandLine.AppendSwitchIfNotNull("/subsystemversion:", this.SubsystemVersion);
// TODO: uncomment the below line once "/reportanalyzer" switch is added to compiler.
//commandLine.AppendWhenTrue("/reportanalyzer", this._store, "ReportAnalyzer");
commandLine.AppendWhenTrue("/reportanalyzer", this._store, "ReportAnalyzer");
// If the strings "LogicalName" or "Access" ever change, make sure to search/replace everywhere in vsproject.
commandLine.AppendSwitchIfNotNull("/resource:", this.Resources, new string[] { "LogicalName", "Access" });
commandLine.AppendSwitchIfNotNull("/target:", this.TargetType);
......
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.0
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
......@@ -70,6 +70,33 @@ internal class CodeAnalysisResources {
}
}
/// <summary>
/// Looks up a localized string similar to Time (s).
/// </summary>
internal static string AnalyzerExecutionTimeColumnHeader {
get {
return ResourceManager.GetString("AnalyzerExecutionTimeColumnHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Analyzer.
/// </summary>
internal static string AnalyzerNameColumnHeader {
get {
return ResourceManager.GetString("AnalyzerNameColumnHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Total analyzer execution time: {0} seconds..
/// </summary>
internal static string AnalyzerTotalExecutionTime {
get {
return ResourceManager.GetString("AnalyzerTotalExecutionTime", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Argument cannot be empty..
/// </summary>
......@@ -665,6 +692,15 @@ internal class CodeAnalysisResources {
}
}
/// <summary>
/// Looks up a localized string similar to NOTE: Elapsed time may be less than analyzer execution time because analyzers can run concurrently..
/// </summary>
internal static string MultithreadedAnalyzerExecutionNote {
get {
return ResourceManager.GetString("MultithreadedAnalyzerExecutionNote", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name cannot be empty..
/// </summary>
......
......@@ -457,4 +457,16 @@
<data name="MissingKeepAlive" xml:space="preserve">
<value>Missing argument for '/keepalive' option.</value>
</data>
<data name="AnalyzerTotalExecutionTime" xml:space="preserve">
<value>Total analyzer execution time: {0} seconds.</value>
</data>
<data name="MultithreadedAnalyzerExecutionNote" xml:space="preserve">
<value>NOTE: Elapsed time may be less than analyzer execution time because analyzers can run concurrently.</value>
</data>
<data name="AnalyzerExecutionTimeColumnHeader" xml:space="preserve">
<value>Time (s)</value>
</data>
<data name="AnalyzerNameColumnHeader" xml:space="preserve">
<value>Analyzer</value>
</data>
</root>
\ No newline at end of file
......@@ -115,6 +115,11 @@ public abstract class CommandLineArguments
/// </summary>
public ImmutableArray<CommandLineSourceFile> AdditionalFiles { get; internal set; }
/// <value>
/// Report additional information related to analyzers, such as analyzer execution time.
/// </value>
public bool ReportAnalyzer { get; internal set; }
/// <summary>
/// If true, prepend the command line header logo during
/// <see cref="CommonCompiler.Run"/>.
......
......@@ -355,6 +355,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
CancellationTokenSource analyzerCts = null;
AnalyzerManager analyzerManager = null;
AnalyzerDriver analyzerDriver = null;
try
{
Func<ImmutableArray<Diagnostic>> getAnalyzerDiagnostics = null;
......@@ -365,7 +366,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
var analyzerExceptionDiagnostics = new ConcurrentSet<Diagnostic>();
Action<Diagnostic> addExceptionDiagnostic = diagnostic => analyzerExceptionDiagnostics.Add(diagnostic);
var analyzerOptions = new AnalyzerOptions(ImmutableArray<AdditionalText>.CastUp(additionalTextFiles));
var analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, analyzerOptions, analyzerManager, addExceptionDiagnostic, out compilation, analyzerCts.Token);
analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, analyzerOptions, analyzerManager, addExceptionDiagnostic, Arguments.ReportAnalyzer, out compilation, analyzerCts.Token);
getAnalyzerDiagnostics = () =>
{
......@@ -525,8 +526,16 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
{
analyzerCts.Cancel();
// Clear cached analyzer descriptors and unregister exception handlers hooked up to the LocalizableString fields of the associated descriptors.
analyzerManager.ClearAnalyzerState(analyzers);
if (analyzerManager != null)
{
// Clear cached analyzer descriptors and unregister exception handlers hooked up to the LocalizableString fields of the associated descriptors.
analyzerManager.ClearAnalyzerState(analyzers);
}
if (Arguments.ReportAnalyzer && analyzerDriver != null && compilation != null)
{
ReportAnalyzerExecutionTime(consoleOutput, analyzerDriver, Culture, compilation.Options.ConcurrentBuild);
}
}
}
......@@ -545,6 +554,71 @@ private ImmutableArray<AdditionalTextFile> ResolveAdditionalFilesFromArguments(L
return builder.ToImmutableArray();
}
private static void ReportAnalyzerExecutionTime(TextWriter consoleOutput, AnalyzerDriver analyzerDriver, CultureInfo culture, bool isConcurrentBuild)
{
Debug.Assert(analyzerDriver.AnalyzerExecutionTimes != null);
if (analyzerDriver.AnalyzerExecutionTimes.IsEmpty)
{
return;
}
var totalAnalyzerExecutionTime = analyzerDriver.AnalyzerExecutionTimes.Sum(kvp => kvp.Value.TotalSeconds);
Func<double, string> getFormattedTime = d => d.ToString("##0.000", culture);
consoleOutput.WriteLine();
consoleOutput.WriteLine(string.Format(CodeAnalysisResources.AnalyzerTotalExecutionTime, getFormattedTime(totalAnalyzerExecutionTime)));
if (isConcurrentBuild)
{
consoleOutput.WriteLine(CodeAnalysisResources.MultithreadedAnalyzerExecutionNote);
}
var analyzersByAssembly = analyzerDriver.AnalyzerExecutionTimes
.GroupBy(kvp => kvp.Key.GetType().GetTypeInfo().Assembly)
.OrderByDescending(kvp => kvp.Sum(entry => entry.Value.Ticks));
consoleOutput.WriteLine();
getFormattedTime = d => d < 0.001 ?
string.Format(culture, "{0,8:<0.000}", 0.001) :
string.Format(culture, "{0,8:##0.000}", d);
Func<int, string> getFormattedPercentage = i => string.Format("{0,5}", i < 1 ? "<1" : i.ToString());
Func<string, string> getFormattedAnalyzerName = s => " " + s;
// Table header
var analyzerTimeColumn = string.Format("{0,8}", CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader);
var analyzerPercentageColumn = string.Format("{0,5}", "%");
var analyzerNameColumn = getFormattedAnalyzerName(CodeAnalysisResources.AnalyzerNameColumnHeader);
consoleOutput.WriteLine(analyzerTimeColumn + analyzerPercentageColumn + analyzerNameColumn);
// Table rows grouped by assembly.
foreach (var analyzerGroup in analyzersByAssembly)
{
var executionTime = analyzerGroup.Sum(kvp => kvp.Value.TotalSeconds);
var percentage = (int)(executionTime * 100 / totalAnalyzerExecutionTime);
analyzerTimeColumn = getFormattedTime(executionTime);
analyzerPercentageColumn = getFormattedPercentage(percentage);
analyzerNameColumn = getFormattedAnalyzerName(analyzerGroup.Key.FullName);
consoleOutput.WriteLine(analyzerTimeColumn + analyzerPercentageColumn + analyzerNameColumn);
// Rows for each diagnostic analyzer in the assembly.
foreach (var kvp in analyzerGroup.OrderByDescending(kvp => kvp.Value))
{
executionTime = kvp.Value.TotalSeconds;
percentage = (int)(executionTime * 100 / totalAnalyzerExecutionTime);
analyzerTimeColumn = getFormattedTime(executionTime);
analyzerPercentageColumn = getFormattedPercentage(percentage);
analyzerNameColumn = getFormattedAnalyzerName(" " + kvp.Key.ToString());
consoleOutput.WriteLine(analyzerTimeColumn + analyzerPercentageColumn + analyzerNameColumn);
}
consoleOutput.WriteLine();
}
}
private void GenerateSqmData(CompilationOptions compilationOptions, ImmutableArray<Diagnostic> diagnostics)
{
// Generate SQM data file for Compilers
......
......@@ -66,7 +66,7 @@ internal abstract class AnalyzerDriver : IDisposable
/// Finally, it initializes and starts the <see cref="_primaryTask"/> for the driver.
/// </summary>
/// <remarks>
/// NOTE: This method must only be invoked from <see cref="AnalyzerDriver.Create(Compilation, ImmutableArray{DiagnosticAnalyzer}, AnalyzerOptions, AnalyzerManager, Action{Diagnostic}, out Compilation, CancellationToken)"/>.
/// NOTE: This method must only be invoked from <see cref="AnalyzerDriver.Create(Compilation, ImmutableArray{DiagnosticAnalyzer}, AnalyzerOptions, AnalyzerManager, Action{Diagnostic}, Boolean, out Compilation, CancellationToken)"/>.
/// </remarks>
private void Initialize(Compilation comp, AnalyzerExecutor analyzerExecutor, CancellationToken cancellationToken)
{
......@@ -144,6 +144,7 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
/// <param name="options">Options that are passed to analyzers.</param>
/// <param name="analyzerManager">AnalyzerManager to manage analyzers for the lifetime of analyzer host.</param>
/// <param name="addExceptionDiagnostic">Delegate to add diagnostics generated for exceptions from third party analyzers.</param>
/// <param name="reportAnalyzer">Report additional information related to analyzers, such as analyzer execution time.</param>
/// <param name="newCompilation">The new compilation with the analyzer driver attached.</param>
/// <param name="cancellationToken">A cancellation token that can be used to abort analysis.</param>
/// <returns>A newly created analyzer driver</returns>
......@@ -156,7 +157,8 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
ImmutableArray<DiagnosticAnalyzer> analyzers,
AnalyzerOptions options,
AnalyzerManager analyzerManager,
Action<Diagnostic> addExceptionDiagnostic,
Action<Diagnostic> addExceptionDiagnostic,
bool reportAnalyzer,
out Compilation newCompilation,
CancellationToken cancellationToken)
{
......@@ -178,7 +180,7 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException =
(ex, analyzer, diagnostic) => addExceptionDiagnostic?.Invoke(diagnostic);
return Create(compilation, analyzers, options, analyzerManager, onAnalyzerException, out newCompilation, cancellationToken: cancellationToken);
return Create(compilation, analyzers, options, analyzerManager, onAnalyzerException, reportAnalyzer, out newCompilation, cancellationToken: cancellationToken);
}
// internal for testing purposes
......@@ -188,6 +190,7 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
AnalyzerOptions options,
AnalyzerManager analyzerManager,
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
bool reportAnalyzer,
out Compilation newCompilation,
CancellationToken cancellationToken)
{
......@@ -213,8 +216,15 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
// Assume all analyzers are non-thread safe.
var singleThreadedAnalyzerToGateMap = ImmutableDictionary.CreateRange(analyzers.Select(a => KeyValuePair.Create(a, new object())));
if (reportAnalyzer)
{
// If we are reporting detailed analyzer performance numbers, then do a dummy invocation of Compilation.GetTypeByMetadataName API upfront.
// This API seems to cause a severe hit for the first analyzer invoking it and hence introduces lot of noise in the computed analyzer execution times.
var unused = newCompilation.GetTypeByMetadataName("System.Object");
}
var analyzerExecutor = AnalyzerExecutor.Create(newCompilation, options, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, cancellationToken);
var analyzerExecutor = AnalyzerExecutor.Create(newCompilation, options, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, reportAnalyzer, cancellationToken);
analyzerDriver.Initialize(newCompilation, analyzerExecutor, cancellationToken);
......@@ -274,6 +284,8 @@ public async Task<ImmutableArray<Diagnostic>> GetDiagnosticsAsync()
/// </summary>
public Task WhenCompletedTask => _primaryTask;
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes => analyzerExecutor.AnalyzerExecutionTimes;
private ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<ImmutableArray<SymbolAnalyzerAction>>> MakeSymbolActionsByKind()
{
var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, ImmutableArray<ImmutableArray<SymbolAnalyzerAction>>>();
......
......@@ -32,7 +32,7 @@ public CompilationWithAnalyzers(Compilation compilation, ImmutableArray<Diagnost
{
_cancellationToken = cancellationToken;
_exceptionDiagnostics = new ConcurrentSet<Diagnostic>();
_driver = AnalyzerDriver.Create(compilation, analyzers, options, AnalyzerManager.Instance, AddExceptionDiagnostic, out _compilation, _cancellationToken);
_driver = AnalyzerDriver.Create(compilation, analyzers, options, AnalyzerManager.Instance, AddExceptionDiagnostic, false, out _compilation, _cancellationToken);
}
private void AddExceptionDiagnostic(Diagnostic diagnostic)
......@@ -120,7 +120,7 @@ public static bool IsDiagnosticAnalyzerSuppressed(DiagnosticAnalyzer analyzer, C
Action<Exception, DiagnosticAnalyzer, Diagnostic> voidHandler = (ex, a, diag) => { };
onAnalyzerException = onAnalyzerException ?? voidHandler;
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance, CancellationToken.None);
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance);
return AnalyzerDriver.IsDiagnosticAnalyzerSuppressed(analyzer, options, AnalyzerManager.Instance, analyzerExecutor);
}
......
......@@ -2203,6 +2203,7 @@ Microsoft.CodeAnalysis.CommandLineArguments.PdbPath.get -> string
Microsoft.CodeAnalysis.CommandLineArguments.PreferredUILang.get -> System.Globalization.CultureInfo
Microsoft.CodeAnalysis.CommandLineArguments.PrintFullPaths.get -> bool
Microsoft.CodeAnalysis.CommandLineArguments.ReferencePaths.get -> System.Collections.Immutable.ImmutableArray<string>
Microsoft.CodeAnalysis.CommandLineArguments.ReportAnalyzer.get -> bool
Microsoft.CodeAnalysis.CommandLineArguments.ResolveAnalyzerReferences(Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader analyzerLoader) -> System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference>
Microsoft.CodeAnalysis.CommandLineArguments.ResolveMetadataReferences(Microsoft.CodeAnalysis.MetadataReferenceResolver metadataResolver) -> System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.MetadataReference>
Microsoft.CodeAnalysis.CommandLineArguments.ScriptArguments.get -> System.Collections.Immutable.ImmutableArray<string>
......
......@@ -2032,5 +2032,195 @@ public void OnlyStartsOneServer()
result = ProcessLauncher.Run(_csharpCompilerClientExecutable, "");
Assert.Equal(1, GetProcessesByFullPath(_compilerServerExecutable).Count);
}
// A dictionary with name and contents of all the files we want to create for the ReportAnalyzerMSBuild test.
private Dictionary<string, string> ReportAnalyzerMsBuildFiles => new Dictionary<string, string> {
{ "HelloSolution.sln",
@"
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""HelloLib"", ""HelloLib.csproj"", ""{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}""
EndProject
Project(""{F184B08F-C81C-45F6-A57F-5ABD9991F28F}"") = ""VBLib"", ""VBLib.vbproj"", ""{F21C894B-28E5-4212-8AF7-C8E0E5455737}""
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|x86.ActiveCfg = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Any CPU.Build.0 = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|x86.ActiveCfg = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|x86.ActiveCfg = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Any CPU.Build.0 = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
"},
{ "HelloLib.csproj",
@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<UsingTask TaskName=""Microsoft.CodeAnalysis.BuildTasks.Csc"" AssemblyFile=""" + _buildTaskDll + @""" />
<PropertyGroup>
<Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
<Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>HelloLib</RootNamespace>
<AssemblyName>HelloLib</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ReportAnalyzer>True</ReportAnalyzer>
</PropertyGroup>
<ItemGroup>
<Reference Include=""System"" />
<Reference Include=""System.Core"" />
<Reference Include=""System.Xml.Linq"" />
<Reference Include=""System.Xml"" />
</ItemGroup>
<ItemGroup>
<Compile Include=""HelloLib.cs"" />
</ItemGroup>
<ItemGroup>
<Folder Include=""Properties\"" />
</ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
<Import Project=""$(MyMSBuildToolsPath)\Microsoft.CSharp.Core.targets"" />
</Project>"},
{ "HelloLib.cs",
@"public class $P {}"},
{ "VBLib.vbproj",
@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<UsingTask TaskName=""Microsoft.CodeAnalysis.BuildTasks.Vbc"" AssemblyFile=""" + _buildTaskDll + @""" />
<PropertyGroup>
<Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
<Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>
</SchemaVersion>
<ProjectGuid>{F21C894B-28E5-4212-8AF7-C8E0E5455737}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>VBLib</RootNamespace>
<AssemblyName>VBLib</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>Windows</MyType>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<OutputPath>bin\Debug\</OutputPath>
<DocumentationFile>VBLib.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">
<DebugType>pdbonly</DebugType>
<DefineDebug>false</DefineDebug>
<DefineTrace>true</DefineTrace>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DocumentationFile>VBLib.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<PropertyGroup>
<OptionExplicit>On</OptionExplicit>
</PropertyGroup>
<PropertyGroup>
<OptionCompare>Binary</OptionCompare>
</PropertyGroup>
<PropertyGroup>
<OptionStrict>Off</OptionStrict>
</PropertyGroup>
<PropertyGroup>
<OptionInfer>On</OptionInfer>
</PropertyGroup>
<PropertyGroup>
<ReportAnalyzer>True</ReportAnalyzer>
</PropertyGroup>
<ItemGroup>
<Reference Include=""System"" />
</ItemGroup>
<ItemGroup>
<Import Include=""Microsoft.VisualBasic"" />
<Import Include=""System"" />
<Import Include=""System.Collections.Generic"" />
</ItemGroup>
<ItemGroup>
<Compile Include=""VBLib.vb"" />
</ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.VisualBasic.targets"" />
<Import Project=""$(MyMSBuildToolsPath)\Microsoft.VisualBasic.Core.targets"" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
-->
</Project>"},
{ "VBLib.vb",
@"
Public Class $P
End Class
"}
};
[Fact]
public void ReportAnalyzerMSBuild()
{
string arguments = string.Format(@"/m /nr:false /t:Rebuild /p:UseRoslyn=1 HelloSolution.sln");
var result = RunCommandLineCompiler(MSBuildExecutable, arguments, _tempDirectory, ReportAnalyzerMsBuildFiles,
new Dictionary<string, string>
{ { "MyMSBuildToolsPath", Path.GetDirectoryName(typeof(CompilerServerUnitTests).Assembly.Location) } });
Assert.True(result.ExitCode != 0);
Assert.Contains("/reportanalyzer", result.Output);
}
}
}
......@@ -145,6 +145,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim sqmsessionguid As Guid = Nothing
Dim touchedFilesPath As String = Nothing
Dim features = New List(Of String)()
Dim reportAnalyzer As Boolean = False
' Process ruleset files first so that diagnostic severity settings specified on the command line via
' /nowarn and /warnaserror can override diagnostic severity settings specified in the ruleset file.
......@@ -937,6 +938,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
UnimplementedSwitch(diagnostics, name)
Continue For
Case "reportanalyzer"
reportAnalyzer = True
Continue For
Case "nostdlib"
If value IsNot Nothing Then
Exit Select
......@@ -1213,7 +1218,8 @@ lVbRuntimePlus:
.EmitPdb = emitPdb,
.DefaultCoreLibraryReference = defaultCoreLibraryReference,
.PreferredUILang = preferredUILang,
.SqmSessionGuid = sqmsessionguid
.SqmSessionGuid = sqmsessionguid,
.ReportAnalyzer = reportAnalyzer
}
End Function
......
'------------------------------------------------------------------------------
' <auto-generated>
' This code was generated by a tool.
' Runtime Version:4.0.30319.0
' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
......
......@@ -5062,7 +5062,8 @@
diagnostics.
/errorlog:&lt;file&gt; Specify a file to log all compiler and analyzer
diagnostics.
/reportanalyzer Report additional analyzer information, such as
execution time.
- LANGUAGE -
/define:&lt;symbol_list&gt; Declare global conditional compilation
symbol(s). symbol_list:name=value,...
......
......@@ -7110,6 +7110,31 @@ out
Assert.Equal(expected:=ReportDiagnostic.Suppress, actual:=arguments.CompilationOptions.SpecificDiagnosticOptions("Test001"))
End Sub
<Fact>
Public Sub ReportAnalyzer()
Dim args1 = DefaultParse({"/reportanalyzer", "a.vb"}, _baseDirectory)
Assert.True(args1.ReportAnalyzer)
Dim args2 = DefaultParse({"", "a.vb"}, _baseDirectory)
Assert.False(args2.ReportAnalyzer)
End Sub
<Fact>
Public Sub ReportAnalyzerOutput()
Dim source As String = Temp.CreateFile().WriteAllText(<text>
Class C
End Class
</text>.Value).Path
Dim vbc = New MockVisualBasicCompiler(Nothing, _baseDirectory, {"/reportanalyzer", "/t:library", "/a:" + Assembly.GetExecutingAssembly().Location, source})
Dim outWriter = New StringWriter()
Dim exitCode = vbc.Run(outWriter, Nothing)
Assert.Equal(0, exitCode)
Dim output = outWriter.ToString()
Assert.Contains(New WarningDiagnosticAnalyzer().ToString(), output, StringComparison.Ordinal)
Assert.Contains(CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader, output, StringComparison.Ordinal)
CleanupAllGeneratedFiles(source)
End Sub
End Class
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
......
......@@ -73,7 +73,7 @@ private static string GetAssemblyQualifiedNameWithoutVersion(Type type)
Action<Exception, DiagnosticAnalyzer, Diagnostic> defaultOnAnalyzerException = (ex, a, diagnostic) =>
OnAnalyzerException_NoTelemetryLogging(ex, a, diagnostic, hostDiagnosticUpdateSource);
return AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException ?? defaultOnAnalyzerException, AnalyzerManager.Instance, cancellationToken);
return AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException ?? defaultOnAnalyzerException, AnalyzerManager.Instance, cancellationToken: cancellationToken);
}
internal static void OnAnalyzerException_NoTelemetryLogging(
......
......@@ -167,7 +167,7 @@ public static TCompilation VerifyDiagnostics<TCompilation>(this TCompilation c,
}
Compilation newCompilation;
var driver = AnalyzerDriver.Create(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, out newCompilation, CancellationToken.None);
var driver = AnalyzerDriver.Create(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, false, out newCompilation, CancellationToken.None);
var discarded = newCompilation.GetDiagnostics();
diagnostics = driver.GetDiagnosticsAsync().Result.AddRange(exceptionDiagnostics);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册