Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix analyzer and code fix construction #1885

Merged
merged 2 commits into from
Jun 27, 2023
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
116 changes: 0 additions & 116 deletions src/Analyzers/AnalyzerAssemblyLoader.cs

This file was deleted.

56 changes: 41 additions & 15 deletions src/Analyzers/AnalyzerFinderHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,64 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;

using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal static class AnalyzerFinderHelpers
{
public static AnalyzersAndFixers LoadAnalyzersAndFixers(IEnumerable<Assembly> assemblies)
public static ImmutableArray<CodeFixProvider> LoadFixers(IEnumerable<Assembly> assemblies, string language)
{
var types = assemblies
.SelectMany(assembly => assembly.GetTypes()
.Where(type => !type.GetTypeInfo().IsInterface &&
!type.GetTypeInfo().IsAbstract &&
!type.GetTypeInfo().ContainsGenericParameters));

var codeFixProviders = types
return assemblies
.SelectMany(GetConcreteTypes)
.Where(t => typeof(CodeFixProvider).IsAssignableFrom(t))
.Select(type => type.TryCreateInstance<CodeFixProvider>(out var instance) ? instance : null)
.Where(t => IsExportedForLanguage(t, language))
.Select(CreateInstanceOfCodeFix)
.OfType<CodeFixProvider>()
.ToImmutableArray();
}

var diagnosticAnalyzers = types
.Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t))
.Select(type => type.TryCreateInstance<DiagnosticAnalyzer>(out var instance) ? instance : null)
.OfType<DiagnosticAnalyzer>()
.ToImmutableArray();
private static bool IsExportedForLanguage(Type codeFixProvider, string language)
{
var exportAttribute = codeFixProvider.GetCustomAttribute<ExportCodeFixProviderAttribute>(inherit: false);
return exportAttribute is not null && exportAttribute.Languages.Contains(language);
}

private static CodeFixProvider? CreateInstanceOfCodeFix(Type codeFixProvider)
{
try
{
return (CodeFixProvider?)Activator.CreateInstance(codeFixProvider);
}
catch
{
return null;
}
}

private static IEnumerable<Type> GetConcreteTypes(Assembly assembly)
{
try
{
var concreteTypes = assembly
.GetTypes()
.Where(type => !type.GetTypeInfo().IsInterface
&& !type.GetTypeInfo().IsAbstract
&& !type.GetTypeInfo().ContainsGenericParameters);

return new AnalyzersAndFixers(diagnosticAnalyzers, codeFixProviders);
// Realize the collection to ensure exceptions are caught
return concreteTypes.ToList();
}
catch
{
return Type.EmptyTypes;
}
}
}
}
3 changes: 2 additions & 1 deletion src/Analyzers/AnalyzerFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ public AnalyzerFormatter(
}

