Skip to content

Commit

Permalink
Merge pull request #10 from Acumatica/feature/AC-333341-rework-groupi…
Browse files Browse the repository at this point in the history
…ng-by-source-file

Feature/ac 333341 rework grouping by source file
  • Loading branch information
SENya1990 authored Jan 22, 2025
2 parents 17955a2 + 5494256 commit d0cec6a
Show file tree
Hide file tree
Showing 25 changed files with 1,036 additions and 586 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# CoreCompatibilyzer

Welcome to CoreCompatibilyzer, a static .Net code analyzer that checks .Net Framework code for compatibility with .Net Core 2.2. There are two options to run the analysis - either install CoreCompatibilyzer VSIX extension or use the console runner.
You can find Release Notes [here](./docs/ReleaseNotes.md).

## Analysis

The analysis itself is not very complex. All API usages are checked against a list of the APIs incompatible with .Net Core 2.2. If API, its containing types or containing namespace are in the list of incompatible APIs, then it is marked as incompatible.
There is a list of banned APIs that is stored in the tool's `.\ApiData\Data` subfolder in the `BannedApis.txt` file. There is also a file `WhiteList.txt` with the whitelisted APIs which are not reported by the analyzer even if they are recognized as incompatible.
Sometimes code may contain types declared in system namespaces such as `System.Web.Compilation.CustomBuildManager` and we don't want to report such types. The open list of APIs loaded by application at runtime provides an easy way to configure the analysis.

You can find list of diagnostics in this [summary](./docs/Summary.md).

You can forbid the usage of some API by adding it to the list of banned APIs or white list an existing banned API by adding it to the list of allowed APIs. The format of API records is described in the next section:

### API format
Expand Down Expand Up @@ -41,10 +45,11 @@ Below is the list of command line arguments:
| ‑‑withUsages | By default, the report output includes only a shortened list of used banned API. Set this flag to include the locations of used banned API calls into the report. |
| ‑‑withDistinctApis | If this flag is specified then the report will start with a list of all distinct APIs used by the code source. |
| ‑‑showMembersOfUsedType | When report is displayed in a shortened form without banned API calls locations, it could be shortened even more. By default, the report will not display used banned type member APIs if their containing type is also banned and used by the code being analyzed. Set this flag to include the banned type member APIs into the report together with their containing type. This flag does not affect the report when the `--withUsages` is specified. |
| -g, ‑‑grouping | This parameter allows you to specify the grouping of API calls. By default there is no grouping. You can make the grouping of the reported API calls by namespaces, types, APIs or any combination of them: |
| | - Add `n` or `N` to group API usages by namespaces, |
| | - Add `t` or `T` to group API usages by types, |
| | - Add `a` or `A` to group API usages by APIs. |
| -g, ‑‑grouping | This parameter allows you to specify the grouping of API calls. By default there is no grouping. You can specify the grouping of the reported API calls by namespaces, types, APIs or any combination of them using the following values: |
| | - Add `f` or `F` to group the API usages by source files. |
| | - Add `n` or `N` to group the API usages by namespaces. |
| | - Add `t` or `T` to group the API usages by types. |
| | - Add `a` or `A` to group the API usages by APIs. |
| -f, ‑‑file | The name of the output file. If not specified then the report will be outputted to the console window. |
| ‑‑outputAbsolutePaths | When report is set to output the detailed list of banned APIs with their usages this flag regulates how the locations of API usages will be output. By default, file paths in locations are relative to the containing project directory. However, if this flag is set then the absolute file paths will be used. This flag does not affect the report when the `--withUsages` is not specified. |
| ‑‑format | The report output format. There are two supported values: |
Expand Down
39 changes: 39 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# CoreCompatibilyzer Release Notes
This document provides information about fixes, enhancements, and key features that are available in CoreCompatibilyzer.

## CoreComparibilyzer 1.1.0 (Current): January 23, 2025
CoreCompatibilyzer 1.1.0 provides new features and bugfixes described in this section as well as the features described below for previous versions of CoreCompatibilyzer.

