Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public async Task TestHostAnalyzerErrorNotLeaking()

var solution = workspace.CurrentSolution;

var analyzerReference = new AnalyzerImageReference([new LeakDocumentAnalyzer()]);
var analyzerReference = new AnalyzerImageReference([new LeakDocumentAnalyzer(), new LeakProjectAnalyzer()]);

var globalOptions = GetGlobalOptions(workspace);
globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution);
Expand Down Expand Up @@ -913,6 +913,13 @@ public override Task<ImmutableArray<Diagnostic>> AnalyzeSemanticsAsync(Document
=> SpecializedTasks.Default<ImmutableArray<Diagnostic>>();
}

private sealed class LeakProjectAnalyzer : ProjectDiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor("project", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [s_rule];
public override Task<ImmutableArray<Diagnostic>> AnalyzeProjectAsync(Project project, CancellationToken cancellationToken) => SpecializedTasks.Default<ImmutableArray<Diagnostic>>();
}

[DiagnosticAnalyzer(LanguageNames.CSharp)]
private sealed class NamedTypeAnalyzer : DiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ namespace Microsoft.CodeAnalysis.Diagnostics;
internal static class DiagnosticAnalyzerExtensions
{
public static bool IsWorkspaceDiagnosticAnalyzer(this DiagnosticAnalyzer analyzer)
=> analyzer is DocumentDiagnosticAnalyzer;
=> analyzer is DocumentDiagnosticAnalyzer
|| analyzer is ProjectDiagnosticAnalyzer;

public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer)
=> analyzer is IBuiltInAnalyzer || analyzer.IsWorkspaceDiagnosticAnalyzer() || analyzer.IsCompilerAnalyzer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api;

