├── .editorconfig
├── .git-blame-ignore-revs
├── .gitattributes
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── LICENSE
├── README.md
├── build.cake
├── dotnet-tools.json
├── global.json
└── src
├── Snitch.Tests.Fixtures
├── Bar
│ ├── Bar.csproj
│ └── Class1.cs
├── Baz
│ ├── Baz.csproj
│ └── Class1.cs
├── FSharp
│ └── FSharp.fsproj
├── Foo
│ ├── Class1.cs
│ └── Foo.csproj
├── Quuux
│ ├── Class1.cs
│ └── Quuux.csproj
├── Quux
│ ├── Class1.cs
│ └── Quux.csproj
├── Qux
│ ├── Class1.cs
│ └── Qux.csproj
├── Snitch.Tests.Fixtures.sln
├── Thud
│ ├── Class1.cs
│ └── Thud.csproj
├── Thuuud
│ ├── Class1.cs
│ └── Thuuud.csproj
└── Zap
│ ├── Class1.cs
│ └── Zap.csproj
├── Snitch.Tests
├── Expectations
│ ├── Baz.Default.verified.txt
│ ├── Baz.Exclude_Autofac.verified.txt
│ ├── Baz.Skip_Bar.verified.txt
│ ├── Baz.Skip_Bar_NoPreRelease.verified.txt
│ ├── Baz.netstandard2.0.verified.txt
│ ├── Baz.netstandard2.0_Strict.verified.txt
│ ├── Baz.netstandard2.Strict.NoPreRelease.verified.txt
│ ├── FSharp.Default.verified.txt
│ ├── Solution.Default.verified.txt
│ ├── Thud.netstandard2.Strict.NoPreRelease.verified.txt
│ ├── Thuuud.netstandard2.NoPreRelease.verified.txt
│ └── Thuuud.netstandard2.Strict.NoPreRelease.verified.txt
├── ProgramTests.cs
├── Snitch.Tests.csproj
└── VerifyConfiguration.cs
├── Snitch.sln
├── Snitch
├── Analysis
│ ├── Models
│ │ ├── Package.cs
│ │ ├── PackageToRemove.cs
│ │ ├── Project.cs
│ │ ├── ProjectComparer.cs
│ │ └── ProjectPackage.cs
│ ├── ProjectAnalyzer.cs
│ ├── ProjectAnalyzerResult.cs
│ ├── ProjectBuildResult.cs
│ ├── ProjectBuilder.cs
│ ├── ProjectReporter.cs
│ └── Utilities
│ │ └── PathUtility.cs
├── Commands
│ ├── AnalyzeCommand.cs
│ └── VersionCommand.cs
├── Program.cs
├── Snitch.csproj
└── Utilities
│ ├── Extensions
│ ├── AnalyzerResultExtensions.cs
│ ├── EnumerableExtensions.cs
│ └── PackageExtensions.cs
│ ├── TypeRegistrar.cs
│ └── TypeResolver.cs
├── icon.png
└── mdsnippets.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = CRLF
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = false
9 | trim_trailing_whitespace = true
10 |
11 | [*.sln]
12 | indent_style = tab
13 |
14 | [*.{csproj,vbproj,vcxproj,vcxproj.filters}]
15 | indent_size = 2
16 |
17 | [*.{xml,config,props,targets,nuspec,ruleset}]
18 | indent_size = 2
19 |
20 | [*.json]
21 | indent_size = 2
22 |
23 | [*.yaml]
24 | indent_size = 2
25 |
26 | [*.md]
27 | trim_trailing_whitespace = false
28 |
29 | [*.sh]
30 | end_of_line = lf
31 | [*.cs]
32 |
33 | # SA1200: Using directives should be placed correctly
34 | dotnet_diagnostic.SA1200.severity = none
35 |
36 | # SA1600: Elements should be documented
37 | dotnet_diagnostic.SA1600.severity = none
38 |
39 | # SA1633: File should have header
40 | dotnet_diagnostic.SA1633.severity = none
41 |
42 | # SA1201: Elements should appear in the correct order
43 | dotnet_diagnostic.SA1201.severity = none
44 |
45 | # SA1101: Prefix local calls with this
46 | dotnet_diagnostic.SA1101.severity = none
47 |
48 | # SA1516: Elements should be separated by blank line
49 | dotnet_diagnostic.SA1516.severity = none
50 |
51 | # SA1309: Field names should not begin with underscore
52 | dotnet_diagnostic.SA1309.severity = none
53 |
54 | # CA1303: Do not pass literals as localized parameters
55 | dotnet_diagnostic.CA1303.severity = none
56 |
57 | # CA1034: Nested types should not be visible
58 | dotnet_diagnostic.CA1034.severity = none
59 |
60 | # CA1819: Properties should not return arrays
61 | dotnet_diagnostic.CA1819.severity = none
62 |
63 | # CA1062: Validate arguments of public methods
64 | dotnet_diagnostic.CA1062.severity = silent
65 |
66 | # CA2007: Consider calling ConfigureAwait on the awaited task
67 | dotnet_diagnostic.CA2007.severity = none
68 |
69 | # SA1204: Static elements should appear before instance elements
70 | dotnet_diagnostic.SA1204.severity = none
71 |
72 | # SA1649: File name should match first type name
73 | dotnet_diagnostic.SA1649.severity = none
74 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # fix commited files from crlf to lf
2 | 729b1bd03e0fd3ef8cb99734a0a8bd5351f2f7a2
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf working-tree-encoding=UTF-8
2 | *.png binary
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
2 | name: Continuous Integration
3 | on: pull_request
4 |
5 | env:
6 | # Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
7 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
8 | # Disable sending usage data to Microsoft
9 | DOTNET_CLI_TELEMETRY_OPTOUT: true
10 |
11 | jobs:
12 | build:
13 | name: Build
14 | if: "!contains(github.event.head_commit.message, 'skip-ci')"
15 | runs-on: ubuntu-latest}
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0
21 |
22 | - name: 'Get Git tags'
23 | run: git fetch --tags
24 | shell: bash
25 |
26 | - name: Setup .NET SDK
27 | uses: actions/setup-dotnet@v3
28 |
29 | - name: Build
30 | shell: bash
31 | run: |
32 | dotnet tool restore
33 | dotnet cake
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Misc folders
2 | [Bb]in/
3 | [Oo]bj/
4 | [Tt]emp/
5 | [Pp]ackages/
6 | /.artifacts/
7 | /[Tt]ools/
8 |
9 | # Cakeup
10 | cakeup-x86_64-latest.exe
11 |
12 | # .NET Core CLI
13 | /.dotnet/
14 | /.packages/
15 | dotnet-install.sh*
16 | *.lock.json
17 |
18 | # Visual Studio
19 | .vs/
20 | .vscode/
21 | launchSettings.json
22 | *.sln.ide/
23 |
24 | # Rider
25 | src/.idea/**/workspace.xml
26 | src/.idea/**/tasks.xml
27 | src/.idea/dictionaries
28 | src/.idea/**/dataSources/
29 | src/.idea/**/dataSources.ids
30 | src/.idea/**/dataSources.xml
31 | src/.idea/**/dataSources.local.xml
32 | src/.idea/**/sqlDataSources.xml
33 | src/.idea/**/dynamic.xml
34 | src/.idea/**/uiDesigner.xml
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 | *.userprefs
44 | *.GhostDoc.xml
45 | *StyleCop.Cache
46 |
47 | # Build results
48 | [Dd]ebug/
49 | [Rr]elease/
50 | x64/
51 | *_i.c
52 | *_p.c
53 | *.ilk
54 | *.meta
55 | *.obj
56 | *.pch
57 | *.pdb
58 | *.pgc
59 | *.pgd
60 | *.rsp
61 | *.sbr
62 | *.tlb
63 | *.tli
64 | *.tlh
65 | *.tmp
66 | *.log
67 | *.vspscc
68 | *.vssscc
69 | .builds
70 |
71 | # Visual Studio profiler
72 | *.psess
73 | *.vsp
74 | *.vspx
75 |
76 | # ReSharper is a .NET coding add-in
77 | _ReSharper*
78 |
79 | # NCrunch
80 | *.ncrunch*
81 | .*crunch*.local.xml
82 | _NCrunch_*
83 |
84 | # NuGet Packages Directory
85 | packages
86 |
87 | # Windows
88 | Thumbs.db
89 |
90 | # Rider
91 | .idea/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 Spectre Systems AB
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included
11 | in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
15 | A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Snitch
2 |
3 | [](https://www.nuget.org/packages/Snitch/)
4 |
5 | A tool that help you find transitive package references that can be removed.
6 |
7 | ## Example
8 |
9 | ```
10 | > snitch --tfm net462
11 | ```
12 |
13 | Results in:
14 |
15 |
16 |
17 | ```txt
18 | Analyzing...
19 | Analyzing Snitch.Tests.Fixtures.sln
20 | Analyzing Foo...
21 | Analyzing Bar...
22 | Analyzing Baz...
23 | Analyzing Qux...
24 | Analyzing Zap...
25 | Analyzing Quux...
26 | Analyzing Quuux...
27 | Analyzing Thud...
28 | Analyzing Thuuud...
29 |
30 | ╭─────────────────────────────────────────────────────────────────╮
31 | │ Packages that can be removed from Bar: │
32 | │ ┌──────────────────────┬──────────────────────────────────────┐ │
33 | │ │ Package │ Referenced by │ │
34 | │ ├──────────────────────┼──────────────────────────────────────┤ │
35 | │ │ Autofac │ Foo │ │
36 | │ └──────────────────────┴──────────────────────────────────────┘ │
37 | │ │
38 | │ Packages that can be removed from Baz: │
39 | │ ┌──────────────────────┬──────────────────────────────────────┐ │
40 | │ │ Package │ Referenced by │ │
41 | │ ├──────────────────────┼──────────────────────────────────────┤ │
42 | │ │ Autofac │ Foo │ │
43 | │ └──────────────────────┴──────────────────────────────────────┘ │
44 | │ │
45 | │ Packages that might be removed from Qux: │
46 | │ ┌───────────┬───────────┬─────────────────────────────────────┐ │
47 | │ │ Package │ Version │ Reason │ │
48 | │ ├───────────┼───────────┼─────────────────────────────────────┤ │
49 | │ │ Autofac │ 4.9.3 │ Downgraded from 4.9.4 in Foo │ │
50 | │ └───────────┴───────────┴─────────────────────────────────────┘ │
51 | │ │
52 | │ Packages that might be removed from Zap: │
53 | │ ┌──────────────────┬──────────┬───────────────────────────────┐ │
54 | │ │ Package │ Version │ Reason │ │
55 | │ ├──────────────────┼──────────┼───────────────────────────────┤ │
56 | │ │ Newtonsoft.Json │ 12.0.3 │ Updated from 12.0.1 in Foo │ │
57 | │ │ Autofac │ 4.9.3 │ Downgraded from 4.9.4 in Foo │ │
58 | │ └──────────────────┴──────────┴───────────────────────────────┘ │
59 | │ │
60 | │ Packages that might be removed from Thuuud: │
61 | │ ┌─────────────────┬──────────────┬────────────────────────────┐ │
62 | │ │ Package │ Version │ Reason │ │
63 | │ ├─────────────────┼──────────────┼────────────────────────────┤ │
64 | │ │ Newtonsoft.Json │ 13.0.2-beta2 │ Updated from 12.0.1 in Foo │ │
65 | │ └─────────────────┴──────────────┴────────────────────────────┘ │
66 | ╰─────────────────────────────────────────────────────────────────╯
67 | ```
68 | snippet source | anchor
69 |
70 |
71 | ## Installation
72 |
73 | ```
74 | > dotnet tool install -g snitch
75 | ```
76 |
77 | ## Usage
78 |
79 | _Examine a specific project or solution using the first built
80 | target framework._
81 |
82 | ```
83 | > snitch MyProject.csproj
84 | ```
85 |
86 | _Examine a specific project using a specific
87 | target framework moniker._
88 |
89 | ```
90 | > snitch MyProject.csproj --tfm net462
91 | ```
92 |
93 | _Examine a specific project using a specific target framework moniker
94 | and return exit code 0 only if there was no transitive package collisions.
95 | Useful for continuous integration._
96 |
97 | ```
98 | > snitch MyProject.csproj --tfm net462 --strict
99 | ```
100 |
101 | _Examine a specific project using a specific target framework moniker
102 | and make sure that the packages Foo and Bar are excluded from the result._
103 |
104 | ```
105 | > snitch MyProject.csproj --tfm net462 --exclude Foo --exclude Bar
106 | ```
107 |
108 | _Examine a specific project using a specific target framework moniker
109 | and exclude the project OtherProject from analysis._
110 |
111 | ```
112 | > snitch MyProject.csproj --tfm net462 --skip OtherProject
113 | ```
114 |
115 | _Examine a specific project or solution to make sure there are no pre-release package references._
116 |
117 | ```
118 | > snitch MyProject.csproj --no-prerelease
119 | ```
120 |
121 | ## Building Snitch from source
122 |
123 | ```
124 | > dotnet tool restore
125 | > dotnet cake
126 | ```
127 |
128 | ## Icon
129 |
130 | [Hollow](https://thenounproject.com/term/stitch/1571973/) designed by [Ben Davis](https://thenounproject.com/smashicons/) from [The Noun Project](https://thenounproject.com).
131 |
--------------------------------------------------------------------------------
/build.cake:
--------------------------------------------------------------------------------
1 | var semanticVersion = Argument("packageversion", "1.0.0");
2 | var version = semanticVersion.Split(new[] { '-' }).FirstOrDefault() ?? semanticVersion;
3 |
4 | Information("Version: {0}", semanticVersion);
5 | Information("Legacy version: {0}", version);
6 |
7 | Task("Clean")
8 | .Does(() =>
9 | {
10 | CleanDirectory("./.artifacts");
11 | });
12 |
13 | Task("Build")
14 | .IsDependentOn("Clean")
15 | .Does(() =>
16 | {
17 | DotNetBuild("./src/Snitch.sln", new DotNetBuildSettings
18 | {
19 | Configuration = "Release",
20 | MSBuildSettings = new DotNetMSBuildSettings()
21 | .TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
22 | .WithProperty("Version", version)
23 | .WithProperty("AssemblyVersion", version)
24 | .WithProperty("FileVersion", version)
25 | });
26 | });
27 |
28 | Task("Run-Tests")
29 | .IsDependentOn("Build")
30 | .Does(() =>
31 | {
32 | DotNetTest("./src/Snitch.Tests/Snitch.Tests.csproj", new DotNetTestSettings
33 | {
34 | Configuration = "Release"
35 | });
36 | });
37 |
38 | Task("Pack")
39 | .IsDependentOn("Run-Tests")
40 | .Does(() =>
41 | {
42 | DotNetPack("./src/Snitch.sln", new DotNetPackSettings
43 | {
44 | Configuration = "Release",
45 | NoRestore = true,
46 | NoBuild = true,
47 | OutputDirectory = "./.artifacts",
48 | MSBuildSettings = new DotNetMSBuildSettings()
49 | .WithProperty("PackageVersion", semanticVersion)
50 | });
51 | });
52 |
53 | RunTarget("Pack")
--------------------------------------------------------------------------------
/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "cake.tool": {
6 | "version": "3.2.0",
7 | "commands": [
8 | "dotnet-cake"
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "projects": [ "src" ],
3 | "sdk": {
4 | "version": "8.0.100",
5 | "rollForward": "latestPatch"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Bar/Bar.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Bar/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Bar
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Baz/Baz.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Baz/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Baz
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/FSharp/FSharp.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Foo/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Foo
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Foo/Foo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Quuux/Class1.cs:
--------------------------------------------------------------------------------
1 | namespace Quuux
2 | {
3 | public class Class1
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Quuux/Quuux.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Quux/Class1.cs:
--------------------------------------------------------------------------------
1 | namespace Quux
2 | {
3 | public class Class1
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Quux/Quux.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Qux/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Qux
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Qux/Qux.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Snitch.Tests.Fixtures.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29326.143
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foo", "Foo\Foo.csproj", "{27C335C5-0DF2-49DB-AD3B-6284309BB534}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bar", "Bar\Bar.csproj", "{D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Baz", "Baz\Baz.csproj", "{16675791-AE34-41EF-8060-7B1F635C8EE1}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Qux", "Qux\Qux.csproj", "{05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zap", "Zap\Zap.csproj", "{18C1243F-6B3A-45DA-8746-11946205EA35}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quux", "Quux\Quux.csproj", "{0B419579-20DD-48F4-92B8-C715D96A63A6}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quuux", "Quuux\Quuux.csproj", "{E82441AE-9A28-4472-B506-4078FA4DB70B}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thud", "Thud\Thud.csproj", "{22334552-7CE0-4E06-AC9C-974080E2FD99}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thuuud", "Thuuud\Thuuud.csproj", "{42E62FFD-F1CD-41D3-9DCB-93638D77F33B}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Debug|x64 = Debug|x64
28 | Debug|x86 = Debug|x86
29 | Release|Any CPU = Release|Any CPU
30 | Release|x64 = Release|x64
31 | Release|x86 = Release|x86
32 | EndGlobalSection
33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
34 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Debug|x64.ActiveCfg = Debug|Any CPU
37 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Debug|x64.Build.0 = Debug|Any CPU
38 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Debug|x86.ActiveCfg = Debug|Any CPU
39 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Debug|x86.Build.0 = Debug|Any CPU
40 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Release|x64.ActiveCfg = Release|Any CPU
43 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Release|x64.Build.0 = Release|Any CPU
44 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Release|x86.ActiveCfg = Release|Any CPU
45 | {27C335C5-0DF2-49DB-AD3B-6284309BB534}.Release|x86.Build.0 = Release|Any CPU
46 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Debug|x64.ActiveCfg = Debug|Any CPU
49 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Debug|x64.Build.0 = Debug|Any CPU
50 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Debug|x86.ActiveCfg = Debug|Any CPU
51 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Debug|x86.Build.0 = Debug|Any CPU
52 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Release|x64.ActiveCfg = Release|Any CPU
55 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Release|x64.Build.0 = Release|Any CPU
56 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Release|x86.ActiveCfg = Release|Any CPU
57 | {D8552EAB-5C3A-4F7D-82C6-8E882A1032E8}.Release|x86.Build.0 = Release|Any CPU
58 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Debug|x64.ActiveCfg = Debug|Any CPU
61 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Debug|x64.Build.0 = Debug|Any CPU
62 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Debug|x86.ActiveCfg = Debug|Any CPU
63 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Debug|x86.Build.0 = Debug|Any CPU
64 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
65 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Release|Any CPU.Build.0 = Release|Any CPU
66 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Release|x64.ActiveCfg = Release|Any CPU
67 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Release|x64.Build.0 = Release|Any CPU
68 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Release|x86.ActiveCfg = Release|Any CPU
69 | {16675791-AE34-41EF-8060-7B1F635C8EE1}.Release|x86.Build.0 = Release|Any CPU
70 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
71 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
72 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Debug|x64.ActiveCfg = Debug|Any CPU
73 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Debug|x64.Build.0 = Debug|Any CPU
74 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Debug|x86.ActiveCfg = Debug|Any CPU
75 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Debug|x86.Build.0 = Debug|Any CPU
76 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
77 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Release|Any CPU.Build.0 = Release|Any CPU
78 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Release|x64.ActiveCfg = Release|Any CPU
79 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Release|x64.Build.0 = Release|Any CPU
80 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Release|x86.ActiveCfg = Release|Any CPU
81 | {05E36179-ABC8-4EBC-B57F-0E6C1D7249F0}.Release|x86.Build.0 = Release|Any CPU
82 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
83 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
84 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Debug|x64.ActiveCfg = Debug|Any CPU
85 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Debug|x64.Build.0 = Debug|Any CPU
86 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Debug|x86.ActiveCfg = Debug|Any CPU
87 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Debug|x86.Build.0 = Debug|Any CPU
88 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
89 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Release|Any CPU.Build.0 = Release|Any CPU
90 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Release|x64.ActiveCfg = Release|Any CPU
91 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Release|x64.Build.0 = Release|Any CPU
92 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Release|x86.ActiveCfg = Release|Any CPU
93 | {18C1243F-6B3A-45DA-8746-11946205EA35}.Release|x86.Build.0 = Release|Any CPU
94 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
95 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
96 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Debug|x64.ActiveCfg = Debug|Any CPU
97 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Debug|x64.Build.0 = Debug|Any CPU
98 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Debug|x86.ActiveCfg = Debug|Any CPU
99 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Debug|x86.Build.0 = Debug|Any CPU
100 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
101 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Release|Any CPU.Build.0 = Release|Any CPU
102 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Release|x64.ActiveCfg = Release|Any CPU
103 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Release|x64.Build.0 = Release|Any CPU
104 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Release|x86.ActiveCfg = Release|Any CPU
105 | {0B419579-20DD-48F4-92B8-C715D96A63A6}.Release|x86.Build.0 = Release|Any CPU
106 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
107 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Debug|Any CPU.Build.0 = Debug|Any CPU
108 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Debug|x64.ActiveCfg = Debug|Any CPU
109 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Debug|x64.Build.0 = Debug|Any CPU
110 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Debug|x86.ActiveCfg = Debug|Any CPU
111 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Debug|x86.Build.0 = Debug|Any CPU
112 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Release|Any CPU.ActiveCfg = Release|Any CPU
113 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Release|Any CPU.Build.0 = Release|Any CPU
114 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Release|x64.ActiveCfg = Release|Any CPU
115 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Release|x64.Build.0 = Release|Any CPU
116 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Release|x86.ActiveCfg = Release|Any CPU
117 | {E82441AE-9A28-4472-B506-4078FA4DB70B}.Release|x86.Build.0 = Release|Any CPU
118 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
119 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Debug|Any CPU.Build.0 = Debug|Any CPU
120 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Debug|x64.ActiveCfg = Debug|Any CPU
121 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Debug|x64.Build.0 = Debug|Any CPU
122 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Debug|x86.ActiveCfg = Debug|Any CPU
123 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Debug|x86.Build.0 = Debug|Any CPU
124 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Release|Any CPU.ActiveCfg = Release|Any CPU
125 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Release|Any CPU.Build.0 = Release|Any CPU
126 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Release|x64.ActiveCfg = Release|Any CPU
127 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Release|x64.Build.0 = Release|Any CPU
128 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Release|x86.ActiveCfg = Release|Any CPU
129 | {22334552-7CE0-4E06-AC9C-974080E2FD99}.Release|x86.Build.0 = Release|Any CPU
130 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
131 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Debug|Any CPU.Build.0 = Debug|Any CPU
132 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Debug|x64.ActiveCfg = Debug|Any CPU
133 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Debug|x64.Build.0 = Debug|Any CPU
134 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Debug|x86.ActiveCfg = Debug|Any CPU
135 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Debug|x86.Build.0 = Debug|Any CPU
136 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Release|Any CPU.ActiveCfg = Release|Any CPU
137 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Release|Any CPU.Build.0 = Release|Any CPU
138 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Release|x64.ActiveCfg = Release|Any CPU
139 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Release|x64.Build.0 = Release|Any CPU
140 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Release|x86.ActiveCfg = Release|Any CPU
141 | {42E62FFD-F1CD-41D3-9DCB-93638D77F33B}.Release|x86.Build.0 = Release|Any CPU
142 | EndGlobalSection
143 | GlobalSection(SolutionProperties) = preSolution
144 | HideSolutionNode = FALSE
145 | EndGlobalSection
146 | GlobalSection(ExtensibilityGlobals) = postSolution
147 | SolutionGuid = {8BEC268B-CF39-4103-B514-DC9897D72C14}
148 | EndGlobalSection
149 | EndGlobal
150 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Thud/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Thud
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Thud/Thud.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Thuuud/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Thud
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Thuuud/Thuuud.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Thud
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Zap/Class1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Corgi
4 | {
5 | public class Class1
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Snitch.Tests.Fixtures/Zap/Zap.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.Default.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz...
4 | * Analyzing Bar (netstandard2.0)...
5 | * Analyzing Foo (netstandard2.0)...
6 |
7 | ╭─────────────────────────────────────────╮
8 | │ Packages that can be removed from Baz: │
9 | │ ┌──────────────┬──────────────────────┐ │
10 | │ │ Package │ Referenced by │ │
11 | │ ├──────────────┼──────────────────────┤ │
12 | │ │ Autofac │ Foo │ │
13 | │ └──────────────┴──────────────────────┘ │
14 | ╰─────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.Exclude_Autofac.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz...
4 | * Analyzing Bar (netstandard2.0)...
5 | * Analyzing Foo (netstandard2.0)...
6 |
7 | Everything looks good!
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.Skip_Bar.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz...
4 |
5 | Everything looks good!
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.Skip_Bar_NoPreRelease.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz...
4 |
5 | Everything looks good!
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.netstandard2.0.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz (netstandard2.0)...
4 | * Analyzing Bar (netstandard2.0)...
5 | * Analyzing Foo (netstandard2.0)...
6 |
7 | ╭─────────────────────────────────────────╮
8 | │ Packages that can be removed from Baz: │
9 | │ ┌──────────────┬──────────────────────┐ │
10 | │ │ Package │ Referenced by │ │
11 | │ ├──────────────┼──────────────────────┤ │
12 | │ │ Autofac │ Foo │ │
13 | │ └──────────────┴──────────────────────┘ │
14 | ╰─────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.netstandard2.0_Strict.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz (netstandard2.0)...
4 | * Analyzing Bar (netstandard2.0)...
5 | * Analyzing Foo (netstandard2.0)...
6 |
7 | ╭─────────────────────────────────────────╮
8 | │ Packages that can be removed from Baz: │
9 | │ ┌──────────────┬──────────────────────┐ │
10 | │ │ Package │ Referenced by │ │
11 | │ ├──────────────┼──────────────────────┤ │
12 | │ │ Autofac │ Foo │ │
13 | │ └──────────────┴──────────────────────┘ │
14 | ╰─────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Baz.netstandard2.Strict.NoPreRelease.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Baz.csproj
3 | Analyzing Baz...
4 | * Analyzing Bar (netstandard2.0)...
5 | * Analyzing Foo (netstandard2.0)...
6 |
7 | ╭─────────────────────────────────────────╮
8 | │ Packages that can be removed from Baz: │
9 | │ ┌──────────────┬──────────────────────┐ │
10 | │ │ Package │ Referenced by │ │
11 | │ ├──────────────┼──────────────────────┤ │
12 | │ │ Autofac │ Foo │ │
13 | │ └──────────────┴──────────────────────┘ │
14 | ╰─────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/FSharp.Default.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing FSharp.fsproj
3 | Analyzing FSharp...
4 | * Analyzing Bar (netstandard2.0)...
5 | * Analyzing Foo (netstandard2.0)...
6 |
7 | ╭────────────────────────────────────────────╮
8 | │ Packages that can be removed from FSharp: │
9 | │ ┌───────────────┬────────────────────────┐ │
10 | │ │ Package │ Referenced by │ │
11 | │ ├───────────────┼────────────────────────┤ │
12 | │ │ Autofac │ Foo │ │
13 | │ └───────────────┴────────────────────────┘ │
14 | ╰────────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Solution.Default.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Snitch.Tests.Fixtures.sln
3 | Analyzing Foo...
4 | Analyzing Bar...
5 | Analyzing Baz...
6 | Analyzing Qux...
7 | Analyzing Zap...
8 | Analyzing Quux...
9 | Analyzing Quuux...
10 | Analyzing Thud...
11 | Analyzing Thuuud...
12 |
13 | ╭─────────────────────────────────────────────────────────────────╮
14 | │ Packages that can be removed from Bar: │
15 | │ ┌──────────────────────┬──────────────────────────────────────┐ │
16 | │ │ Package │ Referenced by │ │
17 | │ ├──────────────────────┼──────────────────────────────────────┤ │
18 | │ │ Autofac │ Foo │ │
19 | │ └──────────────────────┴──────────────────────────────────────┘ │
20 | │ │
21 | │ Packages that can be removed from Baz: │
22 | │ ┌──────────────────────┬──────────────────────────────────────┐ │
23 | │ │ Package │ Referenced by │ │
24 | │ ├──────────────────────┼──────────────────────────────────────┤ │
25 | │ │ Autofac │ Foo │ │
26 | │ └──────────────────────┴──────────────────────────────────────┘ │
27 | │ │
28 | │ Packages that might be removed from Qux: │
29 | │ ┌───────────┬───────────┬─────────────────────────────────────┐ │
30 | │ │ Package │ Version │ Reason │ │
31 | │ ├───────────┼───────────┼─────────────────────────────────────┤ │
32 | │ │ Autofac │ 4.9.3 │ Downgraded from 4.9.4 in Foo │ │
33 | │ └───────────┴───────────┴─────────────────────────────────────┘ │
34 | │ │
35 | │ Packages that might be removed from Zap: │
36 | │ ┌──────────────────┬──────────┬───────────────────────────────┐ │
37 | │ │ Package │ Version │ Reason │ │
38 | │ ├──────────────────┼──────────┼───────────────────────────────┤ │
39 | │ │ Newtonsoft.Json │ 12.0.3 │ Updated from 12.0.1 in Foo │ │
40 | │ │ Autofac │ 4.9.3 │ Downgraded from 4.9.4 in Foo │ │
41 | │ └──────────────────┴──────────┴───────────────────────────────┘ │
42 | │ │
43 | │ Packages that might be removed from Thuuud: │
44 | │ ┌─────────────────┬──────────────┬────────────────────────────┐ │
45 | │ │ Package │ Version │ Reason │ │
46 | │ ├─────────────────┼──────────────┼────────────────────────────┤ │
47 | │ │ Newtonsoft.Json │ 13.0.2-beta2 │ Updated from 12.0.1 in Foo │ │
48 | │ └─────────────────┴──────────────┴────────────────────────────┘ │
49 | ╰─────────────────────────────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Thud.netstandard2.Strict.NoPreRelease.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Thud.csproj
3 | Analyzing Thud...
4 |
5 | ╭────────────────────────────────────────────────╮
6 | │ │
7 | │ Projects with pre-release package references: │
8 | │ ┌──────────┬──────────────────┬──────────────┐ │
9 | │ │ Project │ Package │ Version │ │
10 | │ ├──────────┼──────────────────┼──────────────┤ │
11 | │ │ Thud │ Newtonsoft.Json │ 13.0.2-beta2 │ │
12 | │ └──────────┴──────────────────┴──────────────┘ │
13 | ╰────────────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Thuuud.netstandard2.NoPreRelease.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Thuuud.csproj
3 | Analyzing Thuuud...
4 | * Analyzing Foo (netstandard2.0)...
5 |
6 | ╭─────────────────────────────────────────────────────────────────╮
7 | │ Packages that might be removed from Thuuud: │
8 | │ ┌─────────────────┬──────────────┬────────────────────────────┐ │
9 | │ │ Package │ Version │ Reason │ │
10 | │ ├─────────────────┼──────────────┼────────────────────────────┤ │
11 | │ │ Newtonsoft.Json │ 13.0.2-beta2 │ Updated from 12.0.1 in Foo │ │
12 | │ └─────────────────┴──────────────┴────────────────────────────┘ │
13 | │ │
14 | │ Projects with pre-release package references: │
15 | │ ┌─────────────┬──────────────────────────┬────────────────────┐ │
16 | │ │ Project │ Package │ Version │ │
17 | │ ├─────────────┼──────────────────────────┼────────────────────┤ │
18 | │ │ Thuuud │ Newtonsoft.Json │ 13.0.2-beta2 │ │
19 | │ └─────────────┴──────────────────────────┴────────────────────┘ │
20 | ╰─────────────────────────────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/Expectations/Thuuud.netstandard2.Strict.NoPreRelease.verified.txt:
--------------------------------------------------------------------------------
1 | Analyzing...
2 | Analyzing Thuuud.csproj
3 | Analyzing Thuuud...
4 | * Analyzing Foo (netstandard2.0)...
5 |
6 | ╭─────────────────────────────────────────────────────────────────╮
7 | │ Packages that might be removed from Thuuud: │
8 | │ ┌─────────────────┬──────────────┬────────────────────────────┐ │
9 | │ │ Package │ Version │ Reason │ │
10 | │ ├─────────────────┼──────────────┼────────────────────────────┤ │
11 | │ │ Newtonsoft.Json │ 13.0.2-beta2 │ Updated from 12.0.1 in Foo │ │
12 | │ └─────────────────┴──────────────┴────────────────────────────┘ │
13 | │ │
14 | │ Projects with pre-release package references: │
15 | │ ┌─────────────┬──────────────────────────┬────────────────────┐ │
16 | │ │ Project │ Package │ Version │ │
17 | │ ├─────────────┼──────────────────────────┼────────────────────┤ │
18 | │ │ Thuuud │ Newtonsoft.Json │ 13.0.2-beta2 │ │
19 | │ └─────────────┴──────────────────────────┴────────────────────┘ │
20 | ╰─────────────────────────────────────────────────────────────────╯
--------------------------------------------------------------------------------
/src/Snitch.Tests/ProgramTests.cs:
--------------------------------------------------------------------------------
1 | using Shouldly;
2 | using Snitch;
3 | using System;
4 | using System.IO;
5 | using System.Threading.Tasks;
6 | using Spectre.Console.Cli;
7 | using Spectre.Console.Testing;
8 | using VerifyTests;
9 | using Xunit;
10 | using VerifyXunit;
11 |
12 | namespace Sntich.Tests
13 | {
14 | [UsesVerify]
15 | public class ProgramTests
16 | {
17 | [Fact]
18 | [Expectation("Baz", "Default")]
19 | public async Task Should_Return_Expected_Result_For_Baz_Not_Specifying_Framework()
20 | {
21 | // Given
22 | var fixture = new Fixture();
23 | var project = Fixture.GetPath("Baz/Baz.csproj");
24 |
25 | // When
26 | var (exitCode, output) = await Fixture.Run(project);
27 |
28 | // Then
29 | exitCode.ShouldBe(0);
30 | await Verifier.Verify(output);
31 | }
32 |
33 | [Fact]
34 | [Expectation("Solution", "Default")]
35 | public async Task Should_Return_Expected_Result_For_Solution_Not_Specifying_Framework()
36 | {
37 | // Given
38 | var fixture = new Fixture();
39 | var solution = Fixture.GetPath("Snitch.Tests.Fixtures.sln");
40 |
41 | // When
42 | var (exitCode, output) = await Fixture.Run(solution);
43 |
44 | // Then
45 | exitCode.ShouldBe(0);
46 | await Verifier.Verify(output);
47 | }
48 |
49 | [Fact]
50 | [Expectation("Baz", "netstandard2.0")]
51 | public async Task Should_Return_Expected_Result_For_Baz_Specifying_Framework()
52 | {
53 | // Given
54 | var fixture = new Fixture();
55 | var project = Fixture.GetPath("Baz/Baz.csproj");
56 |
57 | // When
58 | var (exitCode, output) = await Fixture.Run(project, "--tfm", "netstandard2.0");
59 |
60 | // Then
61 | exitCode.ShouldBe(0);
62 | await Verifier.Verify(output);
63 | }
64 |
65 | [Fact]
66 | [Expectation("Baz", "netstandard2.0_Strict")]
67 | public async Task Should_Return_Non_Zero_Exit_Code_For_Baz_When_Running_With_Strict()
68 | {
69 | // Given
70 | var fixture = new Fixture();
71 | var project = Fixture.GetPath("Baz/Baz.csproj");
72 |
73 | // When
74 | var (exitCode, output) = await Fixture.Run(project, "--tfm", "netstandard2.0", "--strict");
75 |
76 | // Then
77 | exitCode.ShouldBe(-1);
78 | await Verifier.Verify(output);
79 | }
80 |
81 | [Fact]
82 | [Expectation("Baz", "Exclude_Autofac")]
83 | public async Task Should_Return_Expected_Result_For_Baz_When_Excluding_Library()
84 | {
85 | // Given
86 | var fixture = new Fixture();
87 | var project = Fixture.GetPath("Baz/Baz.csproj");
88 |
89 | // When
90 | var (exitCode, output) = await Fixture.Run(project, "--exclude", "Autofac");
91 |
92 | // Then
93 | exitCode.ShouldBe(0);
94 | await Verifier.Verify(output);
95 | }
96 |
97 | [Fact]
98 | [Expectation("Baz", "Skip_Bar")]
99 | public async Task Should_Return_Expected_Result_For_Baz_When_Skipping_Project()
100 | {
101 | // Given
102 | var fixture = new Fixture();
103 | var project = Fixture.GetPath("Baz/Baz.csproj");
104 |
105 | // When
106 | var (exitCode, output) = await Fixture.Run(project, "--skip", "Bar");
107 |
108 | // Then
109 | exitCode.ShouldBe(0);
110 | await Verifier.Verify(output);
111 | }
112 |
113 | [Fact]
114 | [Expectation("Baz", "Skip_Bar_NoPreRelease")]
115 | public async Task Should_Return_Expected_Result_For_Baz_When_Skipping_Project_And_NoReleases()
116 | {
117 | // Given
118 | var fixture = new Fixture();
119 | var project = Fixture.GetPath("Baz/Baz.csproj");
120 |
121 | // When
122 | var (exitCode, output) = await Fixture.Run(project, "--skip", "Bar", "--no-prerelease");
123 |
124 | // Then
125 | exitCode.ShouldBe(0);
126 | await Verifier.Verify(output);
127 | }
128 |
129 | [Fact]
130 | [Expectation("Baz", "netstandard2.Strict.NoPreRelease")]
131 | public async Task Should_Return_Non_Zero_Exit_Code_For_Baz_When_Running_With_Strict_And_NoPreRelease()
132 | {
133 | // Given
134 | var fixture = new Fixture();
135 | var project = Fixture.GetPath("Baz/Baz.csproj");
136 |
137 | // When
138 | var (exitCode, output) = await Fixture.Run(project, "--no-prerelease", "--strict");
139 |
140 | // Then
141 | exitCode.ShouldBe(-1);
142 | await Verifier.Verify(output);
143 | }
144 |
145 | [Fact]
146 | [Expectation("Thud", "netstandard2.Strict.NoPreRelease")]
147 | public async Task Should_Return_Non_Zero_Exit_Code_For_Thud_When_Running_With_Strict_And_NoPreRelease()
148 | {
149 | // Given
150 | var fixture = new Fixture();
151 | var project = Fixture.GetPath("Thud/Thud.csproj");
152 |
153 | // When
154 | var (exitCode, output) = await Fixture.Run(project, "--no-prerelease", "--strict");
155 |
156 | // Then
157 | exitCode.ShouldBe(-1);
158 | await Verifier.Verify(output);
159 | }
160 |
161 | [Fact]
162 | [Expectation("Thuuud", "netstandard2.Strict.NoPreRelease")]
163 | public async Task Should_Return_Non_Zero_Exit_Code_For_Thuuud_When_Running_With_Strict_And_NoPreRelease()
164 | {
165 | // Given
166 | var fixture = new Fixture();
167 | var project = Fixture.GetPath("Thuuud/Thuuud.csproj");
168 |
169 | // When
170 | var (exitCode, output) = await Fixture.Run(project, "--no-prerelease", "--strict");
171 |
172 | // Then
173 | exitCode.ShouldBe(-1);
174 | await Verifier.Verify(output);
175 | }
176 |
177 | [Fact]
178 | [Expectation("Thuuud", "netstandard2.NoPreRelease")]
179 | public async Task Should_Return_Zero_Exit_Code_For_Thuuud_When_Running_With_NoPreRelease()
180 | {
181 | // Given
182 | var fixture = new Fixture();
183 | var project = Fixture.GetPath("Thuuud/Thuuud.csproj");
184 |
185 | // When
186 | var (exitCode, output) = await Fixture.Run(project, "--no-prerelease");
187 |
188 | // Then
189 | exitCode.ShouldBe(0);
190 | await Verifier.Verify(output);
191 | }
192 |
193 | public sealed class Fixture
194 | {
195 | public static string GetPath(string path)
196 | {
197 | var workingDirectory = Environment.CurrentDirectory;
198 | var solutionDirectory = Path.GetFullPath(Path.Combine(workingDirectory, "../../../../Snitch.Tests.Fixtures"));
199 | return Path.GetFullPath(Path.Combine(solutionDirectory, path));
200 | }
201 |
202 | public static async Task<(int exitCode, string output)> Run(params string[] args)
203 | {
204 | var console = new TestConsole { EmitAnsiSequences = false };
205 | var exitCode = await Program.Run(args, c => c.ConfigureConsole(console));
206 | return (exitCode, console.Output.Trim());
207 | }
208 | }
209 |
210 | [Fact]
211 | [Expectation("FSharp", "Default")]
212 | public async Task Should_Return_Expected_Result_For_FSharp_Not_Specifying_Framework()
213 | {
214 | // Given
215 | var fixture = new Fixture();
216 | var project = Fixture.GetPath("FSharp/FSharp.fsproj");
217 |
218 | // When
219 | var (exitCode, output) = await Fixture.Run(project);
220 |
221 | // Then
222 | exitCode.ShouldBe(0);
223 | await Verifier.Verify(output);
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/Snitch.Tests/Snitch.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | false
6 |
7 |
8 |
9 |
10 | all
11 | runtime; build; native; contentfiles; analyzers; buildtransitive
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | all
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Snitch.Tests/VerifyConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using VerifyTests;
3 | using VerifyXunit;
4 |
5 | namespace Sntich.Tests
6 | {
7 | public static class VerifyConfiguration
8 | {
9 | [ModuleInitializer]
10 | public static void Init()
11 | {
12 | Verifier.DerivePathInfo(Expectations.Initialize);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Snitch.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29326.143
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snitch", "Snitch\Snitch.csproj", "{945553B5-F075-4455-88CF-D59A5F7F1A81}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snitch.Tests", "Snitch.Tests\Snitch.Tests.csproj", "{07CAD001-A0AE-45EF-9069-A14AEE6F99AF}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Debug|x64.ActiveCfg = Debug|Any CPU
23 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Debug|x64.Build.0 = Debug|Any CPU
24 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Debug|x86.ActiveCfg = Debug|Any CPU
25 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Debug|x86.Build.0 = Debug|Any CPU
26 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Release|x64.ActiveCfg = Release|Any CPU
29 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Release|x64.Build.0 = Release|Any CPU
30 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Release|x86.ActiveCfg = Release|Any CPU
31 | {945553B5-F075-4455-88CF-D59A5F7F1A81}.Release|x86.Build.0 = Release|Any CPU
32 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Debug|x64.ActiveCfg = Debug|Any CPU
35 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Debug|x64.Build.0 = Debug|Any CPU
36 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Debug|x86.ActiveCfg = Debug|Any CPU
37 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Debug|x86.Build.0 = Debug|Any CPU
38 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Release|x64.ActiveCfg = Release|Any CPU
41 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Release|x64.Build.0 = Release|Any CPU
42 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Release|x86.ActiveCfg = Release|Any CPU
43 | {07CAD001-A0AE-45EF-9069-A14AEE6F99AF}.Release|x86.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {8BEC268B-CF39-4103-B514-DC9897D72C14}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/src/Snitch/Analysis/Models/Package.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using NuGet.Versioning;
4 |
5 | namespace Snitch.Analysis
6 | {
7 | [DebuggerDisplay("{Name,nq} ({Version,nq})")]
8 | internal sealed class Package
9 | {
10 | public string Name { get; }
11 | public NuGetVersion? Version { get; set; }
12 | public VersionRange? Range { get; }
13 |
14 | public string? PrivateAssets { get; }
15 |
16 | public bool IsGreaterThan(Package package, out bool indeterminate)
17 | {
18 | indeterminate = true;
19 |
20 | if (Version != null && package.Version != null)
21 | {
22 | // Version > Version
23 | indeterminate = false;
24 | return new VersionComparer().Compare(Version, package.Version) > 0;
25 | }
26 | else if (Version != null && package.Range != null)
27 | {
28 | // Version > Range
29 | return package.Range.Satisfies(Version);
30 | }
31 | else if (Range != null && package.Range != null)
32 | {
33 | // Range > Range
34 | indeterminate = false;
35 | return new VersionComparer().Compare(Range.MaxVersion, package.Range.MaxVersion) > 0;
36 | }
37 | else if (Range != null && package.Version != null)
38 | {
39 | // Range > Version
40 | return Range.Satisfies(package.Version);
41 | }
42 |
43 | return false;
44 | }
45 |
46 | public bool IsSameVersion(Package package)
47 | {
48 | if (Version != null && package.Version != null)
49 | {
50 | // Version == Version
51 | return new VersionComparer().Equals(Version, package.Version);
52 | }
53 | else if (Range != null && package.Range != null)
54 | {
55 | // Range == Range
56 | new VersionRangeComparer().Equals(Range, package.Range);
57 | }
58 |
59 | return false;
60 | }
61 |
62 | public string GetVersionString()
63 | {
64 | if (Version != null)
65 | {
66 | return Version.ToString();
67 | }
68 |
69 | return Range?.OriginalString ?? "?";
70 | }
71 |
72 | public Package(string name, string version, string? privateAssets)
73 | {
74 | Name = name ?? throw new ArgumentNullException(nameof(name));
75 | PrivateAssets = privateAssets;
76 |
77 | if (NuGetVersion.TryParse(version, out var semanticVersion))
78 | {
79 | Version = semanticVersion;
80 | Range = null;
81 | }
82 | else
83 | {
84 | if (!VersionRange.TryParse(version, out var range))
85 | {
86 | throw new ArgumentException($"Version '{version}' for package '{name}' is not valid.", nameof(version));
87 | }
88 |
89 | Version = null;
90 | Range = range;
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/Models/PackageToRemove.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace Snitch.Analysis
5 | {
6 | [DebuggerDisplay("{PackageDescription(),nq}")]
7 | internal sealed class PackageToRemove
8 | {
9 | public Project Project { get; }
10 | public Package Package { get; }
11 | public ProjectPackage Original { get; }
12 |
13 | public bool CanBeRemoved => Package.IsSameVersion(Original.Package);
14 | public bool VersionMismatch => !Package.IsSameVersion(Original.Package);
15 |
16 | public PackageToRemove(Project project, Package package, ProjectPackage original)
17 | {
18 | Project = project ?? throw new ArgumentNullException(nameof(project));
19 | Package = package ?? throw new ArgumentNullException(nameof(package));
20 | Original = original ?? throw new ArgumentNullException(nameof(original));
21 | }
22 |
23 | private string PackageDescription()
24 | {
25 | return $"{Project.Name}: {Package.Name} ({Original.Project.Name})";
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/Models/Project.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 |
5 | namespace Snitch.Analysis
6 | {
7 | [DebuggerDisplay("{GetProjectName(),nq}")]
8 | internal sealed class Project
9 | {
10 | public string Path { get; }
11 | public string File { get; }
12 | public string Name { get; }
13 | public string TargetFramework { get; set; }
14 | public string? LockFilePath { get; set; }
15 | public List ProjectReferences { get; }
16 | public List Packages { get; }
17 |
18 | public Project(string path)
19 | {
20 | Path = path ?? throw new ArgumentNullException(nameof(path));
21 | File = System.IO.Path.GetFileName(Path);
22 | Name = System.IO.Path.GetFileNameWithoutExtension(Path);
23 | TargetFramework = string.Empty;
24 | ProjectReferences = new List();
25 | Packages = new List();
26 | }
27 |
28 | public void RemovePackages(IEnumerable packages)
29 | {
30 | if (packages != null)
31 | {
32 | foreach (var package in packages)
33 | {
34 | RemovePackage(package);
35 | }
36 | }
37 | }
38 |
39 | private void RemovePackage(string package)
40 | {
41 | Packages.RemoveAll(p => p.Name.Equals(package, StringComparison.OrdinalIgnoreCase));
42 | foreach (var parentProject in ProjectReferences)
43 | {
44 | parentProject.RemovePackage(package);
45 | }
46 | }
47 |
48 | private string GetProjectName()
49 | {
50 | return System.IO.Path.GetFileName(Path);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/Models/ProjectComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 |
5 | namespace Snitch.Analysis
6 | {
7 | internal sealed class ProjectComparer : IEqualityComparer
8 | {
9 | public bool Equals([AllowNull] Project x, [AllowNull] Project y)
10 | {
11 | if (x == null && y == null)
12 | {
13 | return true;
14 | }
15 |
16 | if (x == null || y == null)
17 | {
18 | return false;
19 | }
20 |
21 | return x.Path.Equals(y.Path, StringComparison.OrdinalIgnoreCase);
22 | }
23 |
24 | public int GetHashCode([DisallowNull] Project obj)
25 | {
26 | return obj?.Path?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/Models/ProjectPackage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace Snitch.Analysis
5 | {
6 | [DebuggerDisplay("{PackageDescription(),nq}")]
7 | internal sealed class ProjectPackage
8 | {
9 | public Project Project { get; }
10 | public Package Package { get; }
11 |
12 | public ProjectPackage(Project project, Package package)
13 | {
14 | Project = project ?? throw new ArgumentNullException(nameof(project));
15 | Package = package ?? throw new ArgumentNullException(nameof(package));
16 | }
17 |
18 | private string PackageDescription()
19 | {
20 | return $"{Project.Name}: {Package.Name}";
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/ProjectAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Security.Policy;
5 | using NuGet.Frameworks;
6 | using NuGet.LibraryModel;
7 | using NuGet.ProjectModel;
8 |
9 | namespace Snitch.Analysis
10 | {
11 | internal sealed class ProjectAnalyzer
12 | {
13 | public ProjectAnalyzerResult Analyze(Project project)
14 | {
15 | if (project == null)
16 | {
17 | throw new ArgumentNullException(nameof(project));
18 | }
19 |
20 | // Analyze the project.
21 | var result = new List();
22 | AnalyzeProject(project, project, result);
23 |
24 | if (project.LockFilePath != null)
25 | {
26 | // Now prune stuff that we're not interested in removing
27 | // such as private package references and analyzers.
28 | result = PruneResults(project, result);
29 | }
30 |
31 | return new ProjectAnalyzerResult(project, result);
32 | }
33 |
34 | private List AnalyzeProject(Project root, Project project, List result)
35 | {
36 | var accumulated = new List();
37 | result ??= new List();
38 |
39 | if (project.ProjectReferences.Count > 0)
40 | {
41 | // Iterate through all project references.
42 | foreach (var child in project.ProjectReferences)
43 | {
44 | // Analyze the project recursively.
45 | foreach (var item in AnalyzeProject(root, child, result))
46 | {
47 | // Didn't exist previously in the list of accumulated packages?
48 | if (!accumulated.ContainsPackage(item.Package))
49 | {
50 | accumulated.Add(new ProjectPackage(item.Project, item.Package));
51 | }
52 | }
53 | }
54 |
55 | // Was any package in the current project references
56 | // by one of the projects referenced by the project?
57 | foreach (var package in project.Packages)
58 | {
59 | var found = accumulated.FindProjectPackage(package);
60 | if (found != null)
61 | {
62 | if (!result.ContainsPackage(found.Package))
63 | {
64 | if (project.Name.Equals(root.Name, StringComparison.OrdinalIgnoreCase))
65 | {
66 | result.Add(new PackageToRemove(project, package, found));
67 | }
68 | }
69 | }
70 | else
71 | {
72 | AddToAccumulated(package);
73 | }
74 | }
75 | }
76 | else
77 | {
78 | foreach (var item in project.Packages)
79 | {
80 | if (!accumulated.ContainsPackage(item))
81 | {
82 | AddToAccumulated(item);
83 | }
84 | }
85 | }
86 |
87 | void AddToAccumulated(Package package)
88 | {
89 | if (package.PrivateAssets != null && package.PrivateAssets.Contains("compile"))
90 | {
91 | return;
92 | }
93 |
94 | // Add the package to the list of accumulated packages.
95 | accumulated.Add(new ProjectPackage(project, package));
96 | }
97 |
98 | return accumulated;
99 | }
100 |
101 | private static List PruneResults(Project project, List packages)
102 | {
103 | // Read the lockfile.
104 | var lockfile = new LockFileFormat().Read(project.LockFilePath);
105 |
106 | // Find the expected target.
107 | var framework = NuGetFramework.Parse(project.TargetFramework);
108 | var target = lockfile.PackageSpec.TargetFrameworks.FirstOrDefault(
109 | x => x.FrameworkName.Framework.Equals(framework.Framework, StringComparison.OrdinalIgnoreCase));
110 |
111 | // Could we not find the target?
112 | if (target == null)
113 | {
114 | throw new InvalidOperationException("Could not determine target framework");
115 | }
116 |
117 | var result = new List();
118 | foreach (var package in packages)
119 | {
120 | // Try to find the dependency.
121 | var dependency = target.Dependencies.FirstOrDefault(
122 | x => x.Name.Equals(package.Package.Name, StringComparison.OrdinalIgnoreCase));
123 |
124 | if (dependency != null)
125 | {
126 | // Auto referenced or private package?
127 | if (dependency.AutoReferenced ||
128 | dependency.SuppressParent == LibraryIncludeFlags.All)
129 | {
130 | continue;
131 | }
132 | }
133 |
134 | result.Add(package);
135 | }
136 |
137 | return result;
138 | }
139 | }
140 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/ProjectAnalyzerResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Snitch.Analysis
6 | {
7 | internal sealed class ProjectAnalyzerResult
8 | {
9 | private readonly Project _project;
10 | private readonly List _packages;
11 |
12 | public string Project => _project.Name;
13 | public IReadOnlyList CanBeRemoved { get; }
14 | public IReadOnlyList MightBeRemoved { get; }
15 | public IReadOnlyList PreReleasePackages { get; }
16 |
17 | public bool NoPackagesToRemove => CanBeRemoved.Count == 0 && MightBeRemoved.Count == 0;
18 |
19 | public bool HasPreReleases => PreReleasePackages.Count > 0;
20 |
21 | public ProjectAnalyzerResult(Project project, IEnumerable packages)
22 | {
23 | _project = project;
24 | _packages = new List(packages ?? throw new ArgumentNullException(nameof(packages)));
25 |
26 | CanBeRemoved = new List(packages.Where(p => p.CanBeRemoved));
27 | MightBeRemoved = new List(packages.Where(p => p.VersionMismatch));
28 | PreReleasePackages = new List(project.Packages.Where(p => p.Version != null && p.Version.IsPrerelease));
29 | }
30 |
31 | public ProjectAnalyzerResult Filter(string[]? packages)
32 | {
33 | if (packages == null)
34 | {
35 | return this;
36 | }
37 |
38 | var filtered = _packages.Where(p => !packages.Contains(p.Package.Name, StringComparer.OrdinalIgnoreCase));
39 | return new ProjectAnalyzerResult(_project, filtered);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/ProjectBuildResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Snitch.Analysis
5 | {
6 | internal sealed class ProjectBuildResult
7 | {
8 | public Project Project { get; }
9 | public IReadOnlyList Dependencies { get; }
10 |
11 | public ProjectBuildResult(Project project, IEnumerable dependencies)
12 | {
13 | Project = project;
14 | Dependencies = new List(dependencies ?? Enumerable.Empty());
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/ProjectBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Buildalyzer;
6 | using Snitch.Analysis.Utilities;
7 | using Spectre.Console;
8 |
9 | namespace Snitch.Analysis
10 | {
11 | internal sealed class ProjectBuilder
12 | {
13 | private readonly IAnsiConsole _console;
14 |
15 | public ProjectBuilder(IAnsiConsole console)
16 | {
17 | _console = console ?? throw new ArgumentNullException(nameof(console));
18 | }
19 |
20 | public ProjectBuildResult Build(
21 | string path,
22 | string? tfm,
23 | string[]? skip,
24 | IEnumerable? cache = null)
25 | {
26 | var manager = new AnalyzerManager();
27 | var built = cache?.ToDictionary(x => x.File, x => x, StringComparer.OrdinalIgnoreCase)
28 | ?? new Dictionary(StringComparer.OrdinalIgnoreCase);
29 |
30 | var project = Build(manager, path, tfm, skip, built);
31 |
32 | // Get all dependencies which are all built projects minus the project.
33 | var dependencies = new HashSet(built.Values, new ProjectComparer());
34 | dependencies.Remove(project);
35 |
36 | return new ProjectBuildResult(project, dependencies);
37 | }
38 |
39 | private Project Build(
40 | AnalyzerManager manager,
41 | string path,
42 | string? tfm,
43 | string[]? skip,
44 | Dictionary built,
45 | int indentation = 0)
46 | {
47 | if (manager == null)
48 | {
49 | throw new ArgumentNullException(nameof(manager));
50 | }
51 |
52 | if (built == null)
53 | {
54 | throw new ArgumentNullException(nameof(built));
55 | }
56 |
57 | path = Path.GetFullPath(path);
58 |
59 | // Already built this project?
60 | if (built.TryGetValue(Path.GetFileName(path), out var project))
61 | {
62 | return project;
63 | }
64 |
65 | project = new Project(path);
66 |
67 | var result = Build(manager, project, tfm, indentation);
68 | if (result == null)
69 | {
70 | throw new InvalidOperationException($"Could not build {path}.");
71 | }
72 |
73 | // Get the asset path.
74 | var assetPath = result.GetProjectAssetsFilePath();
75 | if (!string.IsNullOrWhiteSpace(assetPath))
76 | {
77 | if (!File.Exists(assetPath))
78 | {
79 | // Todo: Make sure this exists in future
80 | throw new InvalidOperationException($"{assetPath} not found. Please restore the project's dependencies before running Snitch.");
81 | }
82 | }
83 | else
84 | {
85 | var prefix = new string(' ', indentation * 2);
86 | if (indentation > 0)
87 | {
88 | prefix += " ";
89 | }
90 |
91 | _console.MarkupLine($"{prefix}[yellow]WARN:[/] Old CSPROJ format can't be analyzed");
92 | }
93 |
94 | // Set project information.
95 | project.TargetFramework = result.TargetFramework;
96 | project.LockFilePath = assetPath;
97 |
98 | // Add the project to the built list.
99 | built.Add(Path.GetFileName(path), project);
100 |
101 | // Get the package references.
102 | foreach (var packageReference in result.PackageReferences)
103 | {
104 | if (packageReference.Value.TryGetValue("Version", out var version))
105 | {
106 | var privateAssets = packageReference.Value.GetValueOrDefault("PrivateAssets");
107 |
108 | project.Packages.Add(new Package(packageReference.Key, version, privateAssets));
109 | }
110 | }
111 |
112 | // Analyze all project references.
113 | foreach (var projectReference in result.ProjectReferences)
114 | {
115 | var projectReferencePath = PathUtility.GetPathRelativeToProject(project, projectReference);
116 |
117 | if (skip != null)
118 | {
119 | var projectName = Path.GetFileNameWithoutExtension(projectReferencePath);
120 | if (skip.Contains(projectName, StringComparer.OrdinalIgnoreCase))
121 | {
122 | continue;
123 | }
124 | }
125 |
126 | if (!projectReferencePath.EndsWith("csproj", StringComparison.OrdinalIgnoreCase) && !projectReferencePath.EndsWith("fsproj", StringComparison.OrdinalIgnoreCase))
127 | {
128 | _console.MarkupLine(string.IsNullOrWhiteSpace(tfm)
129 | ? $"Skipping Non .NET Project [aqua]{project.Name}[/]"
130 | : $"Skipping Non .NET Project [aqua]{project.Name}[/] [grey] ({tfm})[/]");
131 |
132 | _console.WriteLine();
133 |
134 | continue;
135 | }
136 |
137 | var analyzedProjectReference = Build(manager, projectReferencePath, project.TargetFramework, skip, built, indentation + 1);
138 | project.ProjectReferences.Add(analyzedProjectReference);
139 | }
140 |
141 | return project;
142 | }
143 |
144 | private IAnalyzerResult? Build(AnalyzerManager manager, Project project, string? tfm, int indentation)
145 | {
146 | var prefix = new string(' ', indentation * 2);
147 | if (indentation > 0)
148 | {
149 | prefix += "[grey]*[/] ";
150 | }
151 |
152 | var status = string.IsNullOrWhiteSpace(tfm)
153 | ? $"{prefix}Analyzing [aqua]{project.Name}[/]..."
154 | : $"{prefix}Analyzing [aqua]{project.Name}[/] [grey]({tfm})[/]...";
155 |
156 | _console.MarkupLine(status);
157 |
158 | var projectAnalyzer = manager.GetProject(project.Path);
159 | var results = (IEnumerable)projectAnalyzer.Build();
160 |
161 | if (!string.IsNullOrWhiteSpace(tfm))
162 | {
163 | var closest = results.GetNearestFrameworkMoniker(tfm);
164 | results = results.Where(p => p.TargetFramework.Equals(closest, StringComparison.OrdinalIgnoreCase));
165 | }
166 |
167 | return results.FirstOrDefault();
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/ProjectReporter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Linq;
5 | using Spectre.Console;
6 |
7 | namespace Snitch.Analysis
8 | {
9 | internal class ProjectReporter
10 | {
11 | private readonly IAnsiConsole _console;
12 |
13 | public ProjectReporter(IAnsiConsole console)
14 | {
15 | _console = console ?? throw new ArgumentNullException(nameof(console));
16 | }
17 |
18 | public void WriteToConsole([NotNull] List results, bool noPreRelease)
19 | {
20 | var resultsWithPackageToRemove = results.Where(r => r.CanBeRemoved.Count > 0).ToList();
21 | var resultsWithPackageMayBeRemove = results.Where(r => r.MightBeRemoved.Count > 0).ToList();
22 | var resultsWithPreReleases = results.Where(r => r.PreReleasePackages.Count > 0).ToList();
23 |
24 | if (results.All(x => x.NoPackagesToRemove) && (!noPreRelease || resultsWithPreReleases.Count == 0))
25 | {
26 | // Output the result.
27 | _console.WriteLine();
28 | _console.MarkupLine("[green]Everything looks good![/]");
29 | _console.WriteLine();
30 | return;
31 | }
32 |
33 | var report = new Grid();
34 | report.AddColumn();
35 |
36 | if (resultsWithPackageToRemove.Count > 0)
37 | {
38 | foreach (var (_, _, last, result) in resultsWithPackageToRemove.Enumerate())
39 | {
40 | var table = new Table().BorderColor(Color.Grey).Expand();
41 | table.AddColumns("[grey]Package[/]", "[grey]Referenced by[/]");
42 | foreach (var item in result.CanBeRemoved)
43 | {
44 | table.AddRow(
45 | $"[green]{item.Package.Name}[/]",
46 | $"[aqua]{item.Original.Project.Name}[/]");
47 | }
48 |
49 | report.AddRow($" [yellow]Packages that can be removed from[/] [aqua]{result.Project}[/]:");
50 | report.AddRow(table);
51 |
52 | if (!last || (last && resultsWithPackageMayBeRemove.Count > 0))
53 | {
54 | report.AddEmptyRow();
55 | }
56 | }
57 | }
58 |
59 | if (resultsWithPackageMayBeRemove.Count > 0)
60 | {
61 | foreach (var (_, _, last, result) in resultsWithPackageMayBeRemove.Enumerate())
62 | {
63 | var table = new Table().BorderColor(Color.Grey).Expand();
64 | table.AddColumns("[grey]Package[/]", "[grey]Version[/]", "[grey]Reason[/]");
65 |
66 | foreach (var item in result.MightBeRemoved)
67 | {
68 | if (item.Package.IsGreaterThan(item.Original.Package, out var indeterminate))
69 | {
70 | var name = item.Original.Project.Name;
71 | var version = item.Original.Package.GetVersionString();
72 | var verb = indeterminate ? "Might be updated from" : "Updated from";
73 | var reason = $"[grey]{verb}[/] [silver]{version}[/] [grey]in[/] [aqua]{name}[/]";
74 |
75 | table.AddRow(
76 | $"[green]{item.Package.Name}[/]",
77 | item.Package.GetVersionString(),
78 | reason);
79 | }
80 | else
81 | {
82 | var name = item.Original.Project.Name;
83 | var version = item.Original.Package.GetVersionString();
84 | var verb = indeterminate ? "Does not match" : "Downgraded from";
85 | var reason = $"[grey]{verb}[/] [silver]{version}[/] [grey]in[/] [aqua]{name}[/]";
86 |
87 | table.AddRow(
88 | $"[green]{item.Package.Name}[/]",
89 | item.Package.GetVersionString(),
90 | reason);
91 | }
92 | }
93 |
94 | report.AddRow($" [yellow]Packages that [u]might[/] be removed from[/] [aqua]{result.Project}[/]:");
95 | report.AddRow(table);
96 |
97 | if (!last)
98 | {
99 | report.AddEmptyRow();
100 | }
101 | }
102 | }
103 |
104 | if (noPreRelease && resultsWithPreReleases.Count > 0)
105 | {
106 | report.AddEmptyRow();
107 | report.AddRow($" [yellow]Projects with pre-release package references:[/]");
108 | var packagesByProject = resultsWithPreReleases.SelectMany(x => x.PreReleasePackages, (project, package) => new
109 | {
110 | Project = project.Project,
111 | PackageName = package.Name,
112 | Version = package.Version,
113 | })
114 | .OrderBy(o => o.Project)
115 | .ToList();
116 |
117 | var table = new Table().BorderColor(Color.Grey).Expand();
118 | table.AddColumns("[grey]Project[/]", "[grey]Package[/]", "[grey]Version[/]");
119 | foreach (var item in packagesByProject)
120 | {
121 | table.AddRow(
122 | $"[green]{item.Project}[/]",
123 | $"[yellow]{item.PackageName}[/]",
124 | $"{item.Version}");
125 | }
126 |
127 | report.AddRow(table);
128 | }
129 |
130 | _console.WriteLine();
131 | _console.Write(
132 | new Panel(report)
133 | .RoundedBorder()
134 | .BorderColor(Color.Grey));
135 | }
136 | }
137 | }
--------------------------------------------------------------------------------
/src/Snitch/Analysis/Utilities/PathUtility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Microsoft.Build.Construction;
6 |
7 | namespace Snitch.Analysis.Utilities
8 | {
9 | internal static class PathUtility
10 | {
11 | public static string GetPathRelativeToProject(Project root, string path)
12 | {
13 | var rootPath = Path.GetDirectoryName(root.Path);
14 | if (rootPath == null)
15 | {
16 | throw new InvalidOperationException("Could not get project root path.");
17 | }
18 |
19 | return Path.GetFullPath(Path.Combine(rootPath, path));
20 | }
21 |
22 | public static List GetProjectPaths(string? path, out string entry)
23 | {
24 | if (!string.IsNullOrWhiteSpace(path))
25 | {
26 | path = Path.GetFullPath(path);
27 | if (!File.Exists(path))
28 | {
29 | if (Directory.Exists(path))
30 | {
31 | return FindProjects(path, out entry);
32 | }
33 |
34 | throw new InvalidOperationException("Project or solution file do not exist.");
35 | }
36 |
37 | entry = path;
38 | return GetProjectsFromFile(path);
39 | }
40 |
41 | return FindProjects(null, out entry);
42 | }
43 |
44 | private static List GetProjectsFromFile(string path)
45 | {
46 | if (path.EndsWith("proj", StringComparison.InvariantCulture))
47 | {
48 | return new List { path };
49 | }
50 |
51 | if (path.EndsWith(".sln", StringComparison.InvariantCulture))
52 | {
53 | return GetProjectsFromSolution(path);
54 | }
55 |
56 | throw new InvalidOperationException("Project or solution file do not exist.");
57 | }
58 |
59 | private static List FindProjects(string? root, out string entry)
60 | {
61 | root ??= Environment.CurrentDirectory;
62 |
63 | var slns = Directory.GetFiles(root, "*.sln");
64 |
65 | if (slns.Length == 0)
66 | {
67 | var subProjects = Directory.GetFiles(root, "*.csproj");
68 | if (subProjects.Length == 0)
69 | {
70 | throw new InvalidOperationException("No project or solution file found.");
71 | }
72 | else if (subProjects.Length > 1)
73 | {
74 | throw new InvalidOperationException("More than one project file found.");
75 | }
76 |
77 | entry = subProjects[0];
78 | return new List(new[] { subProjects[0] });
79 | }
80 | else if (slns.Length > 1)
81 | {
82 | throw new InvalidOperationException("More than one solution file found.");
83 | }
84 | else
85 | {
86 | entry = slns[0];
87 | return GetProjectsFromSolution(slns[0]);
88 | }
89 | }
90 |
91 | private static List GetProjectsFromSolution(string solution)
92 | {
93 | var solutionFile = SolutionFile.Parse(solution);
94 | return solutionFile.ProjectsInOrder.Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat).Select(p => p.AbsolutePath).Distinct().ToList();
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/src/Snitch/Commands/AnalyzeCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics.CodeAnalysis;
5 | using System.IO;
6 | using System.Linq;
7 | using Snitch.Analysis;
8 | using Snitch.Analysis.Utilities;
9 | using Spectre.Console;
10 | using Spectre.Console.Cli;
11 |
12 | namespace Snitch.Commands
13 | {
14 | [Description("Shows transitive package dependencies that can be removed")]
15 | public sealed class AnalyzeCommand : Command
16 | {
17 | private readonly IAnsiConsole _console;
18 | private readonly ProjectBuilder _builder;
19 | private readonly ProjectAnalyzer _analyzer;
20 | private readonly ProjectReporter _reporter;
21 |
22 | public sealed class Settings : CommandSettings
23 | {
24 | [CommandArgument(0, "[PROJECT|SOLUTION]")]
25 | [Description("The project or solution you want to analyze.")]
26 | public string ProjectOrSolutionPath { get; set; } = string.Empty;
27 |
28 | [CommandOption("-t|--tfm ")]
29 | [Description("The target framework moniker to analyze.")]
30 | public string? TargetFramework { get; set; }
31 |
32 | [CommandOption("-e|--exclude ")]
33 | [Description("One or more packages to exclude.")]
34 | public string[]? Exclude { get; set; }
35 |
36 | [CommandOption("--skip ")]
37 | [Description("One or more project references to exclude.")]
38 | public string[]? Skip { get; set; }
39 |
40 | [CommandOption("-s|--strict")]
41 | [Description("Returns exit code 0 only if no conflicts were found.")]
42 | public bool Strict { get; set; }
43 |
44 | [CommandOption("--no-prerelease")]
45 | [Description("Verifies that all package references are not pre-releases.")]
46 | public bool NoPreRelease { get; set; }
47 | }
48 |
49 | public AnalyzeCommand(IAnsiConsole console)
50 | {
51 | _console = console ?? throw new ArgumentNullException(nameof(console));
52 | _builder = new ProjectBuilder(console);
53 | _analyzer = new ProjectAnalyzer();
54 | _reporter = new ProjectReporter(console);
55 | }
56 |
57 | public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
58 | {
59 | var projectsToAnalyze = PathUtility.GetProjectPaths(settings.ProjectOrSolutionPath, out var entry);
60 |
61 | // Remove all projects that we want to skip.
62 | projectsToAnalyze.RemoveAll(p =>
63 | {
64 | var projectName = Path.GetFileNameWithoutExtension(p);
65 | return settings.Skip?.Contains(projectName, StringComparer.OrdinalIgnoreCase) ?? false;
66 | });
67 |
68 | var targetFramework = settings.TargetFramework;
69 | var analyzerResults = new List();
70 | var projectCache = new HashSet(new ProjectComparer());
71 |
72 | _console.WriteLine();
73 |
74 | return _console.Status().Start($"Analyzing...", ctx =>
75 | {
76 | ctx.Refresh();
77 |
78 | _console.MarkupLine($"Analyzing [yellow]{Path.GetFileName(entry)}[/]");
79 |
80 | foreach (var projectToAnalyze in projectsToAnalyze)
81 | {
82 | // Perform a design time build of the project.
83 | var buildResult = _builder.Build(
84 | projectToAnalyze,
85 | targetFramework,
86 | settings.Skip,
87 | projectCache);
88 |
89 | // Update the cache of built projects.
90 | projectCache.Add(buildResult.Project);
91 | foreach (var item in buildResult.Dependencies)
92 | {
93 | projectCache.Add(item);
94 | }
95 |
96 | // Analyze the project.
97 | var analyzeResult = _analyzer.Analyze(buildResult.Project);
98 | if (settings.Exclude?.Length > 0)
99 | {
100 | // Filter packages that should be excluded.
101 | analyzeResult = analyzeResult.Filter(settings.Exclude);
102 | }
103 |
104 | analyzerResults.Add(analyzeResult);
105 | }
106 |
107 | // Write the report to the console
108 | _reporter.WriteToConsole(analyzerResults, settings.NoPreRelease);
109 |
110 | // Return the correct exit code.
111 | return GetExitCode(settings, analyzerResults);
112 | });
113 | }
114 |
115 | private static int GetExitCode(Settings settings, List result)
116 | {
117 | if (settings.Strict && (result.Any(r => !r.NoPackagesToRemove) || (settings.NoPreRelease && result.Any(r => r.HasPreReleases))))
118 | {
119 | return -1;
120 | }
121 |
122 | return 0;
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/src/Snitch/Commands/VersionCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using Spectre.Console.Cli;
4 |
5 | namespace Snitch.Commands
6 | {
7 | [Description("Prints the Snitch version number")]
8 | public sealed class VersionCommand : Command
9 | {
10 | public override int Execute(CommandContext context)
11 | {
12 | Console.WriteLine(typeof(VersionCommand).Assembly.GetName().Version);
13 | return 0;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Snitch/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Snitch.Commands;
4 | using Snitch.Utilities;
5 | using Spectre.Console.Cli;
6 |
7 | namespace Snitch
8 | {
9 | public static class Program
10 | {
11 | public static async Task Main(string[] args)
12 | {
13 | return await Run(args);
14 | }
15 |
16 | public static async Task Run(string[] args, Action? configator = null)
17 | {
18 | var typeRegistrar = new TypeRegistrar();
19 | typeRegistrar.Register(typeof(AnalyzeCommand.Settings), typeof(AnalyzeCommand.Settings));
20 |
21 | var app = new CommandApp(typeRegistrar);
22 |
23 | app.Configure(config =>
24 | {
25 | config.SetApplicationName("snitch");
26 | configator?.Invoke(config);
27 |
28 | config.UseStrictParsing();
29 | config.ValidateExamples();
30 |
31 | config.AddExample(new[] { "Project.csproj" });
32 | config.AddExample(new[] { "Project.csproj", "-e", "Foo", "-e", "Bar" });
33 | config.AddExample(new[] { "Project.csproj", "--tfm", "net462" });
34 | config.AddExample(new[] { "Project.csproj", "--tfm", "net462", "--strict" });
35 |
36 | config.AddExample(new[] { "Solution.sln" });
37 | config.AddExample(new[] { "Solution.sln", "-e", "Foo", "-e", "Bar" });
38 | config.AddExample(new[] { "Solution.sln", "--tfm", "net462" });
39 | config.AddExample(new[] { "Solution.sln", "--tfm", "net462", "--strict" });
40 |
41 | config.AddCommand("version");
42 | });
43 |
44 | return await app.RunAsync(args);
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Snitch/Snitch.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | 10.0
6 | net8.0
7 | 1701;1702;SA0001
8 | enable
9 | true
10 | snitch
11 | icon.png
12 |
13 |
14 |
15 |
16 | A tool that help you find transitive package references.
17 | Spectre Systems AB
18 | Spectre Systems AB
19 | Patrik Svensson
20 | MIT
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | All
37 |
38 |
39 | All
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/Snitch/Utilities/Extensions/AnalyzerResultExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Buildalyzer;
4 | using NuGet.Frameworks;
5 |
6 | namespace Snitch.Analysis
7 | {
8 | internal static class AnalyzerResultExtensions
9 | {
10 | public static string? GetProjectAssetsFilePath(this IAnalyzerResult result)
11 | {
12 | return result?.GetProperty("ProjectAssetsFile");
13 | }
14 |
15 | public static string GetNearestFrameworkMoniker(this IEnumerable source, string framework)
16 | {
17 | var current = NuGetFramework.Parse(framework, DefaultFrameworkNameProvider.Instance);
18 | return current.GetNearestFrameworkMoniker(source.Select(x => x.TargetFramework));
19 | }
20 |
21 | private static string GetNearestFrameworkMoniker(this NuGetFramework framework, IEnumerable candidates)
22 | {
23 | var provider = DefaultFrameworkNameProvider.Instance;
24 | var reducer = new FrameworkReducer();
25 |
26 | var mappings = new Dictionary(
27 | candidates.ToDictionary(
28 | x => NuGetFramework.Parse(x, provider), y => y, new NuGetFrameworkFullComparer()));
29 |
30 | return mappings[reducer.GetNearest(framework, mappings.Keys)];
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Snitch/Utilities/Extensions/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Snitch
5 | {
6 | public static class EnumerableExtensions
7 | {
8 | public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate(this IEnumerable source)
9 | {
10 | if (source is null)
11 | {
12 | throw new ArgumentNullException(nameof(source));
13 | }
14 |
15 | return Enumerate(source.GetEnumerator());
16 | }
17 |
18 | public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate(this IEnumerator source)
19 | {
20 | if (source is null)
21 | {
22 | throw new ArgumentNullException(nameof(source));
23 | }
24 |
25 | var first = true;
26 | var last = !source.MoveNext();
27 | T current;
28 |
29 | for (var index = 0; !last; index++)
30 | {
31 | current = source.Current;
32 | last = !source.MoveNext();
33 | yield return (index, first, last, current);
34 | first = false;
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Snitch/Utilities/Extensions/PackageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Snitch.Analysis
6 | {
7 | internal static class PackageExtensions
8 | {
9 | public static bool ContainsPackage(this IEnumerable source, Package package)
10 | {
11 | return source.Any(x => x.Package.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase));
12 | }
13 |
14 | public static ProjectPackage? FindProjectPackage(this IEnumerable source, Package package)
15 | {
16 | return source.FirstOrDefault(p => p.Package.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase));
17 | }
18 |
19 | public static bool ContainsPackage(this IEnumerable source, Package package)
20 | {
21 | return source.Any(x => x.Package.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase));
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Snitch/Utilities/TypeRegistrar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Autofac;
3 | using Spectre.Console.Cli;
4 |
5 | namespace Snitch.Utilities
6 | {
7 | internal sealed class TypeRegistrar : ITypeRegistrar
8 | {
9 | private readonly ContainerBuilder _builder;
10 |
11 | public TypeRegistrar()
12 | {
13 | _builder = new ContainerBuilder();
14 | }
15 |
16 | public ITypeResolver Build()
17 | {
18 | return new TypeResolver(_builder.Build());
19 | }
20 |
21 | public void Register(Type service, Type implementation)
22 | {
23 | _builder.RegisterType(implementation).As(service).SingleInstance();
24 | }
25 |
26 | public void RegisterInstance(Type service, object implementation)
27 | {
28 | _builder.RegisterInstance(implementation).As(service);
29 | }
30 |
31 | public void RegisterLazy(Type service, Func