private static void WriteTo(ObjectWriter writer, DiagnosticDataLocation item, CancellationToken cancellationToken) { if (item == null) { writer.WriteBoolean(false); return; } else { writer.WriteBoolean(true); } if (item.SourceSpan.HasValue) { writer.WriteBoolean(true); writer.WriteInt32(item.SourceSpan.Value.Start); writer.WriteInt32(item.SourceSpan.Value.Length); } else { writer.WriteBoolean(false); } writer.WriteString(item.OriginalFilePath); writer.WriteInt32(item.OriginalStartLine); writer.WriteInt32(item.OriginalStartColumn); writer.WriteInt32(item.OriginalEndLine); writer.WriteInt32(item.OriginalEndColumn); writer.WriteString(item.MappedFilePath); writer.WriteInt32(item.MappedStartLine); writer.WriteInt32(item.MappedStartColumn); writer.WriteInt32(item.MappedEndLine); writer.WriteInt32(item.MappedEndColumn); }
public async Task DiagnosticData_SourceGeneratedDocumentLocationIsPreserved() { var content = @" namespace B { class A { } } "; using var workspace = TestWorkspace.CreateCSharp(files: Array.Empty <string>(), sourceGeneratedFiles: new[] { content }, composition: EditorTestCompositions.EditorFeatures); var hostDocument = workspace.Documents.Single(); Assert.True(hostDocument.IsSourceGenerated); var documentId = hostDocument.Id; var project = workspace.CurrentSolution.GetRequiredProject(documentId.ProjectId); var document = await project.GetSourceGeneratedDocumentAsync(documentId, CancellationToken.None); await VerifyTextSpanAsync(content, 3, 10, 3, 11, new TextSpan(28, 1)); var location = new DiagnosticDataLocation( documentId, sourceSpan: new TextSpan(28, 1), originalFilePath: document.FilePath, originalStartLine: 3, originalStartColumn: 10, originalEndLine: 3, originalEndColumn: 11); var diagnosticData = new DiagnosticData( id: "test1", category: "Test", message: "test1 message", severity: DiagnosticSeverity.Info, defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, warningLevel: 1, projectId: documentId.ProjectId, customTags: ImmutableArray <string> .Empty, properties: ImmutableDictionary <string, string> .Empty, location: location, additionalLocations: ImmutableArray <DiagnosticDataLocation> .Empty, language: project.Language); var diagnostic = await diagnosticData.ToDiagnosticAsync(project, CancellationToken.None); var roundTripDiagnosticData = DiagnosticData.Create(diagnostic, document); var roundTripLocation = roundTripDiagnosticData.DataLocation; Assert.NotNull(roundTripDiagnosticData.DataLocation); Assert.Equal(location.DocumentId, roundTripLocation.DocumentId); Assert.Equal(location.SourceSpan, roundTripLocation.SourceSpan); Assert.Equal(location.OriginalFilePath, roundTripLocation.OriginalFilePath); Assert.Equal(location.OriginalStartLine, roundTripLocation.OriginalStartLine); Assert.Equal(location.OriginalStartColumn, roundTripLocation.OriginalStartColumn); Assert.Equal(location.OriginalEndLine, roundTripLocation.OriginalEndLine); Assert.Equal(location.OriginalEndLine, roundTripLocation.OriginalEndLine); }
public async Task DiagnosticData_ExternalAdditionalLocationIsPreserved() { using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures); var additionalDocument = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp) .AddDocument("test.cs", "") .Project.AddAdditionalDocument("AdditionalDocument.txt", "First line in file", filePath: "AdditionalDocument.txt"); var document = additionalDocument.Project.Documents.Single(); var externalAdditionalLocation = new DiagnosticDataLocation( additionalDocument.Id, sourceSpan: new TextSpan(0, 1), originalFilePath: additionalDocument.Name, originalStartLine: 0, originalStartColumn: 0, originalEndLine: 0, originalEndColumn: 1); var diagnosticData = new DiagnosticData( id: "test1", category: "Test", message: "test1 message", enuMessageForBingSearch: "test1 message format", severity: DiagnosticSeverity.Info, defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, warningLevel: 1, projectId: document.Project.Id, customTags: ImmutableArray <string> .Empty, properties: ImmutableDictionary <string, string> .Empty, location: new DiagnosticDataLocation(document.Id), additionalLocations: new[] { externalAdditionalLocation }, language: document.Project.Language); var diagnostic = await diagnosticData.ToDiagnosticAsync(document.Project, CancellationToken.None); var roundTripDiagnosticData = DiagnosticData.Create(diagnostic, document); var roundTripAdditionalLocation = Assert.Single(roundTripDiagnosticData.AdditionalLocations); Assert.Equal(externalAdditionalLocation.DocumentId, roundTripAdditionalLocation.DocumentId); Assert.Equal(externalAdditionalLocation.SourceSpan, roundTripAdditionalLocation.SourceSpan); Assert.Equal(externalAdditionalLocation.OriginalFilePath, roundTripAdditionalLocation.OriginalFilePath); Assert.Equal(externalAdditionalLocation.OriginalStartLine, roundTripAdditionalLocation.OriginalStartLine); Assert.Equal(externalAdditionalLocation.OriginalStartColumn, roundTripAdditionalLocation.OriginalStartColumn); Assert.Equal(externalAdditionalLocation.OriginalEndLine, roundTripAdditionalLocation.OriginalEndLine); Assert.Equal(externalAdditionalLocation.OriginalEndLine, roundTripAdditionalLocation.OriginalEndLine); }
/// <summary> /// Gets <see cref="DiagnosticData"/> objects for selected error list entries. /// For remove suppression, the method also returns selected external source diagnostics. /// </summary> public async Task <ImmutableArray <DiagnosticData> > GetSelectedItemsAsync(bool isAddSuppression, CancellationToken cancellationToken) { var builder = ImmutableArray.CreateBuilder <DiagnosticData>(); Dictionary <string, Project> projectNameToProjectMapOpt = null; Dictionary <Project, ImmutableDictionary <string, Document> > filePathToDocumentMapOpt = null; foreach (var entryHandle in _tableControl.SelectedEntries) { cancellationToken.ThrowIfCancellationRequested(); DiagnosticData diagnosticData = null; int index; var roslynSnapshot = GetEntriesSnapshot(entryHandle, out index); if (roslynSnapshot != null) { diagnosticData = roslynSnapshot.GetItem(index)?.Primary; } else if (!isAddSuppression) { // For suppression removal, we also need to handle FxCop entries. bool isSuppressedEntry; if (!IsNonRoslynEntrySupportingSuppressionState(entryHandle, out isSuppressedEntry) || !isSuppressedEntry) { continue; } string errorCode = null, category = null, message = null, filePath = null, projectName = null; int line = -1; // FxCop only supports line, not column. DiagnosticDataLocation location = null; if (entryHandle.TryGetValue(StandardTableColumnDefinitions.ErrorCode, out errorCode) && !string.IsNullOrEmpty(errorCode) && entryHandle.TryGetValue(StandardTableColumnDefinitions.ErrorCategory, out category) && !string.IsNullOrEmpty(category) && entryHandle.TryGetValue(StandardTableColumnDefinitions.Text, out message) && !string.IsNullOrEmpty(message) && entryHandle.TryGetValue(StandardTableColumnDefinitions.ProjectName, out projectName) && !string.IsNullOrEmpty(projectName)) { if (projectNameToProjectMapOpt == null) { projectNameToProjectMapOpt = new Dictionary <string, Project>(); foreach (var p in _workspace.CurrentSolution.Projects) { projectNameToProjectMapOpt[p.Name] = p; } } cancellationToken.ThrowIfCancellationRequested(); Project project; if (!projectNameToProjectMapOpt.TryGetValue(projectName, out project)) { // bail out continue; } Document document = null; var hasLocation = (entryHandle.TryGetValue(StandardTableColumnDefinitions.DocumentName, out filePath) && !string.IsNullOrEmpty(filePath)) && (entryHandle.TryGetValue(StandardTableColumnDefinitions.Line, out line) && line >= 0); if (hasLocation) { if (string.IsNullOrEmpty(filePath) || line < 0) { // bail out continue; } ImmutableDictionary <string, Document> filePathMap; filePathToDocumentMapOpt = filePathToDocumentMapOpt ?? new Dictionary <Project, ImmutableDictionary <string, Document> >(); if (!filePathToDocumentMapOpt.TryGetValue(project, out filePathMap)) { filePathMap = await GetFilePathToDocumentMapAsync(project, cancellationToken).ConfigureAwait(false); filePathToDocumentMapOpt[project] = filePathMap; } if (!filePathMap.TryGetValue(filePath, out document)) { // bail out continue; } var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var linePosition = new LinePosition(line, 0); var linePositionSpan = new LinePositionSpan(start: linePosition, end: linePosition); var textSpan = (await tree.GetTextAsync(cancellationToken).ConfigureAwait(false)).Lines.GetTextSpan(linePositionSpan); location = new DiagnosticDataLocation(document.Id, textSpan, filePath, originalStartLine: linePosition.Line, originalStartColumn: linePosition.Character, originalEndLine: linePosition.Line, originalEndColumn: linePosition.Character); } Contract.ThrowIfNull(project); Contract.ThrowIfFalse((document != null) == (location != null)); // Create a diagnostic with correct values for fields we care about: id, category, message, isSuppressed, location // and default values for the rest of the fields (not used by suppression fixer). diagnosticData = new DiagnosticData( id: errorCode, category: category, message: message, enuMessageForBingSearch: message, severity: DiagnosticSeverity.Warning, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, warningLevel: 1, isSuppressed: isSuppressedEntry, title: message, location: location, customTags: SuppressionHelpers.SynthesizedExternalSourceDiagnosticCustomTags, properties: ImmutableDictionary <string, string> .Empty, workspace: _workspace, projectId: project.Id); } } if (IsEntryWithConfigurableSuppressionState(diagnosticData)) { builder.Add(diagnosticData); } } return(builder.ToImmutable()); }
private DiagnosticDataLocation UpdateDiagnosticLocation( DiagnosticDataLocation dataLocation, SyntaxTree tree, int delta) { var span = dataLocation.SourceSpan.Value; var start = Math.Min(Math.Max(span.Start + delta, 0), tree.Length); var newSpan = new TextSpan(start, start >= tree.Length ? 0 : span.Length); var mappedLineInfo = tree.GetMappedLineSpan(newSpan); var originalLineInfo = tree.GetLineSpan(newSpan); return new DiagnosticDataLocation(dataLocation.DocumentId, newSpan, originalFilePath: originalLineInfo.Path, originalStartLine: originalLineInfo.StartLinePosition.Line, originalStartColumn: originalLineInfo.StartLinePosition.Character, originalEndLine: originalLineInfo.EndLinePosition.Line, originalEndColumn: originalLineInfo.EndLinePosition.Character, mappedFilePath: mappedLineInfo.GetMappedFilePathIfExist(), mappedStartLine: mappedLineInfo.StartLinePosition.Line, mappedStartColumn: mappedLineInfo.StartLinePosition.Character, mappedEndLine: mappedLineInfo.EndLinePosition.Line, mappedEndColumn: mappedLineInfo.EndLinePosition.Character); }
public override bool TryGetValue(int index, string columnName, out object content) { // REVIEW: this method is too-chatty to make async, but otherwise, how one can implement it async? // also, what is cancellation mechanism? var item = GetItem(index); var data = item?.Data; if (data == null) { content = null; return(false); } switch (columnName) { case StandardTableKeyNames.Priority: content = ValueTypeCache.GetOrCreate((VSTASKPRIORITY)data.Value.Priority); return(content != null); case StandardTableKeyNames.Text: content = data.Value.Message; return(content != null); case StandardTableKeyNames.DocumentName: content = DiagnosticDataLocation.GetFilePath(data.Value.OriginalFilePath, data.Value.MappedFilePath); return(content != null); case StandardTableKeyNames.Line: content = GetLineColumn(item).Line; return(true); case StandardTableKeyNames.Column: content = GetLineColumn(item).Character; return(true); case StandardTableKeyNames.TaskCategory: content = ValueTypeCache.GetOrCreate(VSTASKCATEGORY.CAT_COMMENTS); return(content != null); case StandardTableKeyNames.ProjectName: content = item.ProjectName; return(content != null); case ProjectNames: var names = item.ProjectNames; content = names; return(names.Length > 0); case StandardTableKeyNames.ProjectGuid: content = ValueTypeCache.GetOrCreate(item.ProjectGuid); return((Guid)content != Guid.Empty); case ProjectGuids: var guids = item.ProjectGuids; content = guids; return(guids.Length > 0); default: content = null; return(false); } }