### New Features
CoreComparibilyzer 1.1.0 introduces the following new features:
- A way of grouping reported APIs by source files that contain the reported calls to incorrect APIs.
- Support of ARM64 architecture in a Visual Studio extension.

### Code Fixes
CoreComparibilyzer 1.1.0 contains the following code fixes:
- Incorectly banned APIs have been removed from the list of banned APIs incompatible with .Net Core 2.2.
- Analysis of extension methods that were not recognized as banned APIs has been fixed.
- Support for analysis of conditional access expressions (like `obj?.Member`) has been added.
- Analysis of member access expression has been fixed by analyzing the type of the accessed expression.
- Analysis of array type symbols has been fixed.
- The `.editorconfig` file has been added to the CoreCompatibilyzer project to enforce consistent code style.
- CoreCompatibilyzer code has been refactored to improve performance and readability.


## CoreComparibilyzer 1.0.0: September 1, 2023
CoreComparibilyzer 1.0.0 is the initial release that introduces the following features.

### Diagnostics
CoreComparibilyzer finds usages of .Net Framework APIs incompatible with .Net Core 2.2 and reports them with one of the two diagnostics:

| Code | Short Description | Type | Code Fix |
| ------ | ------------------------------------------------------- | ----- | --------- |
| [CoreCompat1001](diagnostics/CoreCompat1001.md) | The reported API is missing in the .Net Core 2.2 runtime. | Error | Unavailable |
| [CoreCompat1002](diagnostics/CoreCompat1002.md) | The underlined API is not portable to .Net Core 2.2 runtime because the API is obsolete. | Error | Unavailable |

### Visual Studio Extension
CoreComparibilyzer provides an extension for Visual Studio to see analyzer alerts directly in the code editor.

### Console Runner
CoreComparibilyzer provides console runner to run the analysis from the command line for the entire solution or project manually or in CI scripts. The console runner also provides advanced options for report formatting style and output format.
There are several available command line arguments that configure them. You can run `--help` to see the available options and their descriptions in the console window.
2 changes: 1 addition & 1 deletion docs/Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ In this document, you can find the list of diagnostics supported by CoreCompatib
| Code | Short Description | Type | Code Fix |
| ------ | ------------------------------------------------------- | ----- | --------- |
| [CoreCompat1001](diagnostics/CoreCompat1001.md) | The reported API is missing in the .Net Core 2.2 runtime. | Error | Unavailable |
| [CoreCompat1002](diagnostics/CoreCompat1002.md) | The underlined API is not portable to .Net Core 2.2 runtime because it is obsolete. | Error | Unavailable |
| [CoreCompat1002](diagnostics/CoreCompat1002.md) | The underlined API is not portable to .Net Core 2.2 runtime because the API is obsolete. | Error | Unavailable |
4 changes: 2 additions & 2 deletions docs/diagnostics/CoreCompat1002.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ This document describes the CoreCompat1002 diagnostic.

| Code | Short Description | Type | Code Fix |
| ------ | ---------------------------------------------------------------------------------------- | ----- | --------- |
| CoreCompat1002 | The underlined API is not portable to .Net Core 2.2 runtime because it is obsolete. | Error | Unavailable |
| CoreCompat1002 | The underlined API is not portable to .Net Core 2.2 runtime because the API is obsolete. | Error | Unavailable |

## Diagnostic Description

The underlined API is not portable to .Net Core 2.2 runtime because it is obsolete. Call to this API will throw PlatformNotSupportedException. You need to change your code to eliminate its usage.
The underlined API is not portable to .Net Core 2.2 runtime because the API is obsolete. A call to this API will throw `PlatformNotSupportedException`. You need to change your code to eliminate its usage.
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

<PropertyGroup>
<Company>Acumatica</Company>
<Copyright>Copyright © 2023 Acumatica Ltd.</Copyright>
<Version>1.0.0.0</Version>
<Copyright>Copyright © 2025 Acumatica, Inc.</Copyright>
<Version>1.1.0.0</Version>
</PropertyGroup>