public async Task<Solution> FormatAsync(
Workspace workspace,
Solution solution,
ImmutableArray<DocumentId> formattableDocuments,
FormatOptions formatOptions,
ILogger logger,
List<FormattedFile> formattedFiles,
CancellationToken cancellationToken)
{
var projectAnalyzersAndFixers = _informationProvider.GetAnalyzersAndFixers(solution, formatOptions, logger);
var projectAnalyzersAndFixers = _informationProvider.GetAnalyzersAndFixers(workspace, solution, formatOptions, logger);
if (projectAnalyzersAndFixers.IsEmpty)
{
return solution;
Expand Down
20 changes: 13 additions & 7 deletions src/Analyzers/AnalyzerReferenceInformationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
Expand All @@ -19,25 +20,27 @@ internal class AnalyzerReferenceInformationProvider : IAnalyzerInformationProvid
private static readonly object s_guard = new();

public ImmutableDictionary<ProjectId, AnalyzersAndFixers> GetAnalyzersAndFixers(
Workspace workspace,
Solution solution,
FormatOptions formatOptions,
ILogger logger)
{
return solution.Projects
.ToImmutableDictionary(project => project.Id, GetAnalyzersAndFixers);
.ToImmutableDictionary(project => project.Id, project => GetAnalyzersAndFixers(workspace, project));
}

private AnalyzersAndFixers GetAnalyzersAndFixers(Project project)
private static AnalyzersAndFixers GetAnalyzersAndFixers(Workspace workspace, Project project)
{
var analyzerAssemblies = project.AnalyzerReferences
.Select(reference => TryLoadAssemblyFrom(reference.FullPath, reference))
.Select(reference => TryLoadAssemblyFrom(workspace, reference.FullPath, reference))
.OfType<Assembly>()
.ToImmutableArray();

return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(analyzerAssemblies);
var analyzers = project.AnalyzerReferences.SelectMany(reference => reference.GetAnalyzers(project.Language)).ToImmutableArray();
return new AnalyzersAndFixers(analyzers, AnalyzerFinderHelpers.LoadFixers(analyzerAssemblies, project.Language));
}

private static Assembly? TryLoadAssemblyFrom(string? path, AnalyzerReference analyzerReference)
private static Assembly? TryLoadAssemblyFrom(Workspace workspace, string? path, AnalyzerReference analyzerReference)
{
// Since we are not deploying these assemblies we need to ensure the files exist.
if (path is null || !File.Exists(path))
Expand All @@ -64,15 +67,18 @@ private AnalyzersAndFixers GetAnalyzersAndFixers(Project project)
}
else
{
var loader = new DefaultAnalyzerAssemblyLoader();
var analyzerService = workspace.Services.GetService<IAnalyzerService>() ?? throw new NotSupportedException();
var loader = analyzerService.GetLoader();
analyzerAssembly = loader.LoadFromPath(path);
}

s_pathsToAssemblies.Add(path, analyzerAssembly);

return analyzerAssembly;
}
catch { }
catch
{
}
}

return null;
Expand Down
37 changes: 29 additions & 8 deletions src/Analyzers/CodeStyleInformationProvider.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
Expand All @@ -17,20 +21,37 @@ internal class CodeStyleInformationProvider : IAnalyzerInformationProvider
private readonly string _featuresVisualBasicPath = Path.Combine(s_executingPath, "Microsoft.CodeAnalysis.VisualBasic.Features.dll");

public ImmutableDictionary<ProjectId, AnalyzersAndFixers> GetAnalyzersAndFixers(
Workspace workspace,
Solution solution,
FormatOptions formatOptions,
ILogger logger)
{
var assemblies = new[]
{
_featuresPath,
_featuresCSharpPath,
_featuresVisualBasicPath
}.Select(path => Assembly.LoadFrom(path));
var analyzerService = workspace.Services.GetService<IAnalyzerService>() ?? throw new NotSupportedException();
var analyzerAssemblyLoader = analyzerService.GetLoader();
var references = new[]
{
_featuresPath,
_featuresCSharpPath,
_featuresVisualBasicPath,
}
.Select(path => new AnalyzerFileReference(path, analyzerAssemblyLoader));

var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
var analyzersByLanguage = new Dictionary<string, AnalyzersAndFixers>();
return solution.Projects
.ToImmutableDictionary(project => project.Id, project => analyzersAndFixers);
.ToImmutableDictionary(
project => project.Id,
project =>
{
if (!analyzersByLanguage.TryGetValue(project.Language, out var analyzersAndFixers))
{
var analyzers = references.SelectMany(reference => reference.GetAnalyzers(project.Language)).ToImmutableArray();
var codeFixes = AnalyzerFinderHelpers.LoadFixers(references.Select(reference => reference.GetAssembly()), project.Language);
analyzersAndFixers = new AnalyzersAndFixers(analyzers, codeFixes);
analyzersByLanguage.Add(project.Language, analyzersAndFixers);
}

return analyzersAndFixers;
});
}

public DiagnosticSeverity GetSeverity(FormatOptions formatOptions) => formatOptions.CodeStyleSeverity;
Expand Down
Loading