Skip to content

Commit 15d6af8

Browse files
committed
Add project files.
1 parent 852a8d8 commit 15d6af8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+3183
-0
lines changed

.editorconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
6+
[*.{sln,*proj,dotsettings}]
7+
charset = utf-8-bom
8+
9+
[*.cs]
10+
indent_size = 4
11+
indent_style = space
12+
insert_final_newline = true
13+
trim_trailing_whitespace = true

.github/workflows/CI.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request: ~
7+
workflow_dispatch: ~
8+
9+
jobs:
10+
build:
11+
12+
runs-on: windows-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Build and test
18+
run: dotnet test -c Release /bl:artifacts\Logs\test.binlog
19+
20+
- name: Upload logs artifact
21+
if: always()
22+
uses: actions/upload-artifact@v4
23+
with:
24+
name: Logs
25+
path: artifacts/Logs

ApiUsageAnalyzer.sln

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.14.36310.24
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiUsageAnalyzer", "src\ApiUsageAnalyzer\ApiUsageAnalyzer.csproj", "{0CF6BB7A-B230-43D6-A900-690920A1B49A}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiUsageAnalyzer.Tests", "src\ApiUsageAnalyzer.Tests\ApiUsageAnalyzer.Tests.csproj", "{5BEEF8E7-DB3D-4F80-BA05-CA606880A0A5}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
11+
ProjectSection(SolutionItems) = preProject
12+
.editorconfig = .editorconfig
13+
Directory.Build.props = Directory.Build.props
14+
global.json = global.json
15+
EndProjectSection
16+
EndProject
17+
Global
18+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
19+
Debug|Any CPU = Debug|Any CPU
20+
Release|Any CPU = Release|Any CPU
21+
EndGlobalSection
22+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
23+
{0CF6BB7A-B230-43D6-A900-690920A1B49A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24+
{0CF6BB7A-B230-43D6-A900-690920A1B49A}.Debug|Any CPU.Build.0 = Debug|Any CPU
25+
{0CF6BB7A-B230-43D6-A900-690920A1B49A}.Release|Any CPU.ActiveCfg = Release|Any CPU
26+
{0CF6BB7A-B230-43D6-A900-690920A1B49A}.Release|Any CPU.Build.0 = Release|Any CPU
27+
{5BEEF8E7-DB3D-4F80-BA05-CA606880A0A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28+
{5BEEF8E7-DB3D-4F80-BA05-CA606880A0A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
29+
{5BEEF8E7-DB3D-4F80-BA05-CA606880A0A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
30+
{5BEEF8E7-DB3D-4F80-BA05-CA606880A0A5}.Release|Any CPU.Build.0 = Release|Any CPU
31+
EndGlobalSection
32+
GlobalSection(SolutionProperties) = preSolution
33+
HideSolutionNode = FALSE
34+
EndGlobalSection
35+
GlobalSection(ExtensibilityGlobals) = postSolution
36+
SolutionGuid = {C55EEC01-DC3B-4CCD-B047-EB2EAECE9F8B}
37+
EndGlobalSection
38+
EndGlobal

Directory.Build.props

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project>
2+
3+
<PropertyGroup>
4+
<LangVersion>preview</LangVersion>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<MSBuildTreatWarningsAsErrors Condition="'$(Configuration)' == 'Release'">true</MSBuildTreatWarningsAsErrors>
8+
</PropertyGroup>
9+
10+
</Project>

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright © 2025 Technology Solutions Associates, LLC
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Readme.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# API Usage Analyzer
2+
3+
Produces a report of API usage across code repositories, with version and target framework statistics, listing unused/removed APIs, and links to each usage. The output file format is [KDL](https://kdl.dev) for ease of human browsing and easily clickable links when viewing. The file is rewritten in real time as the analysis proceeds.
4+
5+
See: [Prerelease status](#prerelease-status), [How to use](#how-to-use), [Command-line arguments](#command-line-arguments)
6+
7+
Sample output:
8+
9+
```kdl
10+
assembly SomeLibrary {
11+
current-api-source {
12+
repo "https://somewhere/SomeLibrary"
13+
branch main
14+
commit b7e651d52c9b19e6d6e0ca6b4389bc06ac3e7fde
15+
}
16+
stats {
17+
unused-api-count 4_000
18+
removed-api-count 66
19+
used-api-count 749
20+
version "1.0.2.0" {
21+
repo "https://somewhere/ConsumingRepo1" { tfm net462 }
22+
}
23+
version "2.0.0.0" {
24+
repo "https://somewhere/ConsumingRepo2" { tfm net8.0 }
25+
repo "https://somewhere/ConsumingRepo3" { tfm net48 }
26+
}
27+
version "3.0.0.0" {
28+
repo "https://somewhere/ConsumingRepo2" { tfm net48; tfm net8.0-windows; tfm net9.0-windows }
29+
}
30+
}
31+
32+
unused-apis {
33+
api "SomeLibrary.AverageAccumulator.AverageAccumulator(double initialTotal, double initialCount)" url="https://somewhere/SomeLibrary?version=GBmain&path=src/SomeLibrary/AverageAccumulator.cs&line=13&lineEnd=17&lineStartColumn=9&lineEndColumn=10" { tfm net48; tfm net8.0 }
34+
api "SomeLibrary.AverageAccumulator.Count.get" url="https://somewhere/SomeLibrary?version=GBmain&path=src/SomeLibrary/AverageAccumulator.cs&line=51&lineEnd=51&lineStartColumn=29&lineEndColumn=37" { tfm net48; tfm net8.0 }
35+
api "SomeLibrary.AverageAccumulator.implicit operator double?(SomeLibrary.AverageAccumulator accumulator)" url="https://somewhere/SomeLibrary?version=GBmain&path=src/SomeLibrary/AverageAccumulator.cs&line=53&lineEnd=56&lineStartColumn=9&lineEndColumn=10" { tfm net48; tfm net8.0 }
36+
}
37+
38+
removed-apis {
39+
api "SomeLibrary.IAsyncDisposable" {
40+
version "2.0.0.0" {
41+
repo "https://somewhere/ConsumingRepo1" {
42+
reference "https://somewhere/ConsumingRepo1?version=GBmain&path=src/ConsumingProject1/AsyncDisposable.cs&line=12&lineEnd=12&lineStartColumn=5&lineEndColumn=112" { tfm net48 }
43+
reference "https://somewhere/ConsumingRepo1?version=GBmain&path=src/ConsumingProject1/AsyncDisposable.cs&line=6&lineEnd=6&lineStartColumn=39&lineEndColumn=64" { tfm net48 }
44+
// ...
45+
}
46+
repo "https://somewhere/ConsumingRepo2" {
47+
reference "https://somewhere/ConsumingRepo2?version=GBmain&path=src/ConsumingProject2/BatchReporting/Domain/MailingClaim.cs&line=5&lineEnd=5&lineStartColumn=40&lineEndColumn=56" { tfm net48 }
48+
reference "https://somewhere/ConsumingRepo2?version=GBmain&path=src/ConsumingProject2/BatchReporting/Domain/MailingClaimLifetime.cs&line=5&lineEnd=5&lineStartColumn=50&lineEndColumn=66" { tfm net48 }
49+
// ...
50+
}
51+
}
52+
}
53+
// ...
54+
}
55+
56+
api "SomeLibrary.AverageAccumulator" url="https://somewhere/SomeLibrary?version=GBmain&path=src/SomeLibrary/AverageAccumulator.cs&line=7&lineEnd=57&lineStartColumn=5&lineEndColumn=6" {
57+
version "2.0.0.0" {
58+
repo "https://somewhere/ConsumingRepo2" {
59+
reference "https://somewhere/ConsumingRepo2?version=GBmain&path=src/ConsumingProject2/PerfDataEntry/ReportHandler.cs&line=447&lineEnd=447&lineStartColumn=90&lineEndColumn=107" { tfm net48 }
60+
reference "https://somewhere/ConsumingRepo2?version=GBmain&path=src/ConsumingProject2/PerfDataEntry/ReportHandler.cs&line=451&lineEnd=451&lineStartColumn=90&lineEndColumn=107" { tfm net48 }
61+
// ...
62+
}
63+
}
64+
}
65+
api "SomeLibrary.AverageAccumulator.Add(double value, double weight)" url="https://somewhere/SomeLibrary?version=GBmain&path=src/SomeLibrary/AverageAccumulator.cs&line=35&lineEnd=47&lineStartColumn=9&lineEndColumn=10" {
66+
version "2.0.0.0" {
67+
repo "https://somewhere/ConsumingRepo2" {
68+
reference "https://somewhere/ConsumingRepo2?version=GBmain&path=src/ConsumingProject2/PerfDataEntry/DataEntrySummaryCalculator.cs&line=135&lineEnd=135&lineStartColumn=40&lineEndColumn=43" { tfm net48 }
69+
reference "https://somewhere/ConsumingRepo2?version=GBmain&path=src/ConsumingProject2/PerfDataEntry/DataEntrySummaryCalculator.cs&line=92&lineEnd=92&lineStartColumn=56&lineEndColumn=59" { tfm net48 }
70+
// ...
71+
}
72+
}
73+
}
74+
// ...
75+
}
76+
```
77+
78+
## Prerelease status
79+
80+
You may hit a NotImplementedException if there's syntax that the tool hasn't seen before. If you don't mind getting a little grease on your hands, this will pose no obstacle to you, and we would be delighted if you're inspired to share back as a pull request. Issues are welcome too.
81+
82+
## How to use
83+
84+
1. Clone this repository (example: `git clone https://github.com/Techsola/api-usage-analyzer`)
85+
2. Navigate to the `src\ApiUsageAnalyzer` subdirectory (example: `cd api-usage-analyzer\src\ApiUsageAnalyzer`)
86+
3. Type `dotnet run` followed by the arguments below. `--open-in-vs-code` is recommended.
87+
4. Wait a bit as repositories are cloned, restored, and analyzed.
88+
5. Peruse the `<assembly-name>.kdl` file in the working directory.
89+
90+
## Command-line arguments
91+
92+
```
93+
USAGE:
94+
ApiUsageAnalyzer <assembly-name> [OPTIONS]
95+
96+
ARGUMENTS:
97+
<assembly-name> The name of the assembly defining the APIs whose usage will be reported
98+
99+
OPTIONS:
100+
-h, --help Prints help information
101+
--azdo <URLS> The Azure DevOps project collection URLs from which
102+
repositories are discovered. Repeat --azdo to specify
103+
more than one URL. Currently, only Git repositories and
104+
only the Azure DevOps platform are supported
105+
--defining-repo-name <NAME> If specified, only repositories with this name will be
106+
searched for the project that defines the API assembly
107+
--defining-repo-url <URL> The remote URL of an repository containing the project
108+
that defines the API assembly. The repository's current
109+
HEAD will be used to define the set of available APIs in
110+
the API assembly which will be used for the
111+
unused/removed API comparison
112+
--repo-package-id-filter <REGEX> If specified, repositories will be skipped unless they
113+
contain a project with a direct reference to a NuGet
114+
package with an ID that matches this regular expression
115+
--open-in-vs-code If specified, the output file will automatically open in
116+
VS Code. VS Code can monitor the live updates to the
117+
results
118+
```

global.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"sdk": {
3+
"version": "9.0.300",
4+
"allowPrerelease": true,
5+
"rollForward": "major"
6+
}
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0-windows</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Shouldly" Version="4.3.0" />
9+
<PackageReference Include="Spectre.Console.Testing" Version="0.50.0" />
10+
<PackageReference Include="TUnit" Version="0.25.21" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\ApiUsageAnalyzer\ApiUsageAnalyzer.csproj" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using ApiUsageAnalyzer.Utils;
2+
using Shouldly;
3+
4+
namespace ApiUsageAnalyzer.Tests;
5+
6+
public class AsyncAutoResetEventTests
7+
{
8+
[Test]
9+
public void The_event_is_initially_not_signaled()
10+
{
11+
var ev = new AsyncAutoResetEvent();
12+
ev.WaitAsync().IsCompleted.ShouldBeFalse();
13+
}
14+
15+
[Test]
16+
public void The_event_can_be_signaled_before_waiting_starts()
17+
{
18+
var ev = new AsyncAutoResetEvent();
19+
ev.Set();
20+
ev.WaitAsync().IsCompleted.ShouldBeTrue();
21+
}
22+
23+
[Test]
24+
public void The_event_can_be_signaled_after_waiting_starts()
25+
{
26+
var ev = new AsyncAutoResetEvent();
27+
var waitTask = ev.WaitAsync();
28+
ev.Set();
29+
waitTask.IsCompleted.ShouldBeTrue();
30+
}
31+
32+
[Test]
33+
public void The_event_automatically_resets_when_signaled_after_the_first_wait_starts()
34+
{
35+
var ev = new AsyncAutoResetEvent();
36+
ev.WaitAsync();
37+
ev.Set();
38+
ev.WaitAsync().IsCompleted.ShouldBeFalse();
39+
}
40+
41+
[Test]
42+
public void The_event_automatically_resets_when_signaled_before_the_first_wait_starts()
43+
{
44+
var ev = new AsyncAutoResetEvent();
45+
ev.Set();
46+
ev.WaitAsync();
47+
ev.WaitAsync().IsCompleted.ShouldBeFalse();
48+
}
49+
50+
[Test]
51+
public void Setting_multiple_times_does_not_signal_more_than_one_wait()
52+
{
53+
var ev = new AsyncAutoResetEvent();
54+
ev.Set();
55+
ev.Set();
56+
ev.WaitAsync();
57+
ev.WaitAsync().IsCompleted.ShouldBeFalse();
58+
}
59+
60+
[Test]
61+
public void Multiple_waits_for_the_same_signal_are_the_same_task()
62+
{
63+
var ev = new AsyncAutoResetEvent();
64+
ev.WaitAsync().ShouldBeSameAs(ev.WaitAsync());
65+
ev.Set();
66+
ev.WaitAsync().ShouldBeSameAs(ev.WaitAsync());
67+
}
68+
}

0 commit comments

Comments
 (0)