<PropertyGroup>
<PackageId>CoreCompatibilyzer</PackageId>
<PackageVersion>1.0.0.0</PackageVersion>
<PackageVersion>1.1.0.0</PackageVersion>
<Authors>Acumatica</Authors>
<PackageLicenseUrl>https://github.com/Acumatica/CoreCompatibilyzer/blob/main/LICENSE.txt</PackageLicenseUrl>
<!--<PackageProjectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</PackageProjectUrl>-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<AssemblyTitle>CoreCompatibilyzer.Runner.NetFramework</AssemblyTitle>
<AssemblyName>CoreCompatibilyzer.Runner.NetFramework</AssemblyName>
<Company>Acumatica</Company>
<Copyright>Copyright © 2023 Acumatica Ltd.</Copyright>
<Version>1.0.0.0</Version>
<Copyright>Copyright © 2025 Acumatica, Inc.</Copyright>
<Version>1.1.0.0</Version>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,30 +63,24 @@ private GroupingMode ReadGroupingMode(string? rawGroupingLocation)
return GroupingMode.None;

string rawGroupingLocationUppered = rawGroupingLocation.ToUpperInvariant();
bool groupByNamespaces = rawGroupingLocationUppered.Contains('N');
bool groupByTypes = rawGroupingLocationUppered.Contains('T');
bool groupByApis = rawGroupingLocationUppered.Contains('A');

bool groupByFiles = rawGroupingLocationUppered.Contains('F');
bool groupByFiles = rawGroupingLocationUppered.Contains('F');
bool groupByNamespaces = rawGroupingLocationUppered.Contains('N');
bool groupByTypes = rawGroupingLocationUppered.Contains('T');
bool groupByApis = rawGroupingLocationUppered.Contains('A');

GroupingMode grouping = groupByFiles
? GroupingMode.Files
: GroupingMode.None;

GroupingMode grouping;
if (groupByNamespaces)
grouping |= GroupingMode.Namespaces;

if (groupByFiles)
grouping = GroupingMode.Files;
if (groupByTypes)
grouping |= GroupingMode.Types;

else
{
grouping = groupByNamespaces
? GroupingMode.Namespaces
: GroupingMode.None;

if (groupByTypes)
grouping |= GroupingMode.Types;

if (groupByApis)
grouping |= GroupingMode.Apis;
}
if (groupByApis)
grouping |= GroupingMode.Apis;

return grouping;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,24 +87,37 @@ internal class CommandLineOptions
$"This flag does not affect the report when the --{CommandLineArgNames.IncludeApiUsages} is specified.")]
public bool ShowMembersOfUsedType { get; }

