/// <summary> /// Processes the provided <paramref name="project"/>, and computes the provided <paramref name="metrics"/>. /// </summary> /// <param name="parameters">The inspection parameters provided by the user.</param> /// <param name="project">The project to inspect.</param> /// <param name="metrics">The metrics to compute.</param> /// <param name="ct">A cancellation token.</param> private async Task <ProjectInspectionResult> ProcessAsync(InspectionParameters parameters, Project project, IList <IMetric> metrics, CancellationToken ct) { _logger.LogVerbose($"Inspecting project: {project.Name}"); var compilation = await project.GetCompilationAsync(ct); if (compilation is null) { throw new InspectionException($"Failed to compile project: {project.Name}"); } var registry = new Registry(); var resolver = new PackageResolver(_logger); var results = new List <PackageInspectionResult>(); var documents = project.Documents.ToImmutableHashSet(); var exclusions = NamespaceExclusionList.CreateFromParameters(parameters, project); await resolver.CreatePackageGraph(project, exclusions, ct); var memberLookupTable = await GetMemberAccessLookupTable(compilation, exclusions, ct); using (var portableExecutableLoadContext = new PortableExecutableLoadContext(project, _logger)) { foreach (var executable in portableExecutableLoadContext.GetExecutables(exclusions)) { var package = resolver.CreatePackage(executable); registry.AddPackage(package); foreach (var type in executable.ExportedTypes) { var compilationType = GetCompilationType(compilation, type); if (compilationType is null) { continue; } var isAdded = await AddConstructorReferences(project.Solution, documents, package, registry, compilationType, ct); if (isAdded) { await AddMemberReferences(project.Solution, documents, package, registry, compilationType, memberLookupTable, ct); } } results.Add(ComputeMetrics(project, compilation, package, metrics, registry)); } } results.AddRange(AddMissingExplicitPackages(resolver, exclusions, results)); return(ProjectInspectionResult.Ok(project, results)); }
/// <summary> /// Adds any packages discovered from the NuGet package graph that was not part of any /// references in the code. /// </summary> private static IEnumerable <PackageInspectionResult> AddMissingExplicitPackages(PackageResolver resolver, NamespaceExclusionList exclusions, List <PackageInspectionResult> results) { foreach (var package in resolver.GetPackages()) { if (exclusions.IsExcluded(package.Name)) { continue; } if (results.Any(result => result.Package.Equals(package))) { continue; } yield return(new PackageInspectionResult(package, new List <IMetricResult?>())); } }
/// <summary> /// Gets all member access syntax tokens from the Roslyn compilation syntax trees, and maps /// them into a lookup table. /// </summary> /// <param name="compilation">The Roslyn compilation of the project source-files.</param> /// <param name="exclusions">The list of namespace excluded from the inspection.</param> /// <param name="ct">A cancellation token.</param> private static async Task <IReadOnlyDictionary <INamespaceSymbol, ICollection <ISymbol> > > GetMemberAccessLookupTable(Compilation compilation, NamespaceExclusionList exclusions, CancellationToken ct) { var symbols = new List <ISymbol>(); foreach (var syntaxTree in compilation.SyntaxTrees) { var root = await syntaxTree.GetRootAsync(ct); var model = compilation.GetSemanticModel(syntaxTree); var intermediate = root .DescendantNodes() .OfType <MemberAccessExpressionSyntax>() .Select(node => model.GetSymbolInfo(node, ct).Symbol) .Where(symbol => symbol != null) .Where(symbol => !exclusions.IsExcluded(symbol !.ContainingNamespace)); symbols.AddRange(intermediate !); } var comparer = SymbolEqualityComparer.Default; var lookup = new Dictionary <INamespaceSymbol, ICollection <ISymbol> >(comparer); foreach (var group in symbols.GroupBy(symbol => symbol.ContainingNamespace, comparer)) { lookup.Add((INamespaceSymbol)group.Key !, group.ToList()); } return(lookup); }