diff --git a/docs/features/local-functions.md b/docs/features/local-functions.md
index 545eb88a735ae48d135afc102122c68604bd0711..8b45585648f47b163b4d57c1af049cb0c2d8d9ce 100644
--- a/docs/features/local-functions.md
+++ b/docs/features/local-functions.md
@@ -12,28 +12,29 @@ Syntax Grammar
This grammar is represented as a diff from the current spec grammar.
```diff
-declaration-statement
- : local-variable-declaration ';'
- | local-constant-declaration ';'
-+ | local-function-declaration
+ declaration_statement
+ : local_variable_declaration ';'
+ | local_constant_declaration ';'
++ | local_function_declaration
;
-+local-function-declaration
-+ : local-function-header local-function-body
++local_function_declaration
++ : local_function_header local_function_body
+ ;
-+local-function-header
-+ : local-function-modifiers? return-type identifier type-parameter-list?
-+ ( formal-parameter-list? ) type-parameter-constraints-clauses
++local_function_header
++ : local_function_modifier* return_type identifier type_parameter_list?
++ '(' formal_parameter_list? ')' type_parameter_constraints_clauses
+ ;
-+local-function-modifiers
-+ : (async | unsafe)
++local_function_modifier
++ : 'async'
++ | 'unsafe'
+ ;
-+local-function-body
++local_function_body
+ : block
-+ | arrow-expression-body
++ | arrow_expression_body
+ ;
```
diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs
index bd436b71a7ac9eba7f884a8d90c13f94024eadb0..e48a20a85e881a511600c2a3d6ef6950437197ac 100644
--- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs
@@ -916,6 +916,31 @@ public void TestReportingDiagnosticWithInvalidLocation()
}
}
+ [Fact]
+ public void TestReportingDiagnosticWithInvalidSpan()
+ {
+ var source1 = @"class C1 { void M() { int i = 0; i++; } }";
+ var compilation = CreateCompilationWithMscorlib45(source1, parseOptions: TestOptions.RegularWithIOperationFeature);
+ var treeInAnotherCompilation = compilation.SyntaxTrees.Single();
+
+ var badSpan = new Text.TextSpan(100000, 10000);
+
+ string message = new ArgumentException(
+ string.Format(CodeAnalysisResources.InvalidDiagnosticSpanReported, AnalyzerWithInvalidDiagnosticSpan.Descriptor.Id, badSpan, treeInAnotherCompilation.FilePath), "diagnostic").Message;
+
+ compilation.VerifyDiagnostics();
+
+ var analyzer = new AnalyzerWithInvalidDiagnosticSpan(badSpan);
+ var analyzers = new DiagnosticAnalyzer[] { analyzer };
+ compilation
+ .VerifyAnalyzerDiagnostics(analyzers, null, null, logAnalyzerExceptionAsDiagnostics: true,
+ expected:
+ Diagnostic("AD0001")
+ .WithArguments("Microsoft.CodeAnalysis.CommonDiagnosticAnalyzers+AnalyzerWithInvalidDiagnosticSpan", "System.ArgumentException", message)
+ .WithLocation(1, 1)
+ );
+ }
+
[Fact, WorkItem(13120, "https://github.com/dotnet/roslyn/issues/13120")]
public void TestRegisteringAsyncAnalyzerMethod()
{
@@ -1903,7 +1928,7 @@ public class RegularClass
var tree = CSharpSyntaxTree.ParseText(source, path: "Source.cs");
var compilation = CreateCompilationWithMscorlib45(new[] { tree });
compilation.VerifyDiagnostics();
-
+
var analyzers = new DiagnosticAnalyzer[] { new GeneratedCodeAnalyzer(GeneratedCodeAnalysisFlags.None) };
compilation.VerifyAnalyzerDiagnostics(analyzers, null, null, true,
Diagnostic("GeneratedCodeAnalyzerWarning", "}").WithArguments("Source.cs").WithLocation(11, 1),
diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs
index 128f8df04c48814c2da5d392e424f83d7c5df843..be93d74078b7f062c15fa52d77311098b08fc368 100644
--- a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs
+++ b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs
@@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class CodeAnalysisResources {
@@ -748,6 +748,15 @@ internal class CodeAnalysisResources {
}
}
+ ///
+ /// Looks up a localized string similar to Reported diagnostic '{0}' has a source location '{1}' in file '{2}', which is outside of the given file..
+ ///
+ internal static string InvalidDiagnosticSpanReported {
+ get {
+ return ResourceManager.GetString("InvalidDiagnosticSpanReported", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Invalid hash..
///
@@ -1334,7 +1343,7 @@ internal class CodeAnalysisResources {
}
///
- /// Looks up a localized string similar to Windows PDB writer doesn't support deterministic compilation: '{0}'.
+ /// Looks up a localized string similar to Windows PDB writer doesn't support SourceLink feature: '{0}'.
///
internal static string SymWriterDoesNotSupportSourceLink {
get {
@@ -1343,7 +1352,7 @@ internal class CodeAnalysisResources {
}
///
- /// Looks up a localized string similar to Windows PDB writer is not available -- could not find Microsoft.DiaSymReader.Native.{0}.dll.
+ /// Looks up a localized string similar to Windows PDB writer doesn't support deterministic compilation: '{0}'.
///
internal static string SymWriterNotDeterministic {
get {
diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx
index 17dde4a37fd63fb92561c6e0cf33c05fae6ab159..102db4f2b47edc27a8b3c8a21676fece1f940961 100644
--- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx
+++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx
@@ -612,4 +612,7 @@
Stream contains invalid data
+
+ Reported diagnostic '{0}' has a source location '{1}' in file '{2}', which is outside of the given file.
+
\ No newline at end of file
diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContextHelpers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContextHelpers.cs
index 4db78d5c4873e8ccccdedcb7cf01002659159525..58cf39ca3a59b8347fbc7dddde93508aa8fe00b0 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContextHelpers.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContextHelpers.cs
@@ -73,7 +73,7 @@ internal static void VerifyDiagnosticLocationsInCompilation(Diagnostic diagnosti
}
}
}
-
+
internal static void VerifyIOperationFeatureFlag(bool isIOperationFeatureEnabled)
{
if (!isIOperationFeatureEnabled)
@@ -84,11 +84,22 @@ internal static void VerifyIOperationFeatureFlag(bool isIOperationFeatureEnabled
private static void VerifyDiagnosticLocationInCompilation(string id, Location location, Compilation compilation)
{
- if (location.IsInSource && !compilation.ContainsSyntaxTree(location.SourceTree))
+ if (!location.IsInSource)
+ {
+ return;
+ }
+
+ if (!compilation.ContainsSyntaxTree(location.SourceTree))
{
// Disallow diagnostics with source locations outside this compilation.
throw new ArgumentException(string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, id, location.SourceTree.FilePath), "diagnostic");
}
+
+ if (location.SourceSpan.End > location.SourceTree.Length)
+ {
+ // Disallow diagnostics with source locations outside this compilation.
+ throw new ArgumentException(string.Format(CodeAnalysisResources.InvalidDiagnosticSpanReported, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
+ }
}
private static void VerifyAction(Action action)
diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs
index eb20ae761fa3ed5eaee8299869dded6731c89700..658b8cfe2c3020602d2dd120773a3aa4c46ea321 100644
--- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs
+++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
@@ -312,5 +313,34 @@ public void AnalyzeNode(SyntaxNodeAnalysisContext context)
}
}
}
+
+ [Fact]
+ public async Task TestDiagnosticSpan()
+ {
+ var source = @"// empty code";
+
+ var analyzer = new InvalidSpanAnalyzer();
+ using (var compilerEngineWorkspace = TestWorkspace.CreateCSharp(source))
+ {
+ var compilerEngineCompilation = (CSharpCompilation)(await compilerEngineWorkspace.CurrentSolution.Projects.Single().GetCompilationAsync());
+
+ var diagnostics = compilerEngineCompilation.GetAnalyzerDiagnostics(new[] { analyzer });
+ AssertEx.Any(diagnostics, d => d.Id == AnalyzerHelper.AnalyzerExceptionDiagnosticId);
+ }
+ }
+
+ private class InvalidSpanAnalyzer : DiagnosticAnalyzer
+ {
+ public static DiagnosticDescriptor Descriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic");
+
+ public override ImmutableArray SupportedDiagnostics
+ => ImmutableArray.Create(Descriptor);
+
+ public override void Initialize(AnalysisContext context)
+ => context.RegisterSyntaxTreeAction(Analyze);
+
+ private void Analyze(SyntaxTreeAnalysisContext context)
+ => context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.Create(context.Tree, TextSpan.FromBounds(1000, 2000))));
+ }
}
}
diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.TaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.TaggerProvider.cs
index 047ff9592b0159bb3c7da9a3aac07f62315bdbdb..d3879ced7d0fc1f9f3f45e7de82e33fb74bb8ed1 100644
--- a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.TaggerProvider.cs
+++ b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.TaggerProvider.cs
@@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
+using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
@@ -63,7 +64,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, I
// We act as a source of events ourselves. When the diagnostics service tells
// us about new diagnostics, we'll use that to kick of the asynchronous tagging
// work.
-
+
var eventSource = TaggerEventSources.Compose(
TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.Medium),
this);
@@ -83,69 +84,79 @@ protected override Task ProduceTagsAsync(TaggerContext context, DocumentSn
private void ProduceTags(TaggerContext context, DocumentSnapshotSpan spanToTag)
{
- if (!_owner.IsEnabled)
+ try
{
- return;
- }
+ if (!_owner.IsEnabled)
+ {
+ return;
+ }
- var document = spanToTag.Document;
- if (document == null)
- {
- return;
- }
+ var document = spanToTag.Document;
+ if (document == null)
+ {
+ return;
+ }
- // See if we've marked any spans as those we want to suppress diagnostics for.
- // This can happen for buffers used in the preview workspace where some feature
- // is generating code that it doesn't want errors shown for.
- var buffer = spanToTag.SnapshotSpan.Snapshot.TextBuffer;
- var suppressedDiagnosticsSpans = default(NormalizedSnapshotSpanCollection);
- buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans);
-
- // Producing tags is simple. We just grab the diagnostics we were already told about,
- // and we convert them to tag spans.
- object id;
- ImmutableArray diagnostics;
- SourceText sourceText;
- ITextSnapshot editorSnapshot;
- lock (_gate)
- {
- id = _latestId;
- diagnostics = _latestDiagnostics;
- sourceText = _latestSourceText;
- editorSnapshot = _latestEditorSnapshot;
- }
+ // See if we've marked any spans as those we want to suppress diagnostics for.
+ // This can happen for buffers used in the preview workspace where some feature
+ // is generating code that it doesn't want errors shown for.
+ var buffer = spanToTag.SnapshotSpan.Snapshot.TextBuffer;
+ var suppressedDiagnosticsSpans = default(NormalizedSnapshotSpanCollection);
+ buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans);
+
+ // Producing tags is simple. We just grab the diagnostics we were already told about,
+ // and we convert them to tag spans.
+ object id;
+ ImmutableArray diagnostics;
+ SourceText sourceText;
+ ITextSnapshot editorSnapshot;
+ lock (_gate)
+ {
+ id = _latestId;
+ diagnostics = _latestDiagnostics;
+ sourceText = _latestSourceText;
+ editorSnapshot = _latestEditorSnapshot;
+ }
- if (sourceText == null || editorSnapshot == null)
- {
- return;
- }
+ if (sourceText == null || editorSnapshot == null)
+ {
+ return;
+ }
- var project = document.Project;
+ var project = document.Project;
- var requestedSnapshot = spanToTag.SnapshotSpan.Snapshot;
- var requestedSpan = spanToTag.SnapshotSpan;
- var isLiveUpdate = id is ISupportLiveUpdate;
+ var requestedSnapshot = spanToTag.SnapshotSpan.Snapshot;
+ var requestedSpan = spanToTag.SnapshotSpan;
+ var isLiveUpdate = id is ISupportLiveUpdate;
- foreach (var diagnosticData in diagnostics)
- {
- if (_owner.IncludeDiagnostic(diagnosticData))
+ foreach (var diagnosticData in diagnostics)
{
- var actualSpan = diagnosticData
- .GetExistingOrCalculatedTextSpan(sourceText)
- .ToSnapshotSpan(editorSnapshot)
- .TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
-
- if (actualSpan.IntersectsWith(requestedSpan) &&
- !IsSuppressed(suppressedDiagnosticsSpans, actualSpan))
+ if (_owner.IncludeDiagnostic(diagnosticData))
{
- var tagSpan = _owner.CreateTagSpan(isLiveUpdate, actualSpan, diagnosticData);
- if (tagSpan != null)
+ var actualSpan = diagnosticData
+ .GetExistingOrCalculatedTextSpan(sourceText)
+ .ToSnapshotSpan(editorSnapshot)
+ .TranslateTo(requestedSnapshot, SpanTrackingMode.EdgeExclusive);
+
+ if (actualSpan.IntersectsWith(requestedSpan) &&
+ !IsSuppressed(suppressedDiagnosticsSpans, actualSpan))
{
- context.AddTag(tagSpan);
+ var tagSpan = _owner.CreateTagSpan(isLiveUpdate, actualSpan, diagnosticData);
+ if (tagSpan != null)
+ {
+ context.AddTag(tagSpan);
+ }
}
}
}
}
+ catch (ArgumentOutOfRangeException ex) when (FatalError.ReportWithoutCrash(ex))
+ {
+ // https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=428328&_a=edit&triage=false
+ // explicitly report NFW to find out what is causing us for out of range.
+ // stop crashing on such occations
+ return;
+ }
}
private bool IsSuppressed(NormalizedSnapshotSpanCollection suppressedSpans, SnapshotSpan span)
diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs
index 0cef847a5ceff3eeda020fbb42fe5fe40e738fb3..ec54350ba0dbeb32627d97bba1aebe0d776e5523 100644
--- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs
+++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs
@@ -377,7 +377,7 @@ public IEnumerable ConvertToLocalDiagnostics(Document targetDocu
try
{
- var diagnostics = await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false);
+ var diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty();
// Apply filtering from compilation options (source suppressions, ruleset, etc.)
if (compilationOpt != null)
@@ -385,6 +385,13 @@ public IEnumerable ConvertToLocalDiagnostics(Document targetDocu
diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt).ToImmutableArrayOrEmpty();
}
+#if DEBUG
+ // since all ProjectDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime
+ // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should
+ // from intern teams.
+ await VerifyDiagnosticLocationsAsync(diagnostics, project, cancellationToken).ConfigureAwait(false);
+#endif
+
return diagnostics;
}
catch (Exception e) when (!IsCanceled(e, cancellationToken))
@@ -423,6 +430,13 @@ public IEnumerable ConvertToLocalDiagnostics(Document targetDocu
return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt);
}
+#if DEBUG
+ // since all DocumentDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime
+ // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should
+ // from intern teams.
+ await VerifyDiagnosticLocationsAsync(diagnostics, document.Project, cancellationToken).ConfigureAwait(false);
+#endif
+
return diagnostics;
}
catch (Exception e) when (!IsCanceled(e, cancellationToken))
@@ -584,6 +598,86 @@ private IEnumerable ConvertToLocalDiagnosticsWithCompilation(Doc
}
}
+ private static async Task VerifyDiagnosticLocationsAsync(ImmutableArray diagnostics, Project project, CancellationToken cancellationToken)
+ {
+ foreach (var diagnostic in diagnostics)
+ {
+ await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location, project, cancellationToken).ConfigureAwait(false);
+
+ if (diagnostic.AdditionalLocations != null)
+ {
+ foreach (var location in diagnostic.AdditionalLocations)
+ {
+ await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location, project, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+
+ private static async Task VerifyDiagnosticLocationAsync(string id, Location location, Project project, CancellationToken cancellationToken)
+ {
+ switch (location.Kind)
+ {
+ case LocationKind.None:
+ case LocationKind.MetadataFile:
+ case LocationKind.XmlFile:
+ // ignore these kinds
+ break;
+ case LocationKind.SourceFile:
+ {
+ if (project.GetDocument(location.SourceTree) == null)
+ {
+ // Disallow diagnostics with source locations outside this project.
+ throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_in_file_1_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceTree.FilePath), "diagnostic");
+ }
+
+ if (location.SourceSpan.End > location.SourceTree.Length)
+ {
+ // Disallow diagnostics with source locations outside this project.
+ throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic");
+ }
+ }
+ break;
+ case LocationKind.ExternalFile:
+ {
+ var filePath = location.GetLineSpan().Path;
+ var document = TryGetDocumentWithFilePath(project, filePath);
+ if (document == null)
+ {
+ // this is not a roslyn file. we don't care about this file.
+ return;
+ }
+
+ // this can be potentially expensive since it will load text if it is not already loaded.
+ // but, this text is most likely already loaded since producer of this diagnostic (Document/ProjectDiagnosticAnalyzers)
+ // should have loaded it to produce the diagnostic at the first place. once loaded, it should stay in memory until
+ // project cache goes away. when text is already there, await should return right away.
+ var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ if (location.SourceSpan.End > text.Length)
+ {
+ // Disallow diagnostics with locations outside this project.
+ throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, filePath), "diagnostic");
+ }
+ }
+ break;
+ default:
+ throw ExceptionUtilities.Unreachable;
+ }
+ }
+
+ private static Document TryGetDocumentWithFilePath(Project project, string path)
+ {
+ foreach (var documentId in project.Solution.GetDocumentIdsWithFilePath(path))
+ {
+ if (documentId.ProjectId == project.Id)
+ {
+ return project.GetDocument(documentId);
+ }
+ }
+
+ return null;
+ }
+
private static void GetLogFunctionIdAndTitle(AnalysisKind kind, out FunctionId functionId, out string title)
{
switch (kind)
diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs
index 0f4939afaed8c753f85dd3865f6fd1f9d42b52d1..0d5a25f899e0d2644be1493d11ed80801444d625 100644
--- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs
+++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs
@@ -225,7 +225,7 @@ public async Task SaveAsync(Project project, DiagnosticAnalysisResult result)
await SerializeAsync(serializer, project, result.ProjectId, _owner.NonLocalStateName, result.Others).ConfigureAwait(false);
- AnalyzerABTestLogger.LogProjectDiagnostics(project, result);
+ AnalyzerABTestLogger.LogProjectDiagnostics(project, _owner.StateName, result);
}
public void ResetVersion()
@@ -244,7 +244,7 @@ public async Task MergeAsync(ActiveFileState state, Document document)
var syntax = state.GetAnalysisData(AnalysisKind.Syntax);
var semantic = state.GetAnalysisData(AnalysisKind.Semantic);
- AnalyzerABTestLogger.LogDocumentDiagnostics(document, syntax.Items, semantic.Items);
+ AnalyzerABTestLogger.LogDocumentDiagnostics(document, _owner.StateName, syntax.Items, semantic.Items);
var project = document.Project;
diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs
index bd7260fbfb231db5ef2f23c3603dc725e5543069..30d10234e191afb15bbcb1c9dffdb05f5f69cdfd 100644
--- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs
+++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs
@@ -1,7 +1,9 @@
// 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.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
@@ -15,6 +17,8 @@ internal partial class DiagnosticIncrementalAnalyzer
{
public async Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> map)
{
+ DebugVerifyDiagnosticLocations(map);
+
if (!PreferBuildErrors(workspace))
{
// prefer live errors over build errors
@@ -51,6 +55,18 @@ public async Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictio
}
}
+ [Conditional("DEBUG")]
+ private void DebugVerifyDiagnosticLocations(ImmutableDictionary> map)
+ {
+ foreach (var diagnostic in map.Values.SelectMany(v => v))
+ {
+ // errors from build shouldn't have any span set.
+ // this is debug check since it gets data from us only not from third party unlike one in compiler
+ // that checks span for third party reported diagnostics
+ Contract.Requires(!diagnostic.HasTextSpan);
+ }
+ }
+
private async Task CreateProjectAnalysisDataAsync(Project project, ImmutableArray stateSets, ImmutableArray diagnostics)
{
// we always load data sicne we don't know right version.
diff --git a/src/Features/Core/Portable/Experimentation/AnalyzerABTestLogger.cs b/src/Features/Core/Portable/Experimentation/AnalyzerABTestLogger.cs
index fd2f932d98ccd4e19afea08dcb1e153f4b0dabd9..c93696fdf008bfad1c16e7642988fb7df7d776fb 100644
--- a/src/Features/Core/Portable/Experimentation/AnalyzerABTestLogger.cs
+++ b/src/Features/Core/Portable/Experimentation/AnalyzerABTestLogger.cs
@@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Experimentation
internal static class AnalyzerABTestLogger
{
private static bool s_reportErrors = false;
- private static readonly ConcurrentDictionary