// todo: not only files
/// <summary>
/// The report grouping. By default, there is no grouping. You can make grouping by reported API or by source.
/// The grouping of reported API can be by namespaces, types or any combination of them:<br/>
/// The report grouping. By default, there is no grouping. You can make grouping by source file paths, namespaces, types, APIs, or by any combination of them:<br/>
/// - Add "<c>f</c>" or "<c>F</c>" to group results by source file.<br/>
/// - Add "<c>n</c>" or "<c>N</c>" to group results by namespaces,<br/>
/// - Add "<c>t</c>" or "<c>T</c>" to group results by types,<br/>
/// - Add both to group results by both types and namespaces.
/// The grouping of source can be by files:
/// - Add "<c>f</c>" or "<c>F</c>" to group results by source file.<br/>
/// - Add "<c>a</c>" or "<c>A</c>" to group API usages by APIs.<br/><br/>
/// Any combination of these characters will specify a report grouping. For example, specify both "<c>ftn</c>" to group results by files, types and namespaces.<br/>
/// </summary>
/// <remarks>
/// Reports grouping works like this:<br/>
/// - First, reports are grouped by filepaths, if "<c>f</c>" or "<c>F</c>" is specified in the grouping.<br/>
/// - Second, reports are grouped by namespaces, if "<c>n</c>" or "<c>N</c>" is specified in the grouping.<br/>
/// - Third, reports are grouped by types, if "<c>t</c>" or "<c>T</c>" is specified in the grouping.<br/>
/// - Fourth, reports are grouped by APIs, if "<c>a</c>" or "<c>A</c>" is specified in the grouping.<br/>
/// </remarks>
[Option(shortName: CommandLineArgNames.ReportGroupingShort, longName: CommandLineArgNames.ReportGroupingLong,
HelpText = "The report grouping. By default, there is no grouping. You can make grouping by reported API or by source.\n" +
"The grouping of reported API can be by by namespaces, types, APIs or any combination of them:\n" +
"- Add \"n\" or \"N\" to group API usages by namespaces,\n" +
"- Add \"t\" or \"T\" to group API usages by types,\n" +
"- Add \"a\" or \"A\" to group API usages by APIs.\n" +
"The grouping of source can be by files:\n" +
"- Add \"f\" or \"F\" to group results by source file.")]
HelpText = """
The report grouping. By default, there is no grouping. You can make grouping by source file paths, namespaces, types, APIs, or by any combination of them:
- Add "f" or "F" to group results by source file.
- Add "n" or "N" to group results by namespaces,
- Add "t" or "T" to group results by types,
- Add "a" or "A" to group API usages by APIs.
Any combination of these characters will specify a report grouping. For example, specify both "ftn" to group results by files, types and namespaces.
Reports grouping works like this:
- First, reports are grouped by filepaths, if "f" or "F" is specified in the grouping.
- Second, reports are grouped by namespaces, if "n" or "N" is specified in the grouping.
- Third, reports are grouped by types, if "t" or "T" is specified in the grouping.
- Fourth, reports are grouped by APIs, if "a" or "A" is specified in the grouping.
""")]
public string? ReportGrouping { get; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ internal class DiagnosticsWithBannedApis : IReadOnlyList<(Diagnostic Diagnostic,

public HashSet<string> UsedBannedTypes { get; } = new();

public DiagnosticsWithBannedApis()
{
_diagnosticsWithApis = new();
UnrecognizedDiagnostics = new List<Diagnostic>();
UsedDistinctApis = new List<Api>();
}

public DiagnosticsWithBannedApis(IEnumerable<Diagnostic> diagnostics, AppAnalysisContext analysisContext)
{
analysisContext.ThrowIfNull(nameof(analysisContext));
Expand All @@ -55,16 +48,39 @@ public DiagnosticsWithBannedApis(IEnumerable<Diagnostic> diagnostics, AppAnalysi
? diagnosticsWithSuccessfullyReadApis.ToList(estimatedCapacity.Value)
: diagnosticsWithSuccessfullyReadApis.ToList();

foreach (var (_, bannedApi) in _diagnosticsWithApis)
FillBannedNamespacesAndTypes();

DistinctApisCalculator = new UsedDistinctApisCalculator(analysisContext, UsedNamespaces, UsedBannedTypes);
UsedDistinctApis = DistinctApisCalculator.GetAllUsedApis(_diagnosticsWithApis).ToList();
}

internal DiagnosticsWithBannedApis(IEnumerable<(Diagnostic Diagnostic, Api? BannedApi)> diagnosticsWithApis, AppAnalysisContext analysisContext)
{
analysisContext.ThrowIfNull(nameof(analysisContext));
diagnosticsWithApis.ThrowIfNull(nameof(diagnosticsWithApis));

int? estimatedCapacity = (diagnosticsWithApis as IReadOnlyCollection<(Diagnostic Diagnostic, Api BannedApi)>)?.Count;
IEnumerable<(Diagnostic Diagnostic, Api BannedApi)> diagnosticsWithSuccessfullyReadApisQuery = diagnosticsWithApis.Where(d => d.BannedApi != null)!;

_diagnosticsWithApis = estimatedCapacity.HasValue
? diagnosticsWithSuccessfullyReadApisQuery.ToList(estimatedCapacity.Value)
: diagnosticsWithSuccessfullyReadApisQuery.ToList();

if (_diagnosticsWithApis.Count == estimatedCapacity)
UnrecognizedDiagnostics = Array.Empty<Diagnostic>();
else
{
if (bannedApi.Kind == ApiKind.Type)
UsedBannedTypes.Add(bannedApi.FullTypeName);
else if (bannedApi.Kind == ApiKind.Namespace)
UsedNamespaces.Add(bannedApi.Namespace);
var unrecognizedDiagnosticsQuery = diagnosticsWithApis.Where(d => d.BannedApi == null)
.Select(d => d.Diagnostic);
UnrecognizedDiagnostics = estimatedCapacity.HasValue
? unrecognizedDiagnosticsQuery.ToList(estimatedCapacity.Value - _diagnosticsWithApis.Count)
: unrecognizedDiagnosticsQuery.ToList();
}

FillBannedNamespacesAndTypes();

DistinctApisCalculator = new UsedDistinctApisCalculator(analysisContext, UsedNamespaces, UsedBannedTypes);
UsedDistinctApis = DistinctApisCalculator.GetAllUsedApis(_diagnosticsWithApis).ToList();
UsedDistinctApis = DistinctApisCalculator.GetAllUsedApis(_diagnosticsWithApis).ToList();
}

private Api? GetBannedApiFromDiagnostic(Diagnostic diagnostic)
Expand All @@ -86,6 +102,17 @@ public DiagnosticsWithBannedApis(IEnumerable<Diagnostic> diagnostics, AppAnalysi
}
}

private void FillBannedNamespacesAndTypes()
{
foreach (var (_, bannedApi) in _diagnosticsWithApis)
{
if (bannedApi.Kind == ApiKind.Type)
UsedBannedTypes.Add(bannedApi.FullTypeName);
else if (bannedApi.Kind == ApiKind.Namespace)
UsedNamespaces.Add(bannedApi.Namespace);
}
}

public List<(Diagnostic Diagnostic, Api BannedApi)>.Enumerator GetEnumerator() =>
_diagnosticsWithApis.GetEnumerator();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,43 @@

namespace CoreCompatibilyzer.Runner.Output.Data
{
/// <summary>
/// Report grouping modes.
/// </summary>
[Flags]
internal enum GroupingMode
{
/// <summary>
/// No grouping is specified for the report.
/// </summary>
None = 0b0000,

/// <summary>
/// Group API calls by namespaces.
/// </summary>
Namespaces = ByUsedApi | 0xb0001,

/// <summary>
/// Group API calls by types.
/// </summary>
Types = ByUsedApi | 0xb0010,

/// <summary>
/// Group API calls by API.
/// </summary>
Apis = ByUsedApi | 0xb0100,

ByUsedApi = 0b1000,

// todo: xml

Files = BySource | 0b0001_0000,

BySource = 0b1000_0000,

}

internal static class GroupingModeExtensions
{
public static bool HasGrouping(this GroupingMode groupingMode, GroupingMode groupingToCheck) =>
(groupingMode & groupingToCheck) == groupingToCheck;
}
/// <summary>
/// Report grouping modes.
/// </summary>
[Flags]
internal enum GroupingMode
{
/// <summary>
/// No grouping is specified for the report.
/// </summary>
None = 0b0000,

/// <summary>
/// Group API calls by namespaces.
/// </summary>
Namespaces = 0b0001,

/// <summary>
/// Group API calls by types.
/// </summary>
Types = 0b0010,

/// <summary>
/// Group API calls by API.
/// </summary>
Apis = 0b0100,

/// <summary>
/// Group API calls by source file.
/// </summary>
Files = 0b1000

// TODO: add other grouping modes, not only by files
}

internal static class GroupingModeExtensions
{
public static bool HasGrouping(this GroupingMode groupingMode, GroupingMode groupingToCheck) =>
(groupingMode & groupingToCheck) == groupingToCheck;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace CoreCompatibilyzer.Runner.Output.Data
internal enum TitleKind
{
NotSpecified,
File,
Namespace,
Type,
Members,
Expand Down
Loading

0 comments on commit d0cec6a

Please sign in to comment.