internal interface IVSTypeScriptDiagnosticAnalyzerImplementation
{
Task<ImmutableArray<Diagnostic>> AnalyzeProjectAsync(Project project, CancellationToken cancellationToken);
Task<ImmutableArray<Diagnostic>> AnalyzeDocumentSyntaxAsync(Document document, CancellationToken cancellationToken);
Task<ImmutableArray<Diagnostic>> AnalyzeDocumentSemanticsAsync(Document document, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api;

namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript;

[Shared]
[ExportLanguageService(typeof(VSTypeScriptDiagnosticAnalyzerLanguageService), InternalLanguageNames.TypeScript)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class VSTypeScriptDiagnosticAnalyzerLanguageService(
[Import(AllowDefault = true)] IVSTypeScriptDiagnosticAnalyzerImplementation? implementation = null) : ILanguageService
{
internal readonly IVSTypeScriptDiagnosticAnalyzerImplementation? Implementation = implementation;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript;

[DiagnosticAnalyzer(InternalLanguageNames.TypeScript)]
internal sealed class VSTypeScriptDocumentDiagnosticAnalyzer : DocumentDiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [];

public override Task<ImmutableArray<Diagnostic>> AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken)
{
var analyzer = document.Project.Services.GetRequiredService<VSTypeScriptDiagnosticAnalyzerLanguageService>().Implementation;
if (analyzer == null)
{
return SpecializedTasks.EmptyImmutableArray<Diagnostic>();
}

return analyzer.AnalyzeDocumentSyntaxAsync(document, cancellationToken);
}

public override Task<ImmutableArray<Diagnostic>> AnalyzeSemanticsAsync(Document document, CancellationToken cancellationToken)
{
var analyzer = document.Project.Services.GetRequiredService<VSTypeScriptDiagnosticAnalyzerLanguageService>().Implementation;
if (analyzer == null)
{
return SpecializedTasks.EmptyImmutableArray<Diagnostic>();
}

return analyzer.AnalyzeDocumentSemanticsAsync(document, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript;

[DiagnosticAnalyzer(InternalLanguageNames.TypeScript)]
internal sealed class VSTypeScriptProjectDiagnosticAnalyzer : ProjectDiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [];

public override Task<ImmutableArray<Diagnostic>> AnalyzeProjectAsync(Project project, CancellationToken cancellationToken)
{
var analyzer = project.Services.GetRequiredService<VSTypeScriptDiagnosticAnalyzerLanguageService>().Implementation;
if (analyzer == null)
{
return SpecializedTasks.EmptyImmutableArray<Diagnostic>();
}

return analyzer.AnalyzeProjectAsync(project, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,39 @@ public static async Task<ImmutableArray<Diagnostic>> ComputeDocumentDiagnosticAn
return diagnostics;
}

public static async Task<ImmutableArray<Diagnostic>> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(
ProjectDiagnosticAnalyzer analyzer,
Project project,
Compilation? compilation,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

ImmutableArray<Diagnostic> diagnostics;
try
{
diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty();
#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
}
catch (Exception e) when (!IsCanceled(e, cancellationToken))
{
diagnostics = [CreateAnalyzerExceptionDiagnostic(analyzer, e)];
}

// Apply filtering from compilation options (source suppressions, ruleset, etc.)
if (compilation != null)
{
diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilation).ToImmutableArrayOrEmpty();
}

return diagnostics;
}

private static bool IsCanceled(Exception ex, CancellationToken cancellationToken)
=> (ex as OperationCanceledException)?.CancellationToken == cancellationToken;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ private sealed partial class DiagnosticIncrementalAnalyzer
private async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticAnalysisResultsAsync(
CompilationWithAnalyzersPair? compilationWithAnalyzers,
Project project,
ImmutableArray<DocumentDiagnosticAnalyzer> analyzers,
ImmutableArray<DiagnosticAnalyzer> analyzers,
CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, analyzers, cancellationToken))
{
try
{
var result = await ComputeDiagnosticsForAnalyzersAsync(analyzers).ConfigureAwait(false);
var result = await ComputeDiagnosticsForIDEAnalyzersAsync(analyzers).ConfigureAwait(false);

// If project is not loaded successfully, get rid of any semantic errors from compiler analyzer.
// Note: In the past when project was not loaded successfully we did not run any analyzers on the project.
Expand Down Expand Up @@ -86,7 +86,7 @@ async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> Re
// Calculate all diagnostics for a given project using analyzers referenced by the project and specified IDE analyzers.
// </summary>
async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticsForAnalyzersAsync(
ImmutableArray<DocumentDiagnosticAnalyzer> ideAnalyzers)
ImmutableArray<DiagnosticAnalyzer> ideAnalyzers)
{
try
{
Expand All @@ -107,6 +107,7 @@ async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> Co
}

// check whether there is IDE specific project diagnostic analyzer
Debug.Assert(ideAnalyzers.All(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer));
return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(ideAnalyzers, result).ConfigureAwait(false);
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
Expand All @@ -115,42 +116,67 @@ async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> Co
}
}

async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticsForIDEAnalyzersAsync(
ImmutableArray<DiagnosticAnalyzer> analyzers)
{
try
{
var ideAnalyzers = analyzers.WhereAsArray(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer);

return await ComputeDiagnosticsForAnalyzersAsync(ideAnalyzers).ConfigureAwait(false);
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable();
}
}

async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> MergeProjectDiagnosticAnalyzerDiagnosticsAsync(
ImmutableArray<DocumentDiagnosticAnalyzer> ideAnalyzers,
ImmutableArray<DiagnosticAnalyzer> ideAnalyzers,
ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult> result)
{
try
{
var compilation = compilationWithAnalyzers?.HostCompilation;

foreach (var documentAnalyzer in ideAnalyzers)
foreach (var analyzer in ideAnalyzers)
{
var builder = new DiagnosticAnalysisResultBuilder(project);

foreach (var textDocument in project.AdditionalDocuments.Concat(project.Documents))
switch (analyzer)
{
var tree = textDocument is Document document
? await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)
: null;
var syntaxDiagnostics = await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, textDocument, AnalysisKind.Syntax, compilation, tree, cancellationToken).ConfigureAwait(false);
var semanticDiagnostics = await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, textDocument, AnalysisKind.Semantic, compilation, tree, cancellationToken).ConfigureAwait(false);

if (tree != null)
{
builder.AddSyntaxDiagnostics(tree, syntaxDiagnostics);
builder.AddSemanticDiagnostics(tree, semanticDiagnostics);
}
else
{
builder.AddExternalSyntaxDiagnostics(textDocument.Id, syntaxDiagnostics);
builder.AddExternalSemanticDiagnostics(textDocument.Id, semanticDiagnostics);
}
case DocumentDiagnosticAnalyzer documentAnalyzer:
foreach (var textDocument in project.AdditionalDocuments.Concat(project.Documents))
{
var tree = textDocument is Document document
? await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)
: null;
var syntaxDiagnostics = await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, textDocument, AnalysisKind.Syntax, compilation, tree, cancellationToken).ConfigureAwait(false);
var semanticDiagnostics = await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, textDocument, AnalysisKind.Semantic, compilation, tree, cancellationToken).ConfigureAwait(false);

if (tree != null)
{
builder.AddSyntaxDiagnostics(tree, syntaxDiagnostics);
builder.AddSemanticDiagnostics(tree, semanticDiagnostics);
}
else
{
builder.AddExternalSyntaxDiagnostics(textDocument.Id, syntaxDiagnostics);
builder.AddExternalSemanticDiagnostics(textDocument.Id, semanticDiagnostics);
}
}

break;

case ProjectDiagnosticAnalyzer projectAnalyzer:
builder.AddCompilationDiagnostics(await DocumentAnalysisExecutor.ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(projectAnalyzer, project, compilation, cancellationToken).ConfigureAwait(false));
break;
}

// merge the result to existing one.
// there can be existing one from compiler driver with empty set. overwrite it with
// ide one.
result = result.SetItem(documentAnalyzer, DiagnosticAnalysisResult.CreateFromBuilder(builder));
result = result.SetItem(analyzer, DiagnosticAnalysisResult.CreateFromBuilder(builder));
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ private static int GetPriority(DiagnosticAnalyzer state)
return state switch
{
DocumentDiagnosticAnalyzer analyzer => analyzer.Priority,
ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority),
_ => RegularDiagnosticAnalyzerPriority,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public DiagnosticIncrementalAnalyzer(
public Task<ImmutableArray<DiagnosticAnalyzer>> GetAnalyzersForTestingPurposesOnlyAsync(Project project, CancellationToken cancellationToken)
=> _stateManager.GetOrCreateAnalyzersAsync(project.Solution.SolutionState, project.State, cancellationToken);

private static string GetProjectLogMessage(Project project, ImmutableArray<DocumentDiagnosticAnalyzer> analyzers)
private static string GetProjectLogMessage(Project project, ImmutableArray<DiagnosticAnalyzer> analyzers)
=> $"project: ({project.Id}), ({string.Join(Environment.NewLine, analyzers.Select(a => a.ToString()))})";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> Ge
var compilation = await GetOrCreateCompilationWithAnalyzersAsync(
project, analyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false);

var result = await ComputeDiagnosticAnalysisResultsAsync(
compilation, project, [.. analyzers.OfType<DocumentDiagnosticAnalyzer>()], cancellationToken).ConfigureAwait(false);
var result = await ComputeDiagnosticAnalysisResultsAsync(compilation, project, analyzers, cancellationToken).ConfigureAwait(false);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ public async Task<ImmutableArray<DiagnosticData>> ForceAnalyzeProjectAsync(Proje
var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(
project, fullSolutionAnalysisAnalyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false);

var projectAnalysisData = await ComputeDiagnosticAnalysisResultsAsync(
compilationWithAnalyzers, project, [.. fullSolutionAnalysisAnalyzers.OfType<DocumentDiagnosticAnalyzer>()], cancellationToken).ConfigureAwait(false);
var projectAnalysisData = await ComputeDiagnosticAnalysisResultsAsync(compilationWithAnalyzers, project, fullSolutionAnalysisAnalyzers, cancellationToken).ConfigureAwait(false);
return (checksum, fullSolutionAnalysisAnalyzers, projectAnalysisData);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static DiagnosticAnalyzerCategory GetDiagnosticAnalyzerCategory(this Diag
{
FileContentLoadAnalyzer => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis,
DocumentDiagnosticAnalyzer => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis,
ProjectDiagnosticAnalyzer => DiagnosticAnalyzerCategory.None,
IBuiltInAnalyzer builtInAnalyzer => builtInAnalyzer.GetAnalyzerCategory(),

// Compiler analyzer supports syntax diagnostics, span-based semantic diagnostics and project level diagnostics.
Expand Down
Loading
Loading