提交 6b92b182 编写于 作者: M mavasani

Add a new switch '/errorlog:' to VB and C# command line compilers to log all...

Add a new switch '/errorlog:' to VB and C# command line compilers to log all the compiler diagnostics into a log file.
Add command line parsing tests and error log content validation tests.
上级 ba88129b
......@@ -33,21 +33,7 @@ protected CSharpCompiler(CSharpCommandLineParser parser, string responseFile, st
public override DiagnosticFormatter DiagnosticFormatter { get { return _diagnosticFormatter; } }
protected internal new CSharpCommandLineArguments Arguments { get { return (CSharpCommandLineArguments)base.Arguments; } }
public override int Run(TextWriter consoleOutput, CancellationToken cancellationToken = default(CancellationToken))
{
try
{
return base.Run(consoleOutput, cancellationToken);
}
catch (OperationCanceledException)
{
DiagnosticInfo diag = new DiagnosticInfo(MessageProvider, (int)ErrorCode.ERR_CompileCancelled);
PrintErrors(new[] { diag }, consoleOutput);
return 1;
}
}
protected override Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger)
protected override Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger, ErrorLogger errorLogger)
{
var parseOptions = Arguments.ParseOptions;
var scriptParseOptions = parseOptions.WithKind(SourceCodeKind.Script);
......@@ -63,7 +49,7 @@ protected override Compilation CreateCompilation(TextWriter consoleOutput, Touch
Parallel.For(0, sourceFiles.Length, UICultureUtilities.WithCurrentUICulture<int>(i =>
{
//NOTE: order of trees is important!!
trees[i] = ParseFile(consoleOutput, parseOptions, scriptParseOptions, ref hadErrors, sourceFiles[i], out normalizedFilePaths[i]);
trees[i] = ParseFile(consoleOutput, parseOptions, scriptParseOptions, ref hadErrors, sourceFiles[i], errorLogger, out normalizedFilePaths[i]);
}));
}
else
......@@ -71,7 +57,7 @@ protected override Compilation CreateCompilation(TextWriter consoleOutput, Touch
for (int i = 0; i < sourceFiles.Length; i++)
{
//NOTE: order of trees is important!!
trees[i] = ParseFile(consoleOutput, parseOptions, scriptParseOptions, ref hadErrors, sourceFiles[i], out normalizedFilePaths[i]);
trees[i] = ParseFile(consoleOutput, parseOptions, scriptParseOptions, ref hadErrors, sourceFiles[i], errorLogger, out normalizedFilePaths[i]);
}
}
......@@ -137,7 +123,7 @@ protected override Compilation CreateCompilation(TextWriter consoleOutput, Touch
var externalReferenceResolver = GetExternalMetadataResolver(touchedFilesLogger);
MetadataFileReferenceResolver referenceDirectiveResolver;
var resolvedReferences = ResolveMetadataReferences(externalReferenceResolver, metadataProvider, diagnostics, assemblyIdentityComparer, touchedFilesLogger, out referenceDirectiveResolver);
if (PrintErrors(diagnostics, consoleOutput))
if (ReportErrors(diagnostics, consoleOutput, errorLogger))
{
return null;
}
......@@ -164,6 +150,7 @@ protected override Compilation CreateCompilation(TextWriter consoleOutput, Touch
CSharpParseOptions scriptParseOptions,
ref bool hadErrors,
CommandLineSourceFile file,
ErrorLogger errorLogger,
out string normalizedFilePath)
{
var fileReadDiagnostics = new List<DiagnosticInfo>();
......@@ -171,7 +158,7 @@ protected override Compilation CreateCompilation(TextWriter consoleOutput, Touch
if (content == null)
{
PrintErrors(fileReadDiagnostics, consoleOutput);
ReportErrors(fileReadDiagnostics, consoleOutput, errorLogger);
fileReadDiagnostics.Clear();
hadErrors = true;
return null;
......@@ -263,12 +250,26 @@ internal override bool SuppressDefaultResponseFile(IEnumerable<string> args)
/// <param name="consoleOutput"></param>
protected override void PrintLogo(TextWriter consoleOutput)
{
Assembly thisAssembly = GetType().Assembly;
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_LogoLine1, Culture), FileVersionInfo.GetVersionInfo(thisAssembly.Location).FileVersion);
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_LogoLine1, Culture), GetToolName(), GetAssemblyFileVersion());
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_LogoLine2, Culture));
consoleOutput.WriteLine();
}
internal override string GetToolName()
{
return ErrorFacts.GetMessage(MessageID.IDS_ToolName, Culture);
}
internal override string GetAssemblyFileVersion()
{
return FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
}
internal override Version GetAssemblyVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version;
}
/// <summary>
/// Print Commandline help message (up to 80 English characters per line)
/// </summary>
......
......@@ -53,6 +53,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
string outputDirectory = baseDirectory;
string outputFileName = null;
string documentationPath = null;
string errorLogPath = null;
bool parseDocumentationComments = false; //Don't just null check documentationFileName because we want to do this even if the file name is invalid.
bool utf8output = false;
OutputKind outputKind = OutputKind.ConsoleApplication;
......@@ -895,6 +896,18 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
case "errorreport":
continue;
case "errorlog":
unquoted = RemoveAllQuotes(value);
if (string.IsNullOrEmpty(unquoted))
{
AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, ":<file>", RemoveAllQuotes(arg));
}
else
{
errorLogPath = ParseGenericPathToFile(unquoted, diagnostics, baseDirectory);
}
continue;
case "appconfig":
unquoted = RemoveAllQuotes(value);
if (string.IsNullOrEmpty(unquoted))
......@@ -903,8 +916,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
}
else
{
appConfigPath = ParseGenericPathToFile(
unquoted, diagnostics, baseDirectory);
appConfigPath = ParseGenericPathToFile(unquoted, diagnostics, baseDirectory);
}
continue;
......@@ -1056,6 +1068,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
EmitPdb = emitPdb,
OutputDirectory = outputDirectory,
DocumentationPath = documentationPath,
ErrorLogPath = errorLogPath,
AppConfigPath = appConfigPath,
SourceFiles = sourceFiles.AsImmutable(),
Encoding = codepage,
......
......@@ -9189,7 +9189,7 @@ internal class CSharpResources {
}
/// <summary>
/// Looks up a localized string similar to Microsoft (R) Visual C# Compiler version {0}.
/// Looks up a localized string similar to {0} version {1}.
/// </summary>
internal static string IDS_LogoLine1 {
get {
......@@ -9395,6 +9395,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Microsoft (R) Visual C# Compiler.
/// </summary>
internal static string IDS_ToolName {
get {
return ResourceManager.GetString("IDS_ToolName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to using variable.
/// </summary>
......
......@@ -4332,8 +4332,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ChainingSpeculativeModelIsNotSupported" xml:space="preserve">
<value>Chaining speculative semantic model is not supported. You should create a speculative model from the non-speculative ParentModel.</value>
</data>
<data name="IDS_ToolName" xml:space="preserve">
<value>Microsoft (R) Visual C# Compiler</value>
</data>
<data name="IDS_LogoLine1" xml:space="preserve">
<value>Microsoft (R) Visual C# Compiler version {0}</value>
<value>{0} version {1}</value>
</data>
<data name="IDS_LogoLine2" xml:space="preserve">
<value>Copyright (C) Microsoft Corporation. All rights reserved.</value>
......@@ -4402,7 +4405,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
/nowarn:&lt;warn list&gt; Disable specific warning messages
/ruleset:&lt;file&gt; Specify a ruleset file that disables specific
diagnostics.
/errorlog:&lt;file&gt; Specify a file to log all compiler and analyzer
diagnostics.
- LANGUAGE -
/checked[+|-] Generate overflow checks
/unsafe[+|-] Allow 'unsafe' code
......
......@@ -101,8 +101,8 @@ internal enum MessageID
// IDS_VersionExperimental = MessageBase + 12694,
IDS_FeatureNameof = MessageBase + 12695,
IDS_FeatureDictionaryInitializer = MessageBase + 12696,
// available: MessageBase + 12697,
IDS_ToolName = MessageBase + 12697,
IDS_LogoLine1 = MessageBase + 12698,
IDS_LogoLine2 = MessageBase + 12699,
IDS_CSCHelp = MessageBase + 12700,
......@@ -110,6 +110,7 @@ internal enum MessageID
IDS_FeatureUsingStatic = MessageBase + 12701,
IDS_FeatureInterpolatedStrings = MessageBase + 12702,
IDS_OperationCausedStackOverflow = MessageBase + 12703,
}
// Message IDs may refer to strings that need to be localized.
......
......@@ -137,6 +137,7 @@ public override ReportDiagnostic GetDiagnosticReport(DiagnosticInfo diagnosticIn
public override int WRN_UnableToLoadAnalyzer { get { return (int)ErrorCode.WRN_UnableToLoadAnalyzer; } }
public override int INF_UnableToLoadSomeTypesInAnalyzer { get { return (int)ErrorCode.INF_UnableToLoadSomeTypesInAnalyzer; } }
public override int ERR_CantReadRulesetFile { get { return (int)ErrorCode.ERR_CantReadRulesetFile; } }
public override int ERR_CompileCancelled { get { return (int)ErrorCode.ERR_CompileCancelled; } }
// compilation options:
public override int ERR_BadCompilationOptionValue { get { return (int)ErrorCode.ERR_BadCompilationOptionValue; } }
......
......@@ -105,6 +105,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>CommandLineTestResources.resx</DependentUpon>
</Compile>
<Compile Include="ErrorLoggerTests.cs" />
<Compile Include="TouchedFileLoggingTests.cs" />
</ItemGroup>
<ItemGroup>
......@@ -128,4 +129,4 @@
<Import Project="..\..\..\..\..\build\Roslyn.Toolsets.Xunit.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -2574,6 +2574,85 @@ public void ParseDoc()
Assert.Equal(DocumentationMode.Diagnose, parsedArgs.ParseOptions.DocumentationMode); //Even though the format was incorrect
}
[Fact]
public void ParseErrorLog()
{
const string baseDirectory = @"C:\abc\def\baz";
var parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:""""", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2006: Command-line syntax error: Missing ':<file>' for '/errorlog:' option
Diagnostic(ErrorCode.ERR_SwitchNeedsString).WithArguments(":<file>", "/errorlog:"));
Assert.Null(parsedArgs.ErrorLogPath);
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2006: Command-line syntax error: Missing ':<file>' for '/errorlog:' option
Diagnostic(ErrorCode.ERR_SwitchNeedsString).WithArguments(":<file>", "/errorlog:"));
Assert.Null(parsedArgs.ErrorLogPath);
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2006: Command-line syntax error: Missing ':<file>' for '/errorlog' option
Diagnostic(ErrorCode.ERR_SwitchNeedsString).WithArguments(":<file>", "/errorlog"));
Assert.Null(parsedArgs.ErrorLogPath);
// Should preserve fully qualified paths
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:C:\MyFolder\MyBinary.xml", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(@"C:\MyFolder\MyBinary.xml", parsedArgs.ErrorLogPath);
// Should handle quotes
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:C:\""My Folder""\MyBinary.xml", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(@"C:\My Folder\MyBinary.xml", parsedArgs.ErrorLogPath);
// Should expand partially qualified paths
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:MyBinary.xml", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(Path.Combine(baseDirectory, "MyBinary.xml"), parsedArgs.ErrorLogPath);
// Should expand partially qualified paths
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:..\MyBinary.xml", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(@"C:\abc\def\MyBinary.xml", parsedArgs.ErrorLogPath);
// drive-relative path:
char currentDrive = Directory.GetCurrentDirectory()[0];
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { "/errorlog:" + currentDrive + @":a.xml", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2021: File name 'D:a.xml' is contains invalid characters, has a drive specification without an absolute path, or is too long
Diagnostic(ErrorCode.FTL_InputFileNameTooLong).WithArguments(currentDrive + ":a.xml"));
Assert.Null(parsedArgs.ErrorLogPath);
// UNC
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:\\b", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
Diagnostic(ErrorCode.FTL_InputFileNameTooLong).WithArguments(@"\\b"));
Assert.Null(parsedArgs.ErrorLogPath);
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:\\server\share\file.xml", "a.vb" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(@"\\server\share\file.xml", parsedArgs.ErrorLogPath);
// invalid name:
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { "/errorlog:a.b\0b", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
Diagnostic(ErrorCode.FTL_InputFileNameTooLong).WithArguments("a.b\0b"));
Assert.Null(parsedArgs.ErrorLogPath);
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:""a<>.xml""", "a.vb" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2021: File name 'a<>.xml' is empty, contains invalid characters, has a drive specification without an absolute path, or is too long
Diagnostic(ErrorCode.FTL_InputFileNameTooLong).WithArguments("a<>.xml"));
Assert.Null(parsedArgs.ErrorLogPath);
}
[Fact]
public void AppConfigParse()
{
......@@ -2678,6 +2757,30 @@ public void ParseDocAndOut()
Assert.Equal("d.exe", parsedArgs.OutputFileName);
}
[Fact]
public void ParseErrorLogAndOut()
{
const string baseDirectory = @"C:\abc\def\baz";
// Can specify separate directories for binary and error log output.
var parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:a\b.xml", @"/out:c\d.exe", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(@"C:\abc\def\baz\a\b.xml", parsedArgs.ErrorLogPath);
Assert.Equal(@"C:\abc\def\baz\c", parsedArgs.OutputDirectory);
Assert.Equal("d.exe", parsedArgs.OutputFileName);
// XML does not fall back on output directory.
parsedArgs = CSharpCommandLineParser.Default.Parse(new[] { @"/errorlog:b.xml", @"/out:c\d.exe", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(@"C:\abc\def\baz\b.xml", parsedArgs.ErrorLogPath);
Assert.Equal(@"C:\abc\def\baz\c", parsedArgs.OutputDirectory);
Assert.Equal("d.exe", parsedArgs.OutputFileName);
}
[Fact]
public void ModuleAssemblyName()
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.IO;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
using static Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers;
using static Microsoft.CodeAnalysis.DiagnosticExtensions;
using static Microsoft.CodeAnalysis.Test.Utilities.SharedResourceHelpers;
namespace Microsoft.CodeAnalysis.CSharp.CommandLine.UnitTests
{
public class ErrorLoggerTests : CSharpTestBase
{
private readonly string _baseDirectory = TempRoot.Root;
[Fact]
public void NoDiagnostics()
{
var helloWorldCS = @"using System;
class C
{
public static void Main(string[] args)
{
Console.WriteLine(""Hello, world"");
}
}";
var hello = Temp.CreateFile().WriteAllText(helloWorldCS).Path;
var errorLogDir = Temp.CreateDirectory();
var errorLogFile = Path.Combine(errorLogDir.Path, "ErrorLog.txt");
var cmd = new MockCSharpCompiler(null, _baseDirectory, new[] { "/nologo", hello,
$"/errorlog:{errorLogFile}" });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var exitCode = cmd.Run(outWriter);
Assert.Equal("", outWriter.ToString().Trim());
Assert.Equal(0, exitCode);
var actualOutput = File.ReadAllText(errorLogFile).Trim();
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
var expectedIssues = @"
""issues"": [
]
}";
var expectedText = expectedHeader + expectedIssues;
Assert.Equal(expectedText, actualOutput);
CleanupAllGeneratedFiles(hello);
CleanupAllGeneratedFiles(errorLogFile);
}
[Fact]
public void SimpleCompilerDiagnostics()
{
var source = @"
public class C
{
private int x;
}";
var sourceFile = Temp.CreateFile().WriteAllText(source).Path;
var errorLogDir = Temp.CreateDirectory();
var errorLogFile = Path.Combine(errorLogDir.Path, "ErrorLog.txt");
var cmd = new MockCSharpCompiler(null, _baseDirectory, new[] {
"/nologo", sourceFile, "/preferreduilang:en", $"/errorlog:{errorLogFile}" });
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var exitCode = cmd.Run(outWriter);
var actualConsoleOutput = outWriter.ToString().Trim();
Assert.Contains("CS0169", actualConsoleOutput);
Assert.Contains("CS5001", actualConsoleOutput);
Assert.NotEqual(0, exitCode);
var actualOutput = File.ReadAllText(errorLogFile).Trim();
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
var expectedIssues = string.Format(@"
""issues"": [
{{
""ruleId"": ""CS0169"",
""locations"": [
{{
""analysisTarget"": [
{{
""uri"": ""{0}"",
""region"": {{
""startLine"": 3,
""startColumn"": 16,
""endLine"": 3,
""endColumn"": 17
}}
}}
]
}}
],
""fullMessage"": ""The field 'C.x' is never used"",
""properties"": {{
""severity"": ""Warning"",
""warningLevel"": ""3"",
""defaultSeverity"": ""Warning"",
""title"": ""Field is never used"",
""category"": ""Compiler"",
""isEnabledByDefault"": ""True"",
""customTags"": ""Compiler;Telemetry""
}}
}},
{{
""ruleId"": ""CS5001"",
""locations"": [
{{
""analysisTarget"": [
{{
""uri"": ""<None>"",
""region"": {{
""startLine"": 0,
""startColumn"": 0,
""endLine"": 0,
""endColumn"": 0
}}
}}
]
}}
],
""fullMessage"": ""Program does not contain a static 'Main' method suitable for an entry point"",
""properties"": {{
""severity"": ""Error"",
""defaultSeverity"": ""Error"",
""category"": ""Compiler"",
""isEnabledByDefault"": ""True"",
""customTags"": ""Compiler;Telemetry;NotConfigurable""
}}
}}
]
}}", sourceFile);
var expectedText = expectedHeader + expectedIssues;
Assert.Equal(expectedText, actualOutput);
CleanupAllGeneratedFiles(sourceFile);
CleanupAllGeneratedFiles(errorLogFile);
}
[Fact]
public void AnalyzerDiagnosticsWithAndWithoutLocation()
{
var source = @"
public class C
{
}";
var sourceFile = Temp.CreateFile().WriteAllText(source).Path;
var outputDir = Temp.CreateDirectory();
var errorLogFile = Path.Combine(outputDir.Path, "ErrorLog.txt");
var outputFilePath = Path.Combine(outputDir.Path, "test.dll");
var cmd = new MockCSharpCompiler(null, _baseDirectory, new[] {
"/nologo", "/t:library", $"/out:{outputFilePath}", sourceFile, "/preferreduilang:en", $"/errorlog:{errorLogFile}" },
analyzer: new AnalyzerForErrorLogTest());
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var exitCode = cmd.Run(outWriter);
var actualConsoleOutput = outWriter.ToString().Trim();
Assert.Contains(AnalyzerForErrorLogTest.Descriptor1.Id, actualConsoleOutput);
Assert.Contains(AnalyzerForErrorLogTest.Descriptor2.Id, actualConsoleOutput);
Assert.NotEqual(0, exitCode);
var actualOutput = File.ReadAllText(errorLogFile).Trim();
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
var expectedIssues = AnalyzerForErrorLogTest.GetExpectedErrorLogIssuesText(cmd.Compilation, outputFilePath);
var expectedText = expectedHeader + expectedIssues;
Assert.Equal(expectedText, actualOutput);
CleanupAllGeneratedFiles(sourceFile);
CleanupAllGeneratedFiles(outputFilePath);
CleanupAllGeneratedFiles(errorLogFile);
}
}
}
\ No newline at end of file
......@@ -17,7 +17,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics
internal class AnalyzerExecutor
{
private const string AnalyzerExceptionDiagnosticId = "AD0001";
private const string DescriptorExceptionDiagnosticId = "AD0002";
private const string DiagnosticCategory = "Compiler";
private readonly Compilation _compilation;
......@@ -505,21 +504,9 @@ internal static Diagnostic GetAnalyzerExceptionDiagnostic(DiagnosticAnalyzer ana
return Diagnostic.Create(descriptor, Location.None, analyzer.ToString(), e.Message);
}
internal static Diagnostic GetDescriptorDiagnostic(string faultedDescriptorId, Exception e)
{
var descriptor = new DiagnosticDescriptor(DescriptorExceptionDiagnosticId,
AnalyzerDriverResources.AnalyzerFailure,
AnalyzerDriverResources.DiagnosticDescriptorThrows,
category: DiagnosticCategory,
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
customTags: WellKnownDiagnosticTags.AnalyzerException);
return Diagnostic.Create(descriptor, Location.None, faultedDescriptorId, e.Message);
}
internal static bool IsAnalyzerExceptionDiagnostic(Diagnostic diagnostic)
{
if (diagnostic.Id == AnalyzerExceptionDiagnosticId || diagnostic.Id == DescriptorExceptionDiagnosticId)
if (diagnostic.Id == AnalyzerExceptionDiagnosticId)
{
#pragma warning disable RS0013 // Its ok to realize the Descriptor for analyzer exception diagnostics, which are descriptor based and also rare.
foreach (var tag in diagnostic.Descriptor.CustomTags)
......
......@@ -70,10 +70,14 @@
<Compile Include="CommandLine\CommonCompiler.LoggingXmlFileResolver.cs" />
<Compile Include="CommandLine\CommonCompiler.LoggingMetadataFileResolver.cs" />
<Compile Include="CommandLine\CommonCompiler.ExistingReferencesResolver.cs" />
<Compile Include="CommandLine\ErrorLogger.Issue.cs" />
<Compile Include="CommandLine\ErrorLogger.Value.cs" />
<Compile Include="CommandLine\ErrorLogger.WellKnownStrings.cs" />
<Compile Include="CommandLine\RuleSet\InvalidRuleSetException.cs" />
<Compile Include="CommandLine\RuleSet\RuleSet.cs" />
<Compile Include="CommandLine\RuleSet\RuleSetInclude.cs" />
<Compile Include="CommandLine\RuleSet\RuleSetProcessor.cs" />
<Compile Include="CommandLine\ErrorLogger.cs" />
<Compile Include="CommandLine\TouchedFileLogger.cs" />
<Compile Include="CompilerPathUtilities.cs" />
<Compile Include="FileSystemExtensions.cs" />
......
......@@ -83,6 +83,11 @@ public abstract class CommandLineArguments
/// </summary>
public string DocumentationPath { get; internal set; }
/// <summary>
/// Absolute path of the error log file or null if not specified.
/// </summary>
public string ErrorLogPath { get; internal set; }
/// <summary>
/// An absolute path of the App.config file or null if not specified.
/// </summary>
......
......@@ -51,16 +51,18 @@ internal static string GetResponseFileFullPath(string responseFileName)
return Path.Combine(GetResponseFileDirectory(), responseFileName);
}
private readonly ObjectPool<MemoryStream> _memoryStreamPool = new ObjectPool<MemoryStream>(() => new MemoryStream(), 4);
public CommonMessageProvider MessageProvider { get; private set; }
public CommandLineArguments Arguments { get; private set; }
public abstract DiagnosticFormatter DiagnosticFormatter { get; }
private readonly HashSet<Diagnostic> _reportedDiagnostics = new HashSet<Diagnostic>();
protected abstract Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger);
protected abstract Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger, ErrorLogger errorLogger);
protected abstract void PrintLogo(TextWriter consoleOutput);
protected abstract void PrintHelp(TextWriter consoleOutput);
internal abstract string GetToolName();
internal abstract Version GetAssemblyVersion();
internal abstract string GetAssemblyFileVersion();
protected abstract uint GetSqmAppID();
protected abstract bool TryGetCompilerDiagnosticCode(string diagnosticId, out uint code);
protected abstract void CompilerSpecificSqm(IVsSqmMulti sqm, uint sqmSession);
......@@ -189,7 +191,7 @@ private DiagnosticInfo ToFileReadDiagnostics(Exception e, CommandLineSourceFile
return diagnosticInfo;
}
internal bool PrintErrors(IEnumerable<Diagnostic> diagnostics, TextWriter consoleOutput)
protected bool ReportErrors(IEnumerable<Diagnostic> diagnostics, TextWriter consoleOutput, ErrorLogger errorLogger)
{
bool hasErrors = false;
foreach (var diag in diagnostics)
......@@ -212,16 +214,8 @@ internal bool PrintErrors(IEnumerable<Diagnostic> diagnostics, TextWriter consol
continue;
}
// Catch exceptions from diagnostic formatter as diagnostic descriptors for analyzer diagnostics can throw an exception while formatting diagnostic message.
try
{
consoleOutput.WriteLine(DiagnosticFormatter.Format(diag, this.Culture));
}
catch (Exception ex)
{
var exceptionDiagnostic = AnalyzerExecutor.GetDescriptorDiagnostic(diag.Id, ex);
consoleOutput.WriteLine(DiagnosticFormatter.Format(exceptionDiagnostic, this.Culture));
}
consoleOutput.WriteLine(DiagnosticFormatter.Format(diag, this.Culture));
ErrorLogger.LogDiagnostic(diag, this.Culture, errorLogger);
if (diag.Severity == DiagnosticSeverity.Error)
{
......@@ -234,7 +228,7 @@ internal bool PrintErrors(IEnumerable<Diagnostic> diagnostics, TextWriter consol
return hasErrors;
}
internal bool PrintErrors(IEnumerable<DiagnosticInfo> diagnostics, TextWriter consoleOutput)
protected bool ReportErrors(IEnumerable<DiagnosticInfo> diagnostics, TextWriter consoleOutput, ErrorLogger errorLogger)
{
bool hasErrors = false;
if (diagnostics != null && diagnostics.Any())
......@@ -248,6 +242,8 @@ internal bool PrintErrors(IEnumerable<DiagnosticInfo> diagnostics, TextWriter co
}
PrintError(diagnostic, consoleOutput);
ErrorLogger.LogDiagnostic(Diagnostic.Create(diagnostic), this.Culture, errorLogger);
if (diagnostic.Severity == DiagnosticSeverity.Error)
{
hasErrors = true;
......@@ -258,17 +254,31 @@ internal bool PrintErrors(IEnumerable<DiagnosticInfo> diagnostics, TextWriter co
return hasErrors;
}
internal virtual void PrintError(DiagnosticInfo diagnostic, TextWriter consoleOutput)
protected virtual void PrintError(DiagnosticInfo diagnostic, TextWriter consoleOutput)
{
consoleOutput.WriteLine(diagnostic.ToString(Culture));
}
private ErrorLogger GetErrorLogger(TextWriter consoleOutput, CancellationToken cancellationToken)
{
Debug.Assert(Arguments.ErrorLogPath != null);
var errorLog = OpenFile(Arguments.ErrorLogPath, consoleOutput, FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
if (errorLog == null)
{
return null;
}
return new ErrorLogger(errorLog, GetToolName(), GetAssemblyFileVersion(), GetAssemblyVersion());
}
/// <summary>
/// csc.exe and vbc.exe entry point.
/// </summary>
public virtual int Run(TextWriter consoleOutput, CancellationToken cancellationToken)
public virtual int Run(TextWriter consoleOutput, CancellationToken cancellationToken = default(CancellationToken))
{
var saveUICulture = Thread.CurrentThread.CurrentUICulture;
ErrorLogger errorLogger = null;
try
{
......@@ -280,15 +290,36 @@ public virtual int Run(TextWriter consoleOutput, CancellationToken cancellationT
Thread.CurrentThread.CurrentUICulture = culture;
}
return RunCore(consoleOutput, cancellationToken);
if (Arguments.ErrorLogPath != null)
{
errorLogger = GetErrorLogger(consoleOutput, cancellationToken);
if (errorLogger == null)
{
return Failed;
}
}
return RunCore(consoleOutput, errorLogger, cancellationToken);
}
catch (OperationCanceledException)
{
var errorCode = MessageProvider.ERR_CompileCancelled;
if (errorCode > 0)
{
var diag = new DiagnosticInfo(MessageProvider, errorCode);
ReportErrors(new[] { diag }, consoleOutput, errorLogger);
}
return Failed;
}
finally
{
Thread.CurrentThread.CurrentUICulture = saveUICulture;
errorLogger?.Dispose();
}
}
private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToken)
private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, CancellationToken cancellationToken)
{
Debug.Assert(!Arguments.IsInteractive);
......@@ -305,14 +336,14 @@ private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToke
return Succeeded;
}
if (PrintErrors(Arguments.Errors, consoleOutput))
if (ReportErrors(Arguments.Errors, consoleOutput, errorLogger))
{
return Failed;
}
var touchedFilesLogger = (Arguments.TouchedFilesPath != null) ? new TouchedFileLogger() : null;
Compilation compilation = CreateCompilation(consoleOutput, touchedFilesLogger);
Compilation compilation = CreateCompilation(consoleOutput, touchedFilesLogger, errorLogger);
if (compilation == null)
{
return Failed;
......@@ -321,7 +352,7 @@ private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToke
var diagnostics = new List<DiagnosticInfo>();
var analyzers = ResolveAnalyzersFromArguments(diagnostics, MessageProvider, touchedFilesLogger);
var additionalTextFiles = ResolveAdditionalFilesFromArguments(diagnostics, MessageProvider, touchedFilesLogger);
if (PrintErrors(diagnostics, consoleOutput))
if (ReportErrors(diagnostics, consoleOutput, errorLogger))
{
return Failed;
}
......@@ -349,12 +380,12 @@ private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToke
}
// Print the diagnostics produced during the parsing stage and exit if there were any errors.
if (PrintErrors(compilation.GetParseDiagnostics(), consoleOutput))
if (ReportErrors(compilation.GetParseDiagnostics(), consoleOutput, errorLogger))
{
return Failed;
}
if (PrintErrors(compilation.GetDeclarationDiagnostics(), consoleOutput))
if (ReportErrors(compilation.GetDeclarationDiagnostics(), consoleOutput, errorLogger))
{
return Failed;
}
......@@ -390,7 +421,7 @@ private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToke
using (var win32Res = GetWin32Resources(Arguments, compilation, out errors))
using (xml)
{
if (PrintErrors(errors, consoleOutput))
if (ReportErrors(errors, consoleOutput, errorLogger))
{
return Failed;
}
......@@ -432,7 +463,7 @@ private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToke
GenerateSqmData(Arguments.CompilationOptions, emitResult.Diagnostics);
if (PrintErrors(emitResult.Diagnostics, consoleOutput))
if (ReportErrors(emitResult.Diagnostics, consoleOutput, errorLogger))
{
return Failed;
}
......@@ -442,7 +473,7 @@ private int RunCore(TextWriter consoleOutput, CancellationToken cancellationToke
bool errorsReadingAdditionalFiles = false;
foreach (var additionalFile in additionalTextFiles)
{
if (PrintErrors(additionalFile.Diagnostics, consoleOutput))
if (ReportErrors(additionalFile.Diagnostics, consoleOutput, errorLogger))
{
errorsReadingAdditionalFiles = true;
}
......@@ -730,6 +761,27 @@ internal static bool TryGetCompilerDiagnosticCode(string diagnosticId, string ex
/// csi.exe and vbi.exe entry point.
/// </summary>
internal int RunInteractive(TextWriter consoleOutput)
{
ErrorLogger errorLogger = null;
if (Arguments.ErrorLogPath != null)
{
errorLogger = GetErrorLogger(consoleOutput, CancellationToken.None);
if (errorLogger == null)
{
return Failed;
}
}
using (errorLogger)
{
return RunInteractiveCore(consoleOutput, errorLogger);
}
}
/// <summary>
/// csi.exe and vbi.exe entry point.
/// </summary>
private int RunInteractiveCore(TextWriter consoleOutput, ErrorLogger errorLogger)
{
Debug.Assert(Arguments.IsInteractive);
......@@ -754,7 +806,7 @@ internal int RunInteractive(TextWriter consoleOutput)
errors = errors.Concat(new[] { Diagnostic.Create(MessageProvider, MessageProvider.ERR_NoScriptsSpecified) });
}
if (PrintErrors(errors, consoleOutput))
if (ReportErrors(errors, consoleOutput, errorLogger))
{
return Failed;
}
......@@ -762,7 +814,7 @@ internal int RunInteractive(TextWriter consoleOutput)
// arguments are always available when executing script code:
Debug.Assert(Arguments.ScriptArguments != null);
var compilation = CreateCompilation(consoleOutput, touchedFilesLogger: null);
var compilation = CreateCompilation(consoleOutput, touchedFilesLogger: null, errorLogger: errorLogger);
if (compilation == null)
{
return Failed;
......@@ -772,7 +824,7 @@ internal int RunInteractive(TextWriter consoleOutput)
using (MemoryStream output = new MemoryStream())
{
EmitResult emitResult = compilation.Emit(output);
if (PrintErrors(emitResult.Diagnostics, consoleOutput))
if (ReportErrors(emitResult.Diagnostics, consoleOutput, errorLogger))
{
return Failed;
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License; Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Microsoft.CodeAnalysis
{
internal partial class ErrorLogger
{
/// <summary>
/// Represents an issue to be logged into the error log.
/// This could be corresponding to a <see cref="Diagnostic"/> or a <see cref="DiagnosticInfo"/> reported by the <see cref="CommonCompiler"/>.
/// </summary>
private struct Issue
{
public readonly string Id;
public readonly string Message;
public readonly string Description;
public readonly string Title;
public readonly string Category;
public readonly string HelpLink;
public readonly bool IsEnabledByDefault;
public readonly DiagnosticSeverity DefaultSeverity;
public readonly DiagnosticSeverity Severity;
public readonly int WarningLevel;
public readonly Location Location;
public readonly IReadOnlyList<Location> AdditionalLocations;
public readonly IReadOnlyList<string> CustomTags;
public readonly ImmutableArray<KeyValuePair<string, string>> CustomProperties;
public Issue(
string id, string message, string description,
string title, string category, string helpLink, bool isEnabledByDefault,
DiagnosticSeverity defaultSeverity, DiagnosticSeverity severity, int warningLevel,
Location location, IReadOnlyList<Location> additionalLocations,
IReadOnlyList<string> customTags, ImmutableDictionary<string, string> customProperties)
{
Id = id;
Message = message;
Description = description;
Title = title;
Category = category;
HelpLink = helpLink;
IsEnabledByDefault = isEnabledByDefault;
DefaultSeverity = defaultSeverity;
Severity = severity;
WarningLevel = warningLevel;
Location = location;
AdditionalLocations = additionalLocations;
CustomTags = customTags;
CustomProperties = customProperties.OrderBy(kvp => kvp.Key).ToImmutableArray();
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Microsoft.CodeAnalysis
{
internal partial class ErrorLogger
{
/// <summary>
/// Represents a value for a key-value pair to be emitted into the error log file.
/// This could be a simple string or an integer OR could be a list of identical values OR a group of heterogenous key-value pairs.
/// </summary>
private abstract class Value
{
protected readonly ErrorLogger Owner;
protected Value(ErrorLogger owner)
{
Owner = owner;
}
public static Value Create(string value, ErrorLogger owner)
{
return new StringValue(value, owner);
}
public static Value Create(int value, ErrorLogger owner)
{
return new IntegerValue(value, owner);
}
public static Value Create(ImmutableArray<KeyValuePair<string, Value>> values, ErrorLogger owner)
{
return new GroupValue(values, owner);
}
public static Value Create(ImmutableArray<Value> values, ErrorLogger owner)
{
return new ListValue(values, owner);
}
public abstract void Write();
private class StringValue : Value
{
private readonly string _value;
public StringValue(string value, ErrorLogger owner)
: base(owner)
{
_value = value;
}
public override void Write()
{
Owner.WriteValue(_value);
}
}
private class IntegerValue : Value
{
private readonly int _value;
public IntegerValue(int value, ErrorLogger owner)
: base(owner)
{
_value = value;
}
public override void Write()
{
Owner.WriteValue(_value);
}
}
private class GroupValue : Value
{
private readonly ImmutableArray<KeyValuePair<string, Value>> _keyValuePairs;
public GroupValue(ImmutableArray<KeyValuePair<string, Value>> keyValuePairs, ErrorLogger owner)
: base(owner)
{
_keyValuePairs = keyValuePairs;
}
public override void Write()
{
Owner.StartGroup();
bool isFirst = true;
foreach (var kvp in _keyValuePairs)
{
Owner.WriteKeyValuePair(kvp, isFirst);
isFirst = false;
}
Owner.EndGroup();
}
}
private class ListValue : Value
{
private readonly ImmutableArray<Value> _values;
public ListValue(ImmutableArray<Value> values, ErrorLogger owner)
: base(owner)
{
_values = values;
}
public override void Write()
{
Owner.StartList();
bool isFirst = true;
foreach (var value in _values)
{
Owner.WriteValue(value, isFirst, valueInList: true);
isFirst = false;
}
Owner.EndList();
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis
{
internal partial class ErrorLogger
{
/// <summary>
/// Contains well known property strings for error log file.
/// </summary>
private static class WellKnownStrings
{
public const string OutputFormatVersion = "version";
public const string ToolFileVersion = "fileVersion";
public const string ToolAssemblyVersion = "productVersion";
public const string ToolInfo = "toolInfo";
public const string ToolName = "toolName";
public const string Issues = "issues";
public const string DiagnosticId = "ruleId";
public const string Locations = "locations";
public const string ShortMessage = "shortMessage";
public const string FullMessage = "fullMessage";
public const string IsSuppressedInSource = "isSuppressedInSource";
public const string Properties = "properties";
public const string Location = "analysisTarget";
public const string LocationSyntaxTreePath = "uri";
public const string LocationSpanInfo = "region";
public const string LocationSpanStartLine = "startLine";
public const string LocationSpanStartColumn = "startColumn";
public const string LocationSpanEndLine = "endLine";
public const string LocationSpanEndColumn = "endColumn";
// Diagnostic/DiagnosticDescriptor properties which are not defined in our log format.
public const string Category = "category";
public const string Title = "title";
public const string HelpLink = "helpLink";
public const string CustomTags = "customTags";
public const string IsEnabledByDefault = "isEnabledByDefault";
public const string DefaultSeverity = "defaultSeverity";
public const string Severity = "severity";
public const string WarningLevel = "warningLevel";
public const string CustomProperties = "customProperties";
public const string None = "<None>";
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Used for logging all compiler diagnostics into a given <see cref="Stream"/>.
/// This logger is responsible for closing the given stream on <see cref="Dispose"/>.
/// </summary>
internal partial class ErrorLogger : IDisposable
{
// Internal for testing purposes.
internal const string OutputFormatVersion = "0.1";
private const string indentDelta = " ";
private const char groupStartChar = '{';
private const char groupEndChar = '}';
private const char listStartChar = '[';
private const char listEndChar = ']';
private readonly StreamWriter _writer;
private string _currentIndent;
private bool _reportedAnyIssues;
public ErrorLogger(Stream stream, string toolName, string toolFileVersion, Version toolAssemblyVersion)
{
Debug.Assert(stream != null);
Debug.Assert(stream.Position == 0);
_writer = new StreamWriter(stream);
_currentIndent = string.Empty;
_reportedAnyIssues = false;
WriteHeader(toolName, toolFileVersion, toolAssemblyVersion);
}
private void WriteHeader(string toolName, string toolFileVersion, Version toolAssemblyVersion)
{
StartGroup();
WriteSimpleKeyValuePair(WellKnownStrings.OutputFormatVersion, OutputFormatVersion, isFirst: true);
var toolInfo = GetToolInfo(toolName, toolFileVersion, toolAssemblyVersion);
WriteKeyValuePair(WellKnownStrings.ToolInfo, toolInfo, isFirst: false);
WriteKey(WellKnownStrings.Issues, isFirst: false);
StartList();
}
private Value GetToolInfo(string toolName, string toolFileVersion, Version toolAssemblyVersion)
{
var builder = ArrayBuilder<KeyValuePair<string, Value>>.GetInstance();
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ToolName, toolName));
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ToolAssemblyVersion, toolAssemblyVersion.ToString(fieldCount: 3)));
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ToolFileVersion, GetToolFileVersionSubStr(toolFileVersion)));
return Value.Create(builder.ToImmutableAndFree(), this);
}
private string GetToolFileVersionSubStr(string toolFileVersion)
{
// Our log format specifies that fileVersion can have at most 3 fields.
int count = 0;
for (int i = 0; i < toolFileVersion.Length; i++)
{
if (toolFileVersion[i] == '.')
{
if (count == 2)
{
return toolFileVersion.Substring(0, i);
}
count++;
}
}
return toolFileVersion;
}
internal static void LogDiagnostic(Diagnostic diagnostic, CultureInfo culture, ErrorLogger errorLogger)
{
if (errorLogger != null)
{
#pragma warning disable RS0013 // We need to invoke Diagnostic.Descriptor here to log all the metadata properties of the diagnostic.
var issue = new Issue(diagnostic.Id, diagnostic.GetMessage(culture),
diagnostic.Descriptor.Description.ToString(culture), diagnostic.Descriptor.Title.ToString(culture),
diagnostic.Category, diagnostic.Descriptor.HelpLinkUri, diagnostic.IsEnabledByDefault,
diagnostic.DefaultSeverity, diagnostic.Severity, diagnostic.WarningLevel, diagnostic.Location,
diagnostic.AdditionalLocations, diagnostic.CustomTags, diagnostic.Properties);
#pragma warning restore RS0013
errorLogger.LogIssue(issue);
}
}
private void LogIssue(Issue issue)
{
var issueValue = GetIssueValue(issue);
WriteValue(issueValue, isFirst: !_reportedAnyIssues, valueInList: true);
_reportedAnyIssues = true;
}
private Value GetIssueValue(Issue issue)
{
var builder = ArrayBuilder<KeyValuePair<string, Value>>.GetInstance();
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.DiagnosticId, issue.Id));
var locationsValue = GetLocationsValue(issue.Location, issue.AdditionalLocations);
builder.Add(KeyValuePair.Create(WellKnownStrings.Locations, locationsValue));
var message = string.IsNullOrEmpty(issue.Message) ? WellKnownStrings.None : issue.Message;
var description = issue.Description;
if (string.IsNullOrEmpty(description))
{
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.FullMessage, message));
}
else
{
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ShortMessage, message));
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.FullMessage, description));
}
var propertiesValue = GetPropertiesValue(issue);
builder.Add(KeyValuePair.Create(WellKnownStrings.Properties, propertiesValue));
return Value.Create(builder.ToImmutableAndFree(), this);
}
private Value GetLocationsValue(Location location, IReadOnlyList<Location> additionalLocations)
{
var builder = ArrayBuilder<Value>.GetInstance();
var locationValue = GetLocationValue(location);
builder.Add(locationValue);
if (additionalLocations?.Count > 0)
{
foreach (var additionalLocation in additionalLocations)
{
locationValue = GetLocationValue(additionalLocation);
builder.Add(locationValue);
}
}
return Value.Create(builder.ToImmutableAndFree(), this);
}
private Value GetLocationValue(Location location)
{
var builder = ArrayBuilder<KeyValuePair<string, Value>>.GetInstance();
var path = location.IsInSource ? location.SourceTree.FilePath : WellKnownStrings.None;
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSyntaxTreePath, path));
var spanInfoValue = GetSpanInfoValue(location.GetLineSpan());
builder.Add(KeyValuePair.Create(WellKnownStrings.LocationSpanInfo, spanInfoValue));
var coreLocationValue = Value.Create(builder.ToImmutableAndFree(), this);
// Our log format requires this to be wrapped.
var wrapperList = Value.Create(ImmutableArray.Create(coreLocationValue), this);
var wrapperKvp = KeyValuePair.Create(WellKnownStrings.Location, wrapperList);
return Value.Create(ImmutableArray.Create(wrapperKvp), this);
}
private Value GetSpanInfoValue(FileLinePositionSpan lineSpan)
{
var builder = ArrayBuilder<KeyValuePair<string, Value>>.GetInstance();
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanStartLine, lineSpan.StartLinePosition.Line));
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanStartColumn, lineSpan.StartLinePosition.Character));
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanEndLine, lineSpan.EndLinePosition.Line));
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanEndColumn, lineSpan.EndLinePosition.Character));
return Value.Create(builder.ToImmutableAndFree(), this);
}
private Value GetPropertiesValue(Issue issue)
{
var builder = ArrayBuilder<KeyValuePair<string, Value>>.GetInstance();
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.Severity, issue.Severity.ToString()));
if (issue.Severity == DiagnosticSeverity.Warning)
{
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.WarningLevel, issue.WarningLevel.ToString()));
}
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.DefaultSeverity, issue.DefaultSeverity.ToString()));
if (!string.IsNullOrEmpty(issue.Title))
{
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.Title, issue.Title));
}
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.Category, issue.Category));
if (!string.IsNullOrEmpty(issue.HelpLink))
{
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.HelpLink, issue.HelpLink));
}
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.IsEnabledByDefault, issue.IsEnabledByDefault.ToString()));
if (issue.CustomTags.Count > 0)
{
builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.CustomTags, issue.CustomTags.WhereNotNull().Join(";")));
}
if (issue.CustomProperties.Length > 0)
{
var customPropertiesBuilder = ArrayBuilder<KeyValuePair<string, Value>>.GetInstance();
foreach (var kvp in issue.CustomProperties)
{
customPropertiesBuilder.Add(CreateSimpleKeyValuePair(kvp.Key, kvp.Value));
}
var customPropertiesValue = Value.Create(customPropertiesBuilder.ToImmutableAndFree(), this);
builder.Add(KeyValuePair.Create(WellKnownStrings.CustomProperties, customPropertiesValue));
}
return Value.Create(builder.ToImmutableAndFree(), this);
}
#region Helper methods for core logging
private void WriteKeyValuePair(KeyValuePair<string, Value> kvp, bool isFirst)
{
WriteKeyValuePair(kvp.Key, kvp.Value, isFirst);
}
private void WriteKeyValuePair(string key, Value value, bool isFirst)
{
WriteKey(key, isFirst);
WriteValue(value);
}
private void WriteSimpleKeyValuePair(string key, string value, bool isFirst)
{
WriteKey(key, isFirst);
WriteValue(value);
}
private void WriteKey(string key, bool isFirst)
{
StartNewEntry(isFirst);
_writer.Write($"\"{key}\": ");
}
private void WriteValue(Value value, bool isFirst = true, bool valueInList = false)
{
if (!isFirst || valueInList)
{
StartNewEntry(isFirst);
}
value.Write();
}
private void WriteValue(string value)
{
_writer.Write($"\"{value}\"");
}
private void WriteValue(int value)
{
_writer.Write(value);
}
private void StartNewEntry(bool isFirst)
{
if (!isFirst)
{
_writer.WriteLine(',');
}
else
{
_writer.WriteLine();
}
_writer.Write(_currentIndent);
}
private void StartGroup()
{
StartGroupOrListCommon(groupStartChar);
}
private void EndGroup()
{
EndGroupOrListCommon(groupEndChar);
}
private void StartList()
{
StartGroupOrListCommon(listStartChar);
}
private void EndList()
{
EndGroupOrListCommon(listEndChar);
}
private void StartGroupOrListCommon(char startChar)
{
_writer.Write(startChar);
IncreaseIndentation();
}
private void EndGroupOrListCommon(char endChar)
{
_writer.WriteLine();
DecreaseIndentation();
_writer.Write(_currentIndent + endChar);
}
private void IncreaseIndentation()
{
_currentIndent += indentDelta;
}
private void DecreaseIndentation()
{
_currentIndent = _currentIndent.Substring(indentDelta.Length);
}
private KeyValuePair<string, Value> CreateSimpleKeyValuePair(string key, string value)
{
var stringValue = Value.Create(value, this);
return KeyValuePair.Create(key, stringValue);
}
private KeyValuePair<string, Value> CreateSimpleKeyValuePair(string key, int value)
{
var intValue = Value.Create(value, this);
return KeyValuePair.Create(key, intValue);
}
#endregion
public void Dispose()
{
// End issues list.
EndList();
// End dictionary for log file key-value pairs.
EndGroup();
_writer.Close();
}
}
}
......@@ -21,6 +21,7 @@ Microsoft.CodeAnalysis.CommandLineArguments.DocumentationPath.get -> string
Microsoft.CodeAnalysis.CommandLineArguments.EmitOptions.get -> Microsoft.CodeAnalysis.Emit.EmitOptions
Microsoft.CodeAnalysis.CommandLineArguments.EmitPdb.get -> bool
Microsoft.CodeAnalysis.CommandLineArguments.Encoding.get -> System.Text.Encoding
Microsoft.CodeAnalysis.CommandLineArguments.ErrorLogPath.get -> string
Microsoft.CodeAnalysis.CommandLineArguments.Errors.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic>
Microsoft.CodeAnalysis.CommandLineArguments.KeyFileSearchPaths.get -> System.Collections.Immutable.ImmutableArray<string>
Microsoft.CodeAnalysis.CommandLineArguments.ManifestResources.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ResourceDescription>
......
......@@ -286,15 +286,6 @@ internal class CodeAnalysisResources {
}
}
/// <summary>
/// Looks up a localized string similar to The DiagnosticDescriptor with ID &apos;{0}&apos; threw an exception with message &apos;{1}&apos;..
/// </summary>
internal static string DiagnosticDescriptorThrows {
get {
return ResourceManager.GetString("DiagnosticDescriptorThrows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A DiagnosticDescriptor must have an Id that is neiter null nor an empty string nor a string that only contains white space..
/// </summary>
......
......@@ -363,9 +363,6 @@
<data name="CompilerAnalyzerThrows" xml:space="preserve">
<value>The Compiler Analyzer '{0}' threw an exception with message '{1}'.</value>
</data>
<data name="DiagnosticDescriptorThrows" xml:space="preserve">
<value>The DiagnosticDescriptor with ID '{0}' threw an exception with message '{1}'.</value>
</data>
<data name="PEImageDoesntContainManagedMetadata" xml:space="preserve">
<value>PE image doesn't contain managed metadata.</value>
</data>
......
......@@ -151,6 +151,7 @@ public DiagnosticInfo FilterDiagnosticInfo(DiagnosticInfo diagnosticInfo, Compil
public abstract int WRN_AnalyzerCannotBeCreated { get; }
public abstract int WRN_NoAnalyzerInAssembly { get; }
public abstract int ERR_CantReadRulesetFile { get; }
public abstract int ERR_CompileCancelled { get; }
// compilation options:
public abstract int ERR_BadCompilationOptionValue { get; }
......
......@@ -966,7 +966,6 @@ internal static class AnalyzerDriverResources
{
internal static string AnalyzerFailure => CodeAnalysisResources.CompilerAnalyzerFailure;
internal static string AnalyzerThrows => CodeAnalysisResources.CompilerAnalyzerThrows;
internal static string DiagnosticDescriptorThrows => CodeAnalysisResources.DiagnosticDescriptorThrows;
internal static string ArgumentElementCannotBeNull => CodeAnalysisResources.ArgumentElementCannotBeNull;
internal static string ArgumentCannotBeEmpty => CodeAnalysisResources.ArgumentCannotBeEmpty;
internal static string UnsupportedDiagnosticReported => CodeAnalysisResources.UnsupportedDiagnosticReported;
......
......@@ -2,18 +2,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Immutable;
using System.IO;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities
{
internal class MockCSharpCompiler : CSharpCompiler
{
public MockCSharpCompiler(string responseFile, string baseDirectory, string[] args)
private readonly ImmutableArray<DiagnosticAnalyzer> _analyzers;
internal Compilation Compilation;
public MockCSharpCompiler(string responseFile, string baseDirectory, string[] args, DiagnosticAnalyzer analyzer = null)
: this(responseFile, baseDirectory, args, analyzer == null ? ImmutableArray<DiagnosticAnalyzer>.Empty : ImmutableArray.Create(analyzer))
{
}
public MockCSharpCompiler(string responseFile, string baseDirectory, string[] args, ImmutableArray<DiagnosticAnalyzer> analyzers)
: base(CSharpCommandLineParser.Default, responseFile, args, baseDirectory, Environment.GetEnvironmentVariable("LIB"))
{
_analyzers = analyzers;
}
protected override void CompilerSpecificSqm(IVsSqmMulti sqm, uint sqmSession)
......@@ -25,5 +34,23 @@ protected override uint GetSqmAppID()
{
throw new NotImplementedException();
}
protected override ImmutableArray<DiagnosticAnalyzer> ResolveAnalyzersFromArguments(List<DiagnosticInfo> diagnostics, CommonMessageProvider messageProvider, TouchedFileLogger touchedFiles)
{
var analyzers = base.ResolveAnalyzersFromArguments(diagnostics, messageProvider, touchedFiles);
if (!_analyzers.IsDefaultOrEmpty)
{
analyzers = analyzers.InsertRange(0, _analyzers);
}
return analyzers;
}
protected override Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger, ErrorLogger errorLogger)
{
Compilation = base.CreateCompilation(consoleOutput, touchedFilesLogger, errorLogger);
return Compilation;
}
}
}
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.VisualBasic
Imports System.Collections.Immutable
Imports System.IO
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.VisualStudio.Shell.Interop
Friend Class MockVisualBasicCompiler
Inherits VisualBasicCompiler
Sub New(baseDirectory As String, args As String())
MyClass.New(Nothing, baseDirectory, args)
Private ReadOnly _analyzers As ImmutableArray(Of DiagnosticAnalyzer)
Public Compilation As Compilation
Sub New(baseDirectory As String, args As String(), Optional analyzer As DiagnosticAnalyzer = Nothing)
MyClass.New(Nothing, baseDirectory, args, analyzer)
End Sub
Sub New(responseFile As String, baseDirectory As String, args As String(), Optional analyzer As DiagnosticAnalyzer = Nothing)
MyClass.New(responseFile, baseDirectory, args, If(analyzer Is Nothing, ImmutableArray(Of DiagnosticAnalyzer).Empty, ImmutableArray.Create(analyzer)))
End Sub
Sub New(responseFile As String, baseDirectory As String, args As String())
Sub New(responseFile As String, baseDirectory As String, args As String(), analyzers As ImmutableArray(Of DiagnosticAnalyzer))
MyBase.New(VisualBasicCommandLineParser.Default, responseFile, args, baseDirectory, Environment.GetEnvironmentVariable("LIB"))
_analyzers = analyzers
End Sub
Protected Overrides Function GetSqmAppID() As UInteger
......@@ -22,4 +33,18 @@ Friend Class MockVisualBasicCompiler
Throw New NotImplementedException
End Sub
Protected Overrides Function ResolveAnalyzersFromArguments(diagnostics As List(Of DiagnosticInfo), messageProvider As CommonMessageProvider, touchedFiles As TouchedFileLogger) As ImmutableArray(Of DiagnosticAnalyzer)
Dim analyzers = MyBase.ResolveAnalyzersFromArguments(diagnostics, messageProvider, touchedFiles)
If Not _analyzers.IsDefaultOrEmpty Then
analyzers = analyzers.InsertRange(0, _analyzers)
End If
Return analyzers
End Function
Protected Overrides Function CreateCompilation(consoleOutput As TextWriter, touchedFilesLogger As TouchedFileLogger, errorLogger As ErrorLogger) As Compilation
Compilation = MyBase.CreateCompilation(consoleOutput, touchedFilesLogger, errorLogger)
Return Compilation
End Function
End Class
......@@ -93,6 +93,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim outputFileName As String = Nothing
Dim outputDirectory As String = baseDirectory
Dim documentationPath As String = Nothing
Dim errorLogPath As String = Nothing
Dim parseDocumentationComments As Boolean = False ' Don't just null check documentationFileName because we want to do this even if the file name is invalid.
Dim outputKind As OutputKind = OutputKind.ConsoleApplication
Dim ssVersion As SubsystemVersion = SubsystemVersion.None
......@@ -459,6 +460,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
parseDocumentationComments = False
Continue For
Case "errorlog"
Dim unquoted = RemoveAllQuotes(value)
If String.IsNullOrEmpty(unquoted) Then
AddDiagnostic(diagnostics, ERRID.ERR_ArgumentRequired, "errorlog", ":<file>")
Else
errorLogPath = ParseGenericPathToFile(unquoted, diagnostics, baseDirectory)
End If
Continue For
Case "netcf"
' Do nothing as we no longer have any use for implementing this switch and
' want to avoid failing with any warnings/errors
......@@ -1168,6 +1179,7 @@ lVbRuntimePlus:
.OutputFileName = outputFileName,
.OutputDirectory = outputDirectory,
.DocumentationPath = documentationPath,
.ErrorLogPath = errorLogPath,
.SourceFiles = sourceFiles.AsImmutable(),
.Encoding = codepage,
.ChecksumAlgorithm = checksumAlgorithm,
......
......@@ -41,13 +41,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
parseOptions As VisualBasicParseOptions,
scriptParseOptions As VisualBasicParseOptions,
ByRef hadErrors As Boolean,
file As CommandLineSourceFile) As SyntaxTree
file As CommandLineSourceFile,
errorLogger As ErrorLogger) As SyntaxTree
Dim fileReadDiagnostics As New List(Of DiagnosticInfo)()
Dim content = ReadFileContent(file, fileReadDiagnostics, Arguments.Encoding, Arguments.ChecksumAlgorithm)
If content Is Nothing Then
PrintErrors(fileReadDiagnostics, consoleOutput)
ReportErrors(fileReadDiagnostics, consoleOutput, errorLogger)
fileReadDiagnostics.Clear()
hadErrors = True
Return Nothing
......@@ -64,7 +65,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return tree
End Function
Protected Overrides Function CreateCompilation(consoleOutput As TextWriter, touchedFilesLogger As TouchedFileLogger) As Compilation
Protected Overrides Function CreateCompilation(consoleOutput As TextWriter, touchedFilesLogger As TouchedFileLogger, errorLogger As ErrorLogger) As Compilation
Dim parseOptions = Arguments.ParseOptions
Dim scriptParseOptions = parseOptions.WithKind(SourceCodeKind.Script)
......@@ -78,12 +79,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
UICultureUtilities.WithCurrentUICulture(Of Integer)(
Sub(i As Integer)
' NOTE: order of trees is important!!
trees(i) = ParseFile(consoleOutput, parseOptions, scriptParseOptions, hadErrors, sourceFiles(i))
trees(i) = ParseFile(consoleOutput, parseOptions, scriptParseOptions, hadErrors, sourceFiles(i), errorLogger)
End Sub))
Else
For i = 0 To sourceFiles.Length - 1
' NOTE: order of trees is important!!
trees(i) = ParseFile(consoleOutput, parseOptions, scriptParseOptions, hadErrors, sourceFiles(i))
trees(i) = ParseFile(consoleOutput, parseOptions, scriptParseOptions, hadErrors, sourceFiles(i), errorLogger)
Next
End If
......@@ -107,7 +108,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim externalReferenceResolver = GetExternalMetadataResolver(touchedFilesLogger)
Dim resolvedReferences = ResolveMetadataReferences(externalReferenceResolver, metadataProvider, diagnostics, assemblyIdentityComparer, touchedFilesLogger, referenceDirectiveResolver)
If PrintErrors(diagnostics, consoleOutput) Then
If ReportErrors(diagnostics, consoleOutput, errorLogger) Then
Return Nothing
End If
......@@ -149,7 +150,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
consoleOutput.WriteLine()
End Sub
Friend Overrides Sub PrintError(Diagnostic As DiagnosticInfo, consoleOutput As TextWriter)
Protected Overrides Sub PrintError(Diagnostic As DiagnosticInfo, consoleOutput As TextWriter)
consoleOutput.Write(VisualBasicCompiler.VbcCommandLinePrefix)
consoleOutput.WriteLine(Diagnostic.ToString(Culture))
End Sub
......@@ -169,12 +170,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
''' </summary>
''' <param name="consoleOutput"></param>
Protected Overrides Sub PrintLogo(consoleOutput As TextWriter)
Dim thisAssembly As Assembly = Me.GetType().Assembly
consoleOutput.WriteLine(ErrorFactory.IdToString(ERRID.IDS_LogoLine1, Culture), FileVersionInfo.GetVersionInfo(thisAssembly.Location).FileVersion)
consoleOutput.WriteLine(ErrorFactory.IdToString(ERRID.IDS_LogoLine1, Culture), GetToolName(), GetAssemblyFileVersion())
consoleOutput.WriteLine(ErrorFactory.IdToString(ERRID.IDS_LogoLine2, Culture))
consoleOutput.WriteLine()
End Sub
Friend Overrides Function GetToolName() As String
Return ErrorFactory.IdToString(ERRID.IDS_ToolName, Culture)
End Function
Friend Overrides Function GetAssemblyFileVersion() As String
Return FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion
End Function
Friend Overrides Function GetAssemblyVersion() As Version
Return Assembly.GetExecutingAssembly().GetName().Version
End Function
''' <summary>
''' Print Commandline help message (up to 80 English characters per line)
''' </summary>
......
......@@ -1920,6 +1920,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
IDS_LogoLine2 = 56008
IDS_VBCHelp = 56009
IDS_InvalidPreprocessorConstantType = 56010
IDS_ToolName = 56011
' Feature codes
FEATURE_AutoProperties
......
......@@ -230,6 +230,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
Public Overrides ReadOnly Property ERR_CompileCancelled As Integer
Get
' TODO: Add an error code for CompileCancelled
Return ERRID.ERR_None
End Get
End Property
' compilation options:
Public Overrides ReadOnly Property ERR_BadCompilationOptionValue As Integer
......
......@@ -4988,8 +4988,11 @@
<data name="ChainingSpeculativeModelIsNotSupported" xml:space="preserve">
<value>Chaining speculative semantic model is not supported. You should create a speculative model from the non-speculative ParentModel.</value>
</data>
<data name="IDS_ToolName" xml:space="preserve">
<value>Microsoft (R) Visual Basic Compiler</value>
</data>
<data name="IDS_LogoLine1" xml:space="preserve">
<value>Microsoft (R) Visual Basic Compiler version {0}</value>
<value>{0} version {1}</value>
</data>
<data name="IDS_LogoLine2" xml:space="preserve">
<value>Copyright (C) Microsoft Corporation. All rights reserved.</value>
......@@ -5057,6 +5060,8 @@
/warnaserror[+|-]:&lt;number_list&gt; Treat a list of warnings as errors.
/ruleset:&lt;file&gt; Specify a ruleset file that disables specific
diagnostics.
/errorlog:&lt;file&gt; Specify a file to log all compiler and analyzer
diagnostics.
- LANGUAGE -
/define:&lt;symbol_list&gt; Declare global conditional compilation
......
......@@ -100,6 +100,7 @@
<Compile Include="CommandLineArgumentsTests.vb" />
<Compile Include="CommandLineBreakingChanges.vb" />
<Compile Include="CommandLineTests.vb" />
<Compile Include="ErrorLoggerTests.vb" />
<Compile Include="My Project\Application.Designer.vb">
<AutoGen>True</AutoGen>
<DependentUpon>Application.myapp</DependentUpon>
......
......@@ -2975,6 +2975,79 @@ End Class
Assert.Equal(Path.Combine(baseDirectory, "a.xml"), parsedArgs.DocumentationPath)
End Sub
<Fact>
Public Sub ParseErrorLog()
Const baseDirectory As String = "C:\abc\def\baz"
Dim parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify(
Diagnostic(ERRID.ERR_ArgumentRequired).WithArguments("errorlog", ":<file>"))
Assert.Null(parsedArgs.ErrorLogPath)
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify(
Diagnostic(ERRID.ERR_ArgumentRequired).WithArguments("errorlog", ":<file>"))
Assert.Null(parsedArgs.ErrorLogPath)
' Should preserve fully qualified paths
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:C:\MyFolder\MyBinary.xml", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal("C:\MyFolder\MyBinary.xml", parsedArgs.ErrorLogPath)
' Should handle quotes
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:C:\""My Folder""\MyBinary.xml", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal("C:\My Folder\MyBinary.xml", parsedArgs.ErrorLogPath)
' Should expand partially qualified paths
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:MyBinary.xml", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal(Path.Combine(baseDirectory, "MyBinary.xml"), parsedArgs.ErrorLogPath)
' Should expand partially qualified paths
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:..\MyBinary.xml", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal("C:\abc\def\MyBinary.xml", parsedArgs.ErrorLogPath)
' drive-relative path:
Dim currentDrive As Char = Directory.GetCurrentDirectory()(0)
Dim filePath = currentDrive + ":a.xml"
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:" + filePath, "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify(
Diagnostic(ERRID.FTL_InputFileNameTooLong).WithArguments(filePath))
Assert.Null(parsedArgs.ErrorLogPath)
' UNC
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:\\server\share\file.xml", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal("\\server\share\file.xml", parsedArgs.ErrorLogPath)
End Sub
<Fact>
Public Sub ParseErrorLogAndOut()
Const baseDirectory As String = "C:\abc\def\baz"
' Can specify separate directories for binary and error log output.
Dim parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:a\b.xml", "/out:c\d.exe", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal("C:\abc\def\baz\a\b.xml", parsedArgs.ErrorLogPath)
Assert.Equal("C:\abc\def\baz\c", parsedArgs.OutputDirectory)
Assert.Equal("d.exe", parsedArgs.OutputFileName)
' error log does not fall back on output directory.
parsedArgs = VisualBasicCommandLineParser.Default.Parse({"/errorlog:b.xml", "/out:c\d.exe", "a.vb"}, baseDirectory)
parsedArgs.Errors.Verify()
Assert.Equal("C:\abc\def\baz\b.xml", parsedArgs.ErrorLogPath)
Assert.Equal("C:\abc\def\baz\c", parsedArgs.OutputDirectory)
Assert.Equal("d.exe", parsedArgs.OutputFileName)
End Sub
<Fact>
Public Sub KeyContainerAndKeyFile()
' KEYCONTAINER
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Globalization
Imports System.IO
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.Test.Utilities.SharedResourceHelpers
Imports Microsoft.CodeAnalysis.VisualBasic.UnitTests
Imports Xunit
Imports Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers
Imports Microsoft.CodeAnalysis.DiagnosticExtensions
Namespace Microsoft.CodeAnalysis.VisualBasic.CommandLine.UnitTests
Public Class ErrorLoggerTests
Inherits BasicTestBase
Private ReadOnly _baseDirectory As String = TempRoot.Root
<Fact>
Public Sub NoDiagnostics()
Dim helloWorldVB As String = <text>
Imports System
Class C
Shared Sub Main(args As String())
Console.WriteLine("Hello, world")
End Sub
End Class
</text>.Value
Dim hello = Temp.CreateFile().WriteAllText(helloWorldVB).Path
Dim errorLogDir = Temp.CreateDirectory()
Dim errorLogFile = Path.Combine(errorLogDir.Path, "ErrorLog.txt")
Dim cmd = New MockVisualBasicCompiler(Nothing, _baseDirectory,
{"/nologo",
$"/errorlog:{errorLogFile}",
hello})
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Dim exitCode = cmd.Run(outWriter, Nothing)
Assert.Equal("", outWriter.ToString().Trim())
Assert.Equal(0, exitCode)
Dim actualOutput = File.ReadAllText(errorLogFile).Trim()
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
Dim expectedIssues = "
""issues"": [
]
}"
Dim expectedText = expectedHeader + expectedIssues
Assert.Equal(expectedText, actualOutput)
CleanupAllGeneratedFiles(hello)
CleanupAllGeneratedFiles(errorLogFile)
End Sub
<Fact>
Public Sub SimpleCompilerDiagnostics()
Dim source As String = <text>
Public Class C
Public Sub Method()
Dim x As Integer
End Sub
End Class
</text>.Value
Dim sourceFilePath = Temp.CreateFile().WriteAllText(source).Path
Dim errorLogDir = Temp.CreateDirectory()
Dim errorLogFile = Path.Combine(errorLogDir.Path, "ErrorLog.txt")
Dim cmd = New MockVisualBasicCompiler(Nothing, _baseDirectory,
{"/nologo",
"/preferreduilang:en",
$"/errorlog:{errorLogFile}",
sourceFilePath})
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Dim exitCode = cmd.Run(outWriter, Nothing)
Dim actualConsoleOutput = outWriter.ToString().Trim()
Assert.Contains("BC42024", actualConsoleOutput)
Assert.Contains("BC30420", actualConsoleOutput)
Assert.NotEqual(0, exitCode)
Dim actualOutput = File.ReadAllText(errorLogFile).Trim()
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
Dim expectedIssues = String.Format("
""issues"": [
{{
""ruleId"": ""BC42024"",
""locations"": [
{{
""analysisTarget"": [
{{
""uri"": ""{0}"",
""region"": {{
""startLine"": 3,
""startColumn"": 12,
""endLine"": 3,
""endColumn"": 13
}}
}}
]
}}
],
""fullMessage"": ""Unused local variable: 'x'."",
""properties"": {{
""severity"": ""Warning"",
""warningLevel"": ""1"",
""defaultSeverity"": ""Warning"",
""title"": ""Unused local variable"",
""category"": ""Compiler"",
""isEnabledByDefault"": ""True"",
""customTags"": ""Compiler;Telemetry""
}}
}},
{{
""ruleId"": ""BC30420"",
""locations"": [
{{
""analysisTarget"": [
{{
""uri"": ""<None>"",
""region"": {{
""startLine"": 0,
""startColumn"": 0,
""endLine"": 0,
""endColumn"": 0
}}
}}
]
}}
],
""fullMessage"": ""'Sub Main' was not found in '{1}'."",
""properties"": {{
""severity"": ""Error"",
""defaultSeverity"": ""Error"",
""category"": ""Compiler"",
""isEnabledByDefault"": ""True"",
""customTags"": ""Compiler;Telemetry;NotConfigurable""
}}
}}
]
}}", sourceFilePath, Path.GetFileNameWithoutExtension(sourceFilePath))
Dim expectedText = expectedHeader + expectedIssues
Assert.Equal(expectedText, actualOutput)
CleanupAllGeneratedFiles(sourceFilePath)
CleanupAllGeneratedFiles(errorLogFile)
End Sub
<Fact>
Public Sub AnalyzerDiagnosticsWithAndWithoutLocation()
Dim source As String = <text>
Imports System
Class C
End Class
</text>.Value
Dim sourceFilePath = Temp.CreateFile().WriteAllText(source).Path
Dim outputDir = Temp.CreateDirectory()
Dim errorLogFile = Path.Combine(outputDir.Path, "ErrorLog.txt")
Dim outputFilePath = Path.Combine(outputDir.Path, "test.dll")
Dim cmd = New MockVisualBasicCompiler(Nothing, _baseDirectory,
{"/nologo",
"/preferreduilang:en",
"/t:library",
$"/out:{outputFilePath}",
$"/errorlog:{errorLogFile}",
sourceFilePath},
analyzer:=New AnalyzerForErrorLogTest())
Dim outWriter = New StringWriter(CultureInfo.InvariantCulture)
Dim exitCode = cmd.Run(outWriter, Nothing)
Dim actualConsoleOutput = outWriter.ToString().Trim()
Assert.Contains(AnalyzerForErrorLogTest.Descriptor1.Id, actualConsoleOutput)
Assert.Contains(AnalyzerForErrorLogTest.Descriptor2.Id, actualConsoleOutput)
Assert.NotEqual(0, exitCode)
Dim actualOutput = File.ReadAllText(errorLogFile).Trim()
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
Dim expectedIssues = AnalyzerForErrorLogTest.GetExpectedErrorLogIssuesText(cmd.Compilation, outputFilePath)
Dim expectedText = expectedHeader + expectedIssues
Assert.Equal(expectedText, actualOutput)
CleanupAllGeneratedFiles(sourceFilePath)
CleanupAllGeneratedFiles(outputFilePath)
CleanupAllGeneratedFiles(errorLogFile)
End Sub
End Class
End Namespace
\ No newline at end of file
......@@ -6,7 +6,6 @@ internal static class AnalyzerDriverResources
{
internal static string AnalyzerFailure => FeaturesResources.UserDiagnosticAnalyzerFailure;
internal static string AnalyzerThrows => FeaturesResources.UserDiagnosticAnalyzerThrows;
internal static string DiagnosticDescriptorThrows => FeaturesResources.DiagnosticDescriptorThrows;
internal static string ArgumentElementCannotBeNull => FeaturesResources.ArgumentElementCannotBeNull;
internal static string ArgumentCannotBeEmpty => FeaturesResources.ArgumentCannotBeEmpty;
internal static string UnsupportedDiagnosticReported => FeaturesResources.UnsupportedDiagnosticReported;
......
......@@ -528,15 +528,6 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to The DiagnosticDescriptor with ID &apos;{0}&apos; threw an exception with message &apos;{1}&apos;..
/// </summary>
internal static string DiagnosticDescriptorThrows {
get {
return ResourceManager.GetString("DiagnosticDescriptorThrows", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to TODO: dispose managed state (managed objects)..
/// </summary>
......
......@@ -659,9 +659,6 @@ Do you want to continue?</value>
<data name="UserDiagnosticAnalyzerThrows" xml:space="preserve">
<value>The User Diagnostic Analyzer '{0}' threw an exception with message '{1}'.</value>
</data>
<data name="DiagnosticDescriptorThrows" xml:space="preserve">
<value>The DiagnosticDescriptor with ID '{0}' threw an exception with message '{1}'.</value>
</data>
<data name="RemoveUnnecessaryCast" xml:space="preserve">
<value>Remove Unnecessary Cast</value>
</data>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
public static class CommonDiagnosticAnalyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class AnalyzerForErrorLogTest : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor Descriptor1 = new DiagnosticDescriptor(
"ID1",
"Title1",
"Message1",
"Category1",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Description1",
helpLinkUri: "HelpLink1",
customTags: new[] { "1_CustomTag1", "1_CustomTag2" });
public static readonly DiagnosticDescriptor Descriptor2 = new DiagnosticDescriptor(
"ID2",
"Title2",
"Message2",
"Category2",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Description2",
helpLinkUri: "HelpLink2",
customTags: new[] { "2_CustomTag1", "2_CustomTag2" });
private static readonly ImmutableDictionary<string, string> _properties =
new Dictionary<string, string> { { "Key1", "Value1" }, { "Key2", "Value2" } }.ToImmutableDictionary();
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get
{
return ImmutableArray.Create(Descriptor1, Descriptor2);
}
}
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationAction(compilationContext =>
{
// With location diagnostic.
var location = compilationContext.Compilation.SyntaxTrees.First().GetRoot().GetLocation();
compilationContext.ReportDiagnostic(Diagnostic.Create(Descriptor1, location, _properties));
// No location diagnostic.
compilationContext.ReportDiagnostic(Diagnostic.Create(Descriptor2, Location.None, _properties));
});
}
private static string GetExpectedPropertiesMapText()
{
var isFirst = true;
var expectedText = @",
""customProperties"": {";
foreach (var kvp in _properties.OrderBy(kvp => kvp.Key))
{
if (!isFirst)
{
expectedText += ",";
}
expectedText += string.Format(@"
""{0}"": ""{1}""", kvp.Key, kvp.Value);
isFirst = false;
}
expectedText += @"
}";
return expectedText;
}
public static string GetExpectedErrorLogIssuesText(Compilation compilation, string outputFilePath)
{
var tree = compilation.SyntaxTrees.First();
var root = tree.GetRoot();
var expectedLineSpan = root.GetLocation().GetLineSpan();
// Pending Design: What should be the URI to emit for diagnostics with no location?
var noLocationUri = "<None>";
return @"
""issues"": [
{
""ruleId"": """ + Descriptor1.Id + @""",
""locations"": [
{
""analysisTarget"": [
{
""uri"": """ + tree.FilePath + @""",
""region"": {
""startLine"": " + expectedLineSpan.StartLinePosition.Line + @",
""startColumn"": " + expectedLineSpan.StartLinePosition.Character + @",
""endLine"": " + expectedLineSpan.EndLinePosition.Line + @",
""endColumn"": " + expectedLineSpan.EndLinePosition.Character + @"
}
}
]
}
],
""shortMessage"": """ + Descriptor1.MessageFormat + @""",
""fullMessage"": """ + Descriptor1.Description + @""",
""properties"": {
""severity"": """ + Descriptor1.DefaultSeverity + @""",
""warningLevel"": ""1"",
""defaultSeverity"": """ + Descriptor1.DefaultSeverity + @""",
""title"": """ + Descriptor1.Title + @""",
""category"": """ + Descriptor1.Category + @""",
""helpLink"": """ + Descriptor1.HelpLinkUri + @""",
""isEnabledByDefault"": """ + Descriptor1.IsEnabledByDefault + @""",
""customTags"": """ + Descriptor1.CustomTags.Join(";") + @"""" +
GetExpectedPropertiesMapText() + @"
}
},
{
""ruleId"": """ + Descriptor2.Id + @""",
""locations"": [
{
""analysisTarget"": [
{
""uri"": """ + noLocationUri + @""",
""region"": {
""startLine"": 0,
""startColumn"": 0,
""endLine"": 0,
""endColumn"": 0
}
}
]
}
],
""shortMessage"": """ + Descriptor2.MessageFormat + @""",
""fullMessage"": """ + Descriptor2.Description + @""",
""properties"": {
""severity"": """ + Descriptor2.DefaultSeverity + @""",
""defaultSeverity"": """ + Descriptor2.DefaultSeverity + @""",
""title"": """ + Descriptor2.Title + @""",
""category"": """ + Descriptor2.Category + @""",
""helpLink"": """ + Descriptor2.HelpLinkUri + @""",
""isEnabledByDefault"": """ + Descriptor2.IsEnabledByDefault + @""",
""customTags"": """ + Descriptor2.CustomTags.Join(";") + @"""" +
GetExpectedPropertiesMapText() + @"
}
}
]
}";
}
}
}
}
\ No newline at end of file
......@@ -248,5 +248,21 @@ public static AnalyzerReference GetCompilerDiagnosticAnalyzerReference(string la
builder.Add(LanguageNames.VisualBasic, ImmutableArray.Create(GetCompilerDiagnosticAnalyzerReference(LanguageNames.VisualBasic)));
return builder.ToImmutable();
}
internal static string GetExpectedErrorLogHeader(string actualOutput, CommonCompiler compiler)
{
var expectedToolName = compiler.GetToolName();
var expectedProductVersion = compiler.GetAssemblyVersion().ToString(fieldCount: 3);
var fileVersion = compiler.GetAssemblyFileVersion();
var expectedFileVersion = fileVersion.Substring(0, fileVersion.LastIndexOf('.'));
return string.Format(@"{{
""version"": ""{0}"",
""toolInfo"": {{
""toolName"": ""{1}"",
""productVersion"": ""{2}"",
""fileVersion"": ""{3}""
}},", ErrorLogger.OutputFormatVersion, expectedToolName, expectedProductVersion, expectedFileVersion);
}
}
}
......@@ -224,6 +224,11 @@ public override int ERR_CantReadRulesetFile
get { throw new NotImplementedException(); }
}
public override int ERR_CompileCancelled
{
get { throw new NotImplementedException(); }
}
public override void ReportDuplicateMetadataReferenceStrong(DiagnosticBag diagnostics, Location location, MetadataReference reference, AssemblyIdentity identity, MetadataReference equivalentReference, AssemblyIdentity equivalentIdentity)
{
throw new NotImplementedException();
......
......@@ -116,6 +116,7 @@
<ItemGroup>
<Compile Include="AssertEx.cs" />
<Compile Include="CLRHelpers.cs" />
<Compile Include="CommonDiagnosticAnalyzers.cs" />
<Compile Include="CommonTestBase.CompilationVerifier.cs" />
<Compile Include="CommonTestBase.cs" />
<Compile Include="CompilationDifference.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册