├── .github
└── workflows
│ └── dotnetcore.yml
├── .gitignore
├── CONTRIBUTORS
├── LICENSE
├── ProjectReferencesRuler.Tests
├── CsprojReferencesExtractorTests.cs
├── Dogfooding
│ ├── PackageReferenceTests.cs
│ ├── ProjectReferenceTests.cs
│ ├── ProjectsExistanceCheckTests.cs
│ └── SolutionCheckTests.cs
├── ProjectReferencesRuler.Tests.csproj
├── ReferenceDuplicatesCheckerTests.cs
├── ReferencesExistenceCheckerTests.cs
├── ReferencesRulerRunnerTests.cs
├── Rules
│ └── References
│ │ ├── ReferenceRuleBuilderTest.cs
│ │ └── ReferencesRulerTests.cs
├── SolutionParserTests.cs
├── SolutionProjectGuidCheckerTests.cs
├── TestProjectFiles
│ ├── Dg.Component.Contracts.xml
│ ├── Dg.Component.xml
│ ├── Dg.Returns.Contracts.xml
│ ├── Dg.Returns.xml
│ ├── Duplicates.xml
│ ├── Empty.xml
│ ├── README.txt
│ └── ReferenceWithoutPath.xml
├── TestSolutionFiles
│ ├── LargeSolution.txt
│ ├── README.txt
│ ├── SmallInvalidTestSolution.txt
│ ├── SmallSolution.txt
│ ├── SmallValidTestSolution.txt
│ └── SolutionWithFolders.txt
└── WildcardPatternParserTests.cs
├── ProjectReferencesRuler
├── PatternParsing
│ ├── IPatternParser.cs
│ ├── RegexPatternParser.cs
│ └── WildcardPatternParser.cs
├── ProjectParsing
│ ├── CsprojReferencesExtractor.cs
│ └── IReferencesExtractor.cs
├── ProjectReferencesRuler.csproj
├── ProjectRunners
│ ├── IProjectFilesRunner.cs
│ ├── ProjectFilesRunner.cs
│ └── ReferencesRulerRunner.cs
├── ProjectsRuler.cs
├── ReferenceDuplicatesChecker.cs
├── ReferencesExistenceChecker.cs
├── Rules
│ ├── Project
│ │ └── Project.cs
│ ├── References
│ │ ├── IReferencesRuler.cs
│ │ ├── Reference.cs
│ │ ├── ReferenceRule.cs
│ │ ├── ReferenceRuleBuilder.cs
│ │ └── ReferencesRuler.cs
│ └── RuleKind.cs
├── SolutionParsing
│ ├── ISolutionParser.cs
│ ├── SolutionParser.cs
│ └── SolutionProject.cs
└── SolutionProjectGuidChecker.cs
├── ProjectsRuler.sln
├── README.md
├── ReferencesRuler.nuspec
├── THIRD-PARTY-NOTICES.txt
└── logo.png
/.github/workflows/dotnetcore.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | name: Cross platform build
8 | runs-on: ${{ matrix.os }}
9 | strategy:
10 | matrix:
11 | os: [ubuntu-latest, windows-latest, macos-latest]
12 |
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: Setup .NET Core
16 | uses: actions/setup-dotnet@v1
17 | - name: Setup Nuget.exe
18 | uses: nuget/setup-nuget@v1
19 | - name: Build with dotnet
20 | run: dotnet build --configuration Release
21 | - name: Run tests
22 | run: dotnet test
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | ProjectReferencesRuler/bin/
3 | ProjectReferencesRuler/obj/
4 | MonolithRuler.sln.DotSettings.user
5 | ProjectReferencesRuler.Tests/bin/
6 | ProjectReferencesRuler.Tests/obj/
7 | MonolithRuler.Console/bin/
8 | MonolithRuler.Console/obj/
9 | ProjectsRuler.Console/bin/
10 | ProjectsRuler.Console/obj/
11 | ProjectsRuler.sln.DotSettings.user
12 | *.nupkg
13 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | bosko.stupar@digitecgalaxus.ch
2 | DomainArchitects@migros.onmicrosoft.com
3 | philipp.kiener@digitecgalaxus.ch
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2020 Digitec Galaxus AG
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.
22 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/CsprojReferencesExtractorTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using ProjectReferencesRuler.ProjectParsing;
3 | using Xunit;
4 |
5 | namespace ProjectReferencesRuler
6 | {
7 | public class CsprojReferencesExtractorTests
8 | {
9 | [Fact]
10 | public void GetProjectReferences_EmptyProjectFile_NoReferencesReturned()
11 | {
12 | var extractor = new CsprojReferencesExtractor();
13 |
14 | var references = extractor.GetProjectReferences(@"../../../TestProjectFiles/Empty.xml");
15 |
16 | Assert.Empty(references);
17 | }
18 |
19 | [Fact]
20 | public void GetProjectReferences_ProjectFileWithoutReferences_NoReferencesReturned()
21 | {
22 | var extractor = new CsprojReferencesExtractor();
23 |
24 | var references = extractor.GetProjectReferences(@"../../../TestProjectFiles/Dg.Returns.Contracts.xml");
25 |
26 | Assert.Empty(references);
27 | }
28 |
29 | [Fact]
30 | public void GetProjectReferences_ProjectFileWithReferenceWithoutPath_NoReferencesReturned()
31 | {
32 | var extractor = new CsprojReferencesExtractor();
33 |
34 | var references = extractor
35 | .GetProjectReferences(@"../../../TestProjectFiles/ReferenceWithoutPath.xml")
36 | .ToList();
37 |
38 | Assert.Empty(references);
39 | }
40 |
41 | [Fact]
42 | public void GetProjectReferences_ProjectFileWithTwoReferences_TwoReferencesReturned()
43 | {
44 | var extractor = new CsprojReferencesExtractor();
45 |
46 | var references = extractor
47 | .GetProjectReferences(@"../../../TestProjectFiles/Dg.Returns.xml")
48 | .ToList();
49 |
50 | Assert.NotEmpty(references);
51 | Assert.Equal(3, references.Count());
52 | }
53 |
54 | // **************** PackageReferences
55 |
56 | [Fact]
57 | public void GetPackageReferences_EmptyProjectFile_NoReferencesReturned()
58 | {
59 | var extractor = new CsprojReferencesExtractor();
60 |
61 | var references = extractor.GetPackageReferences(@"../../../TestProjectFiles/Empty.xml");
62 |
63 | Assert.Empty(references);
64 | }
65 |
66 | [Fact]
67 | public void GetPackageReferences_ProjectFileWith2Packages_2ReferencesReturned()
68 | {
69 | var extractor = new CsprojReferencesExtractor();
70 |
71 | var references = extractor.GetPackageReferences(@"../../../TestProjectFiles/Dg.Returns.Contracts.xml").Select(r => r.To).ToList();
72 |
73 | Assert.NotEmpty(references);
74 | Assert.Equal(2, references.Count);
75 | Assert.Contains("devinite.Tools.Roslyn.Analyzers.InProcessApiGuideline", references);
76 | Assert.Contains("Dg", references);
77 | }
78 |
79 | [Fact]
80 | public void GetPackageReferences_ProjectFileWithReferenceWithoutPath_NoReferencesReturned()
81 | {
82 | var extractor = new CsprojReferencesExtractor();
83 |
84 | var references = extractor
85 | .GetPackageReferences(@"../../../TestProjectFiles/ReferenceWithoutPath.xml")
86 | .ToList();
87 |
88 | Assert.Empty(references);
89 | }
90 |
91 | [Fact]
92 | public void GetPackageReferences_ProjectFileWith4References_4ReferencesReturned()
93 | {
94 | var extractor = new CsprojReferencesExtractor();
95 |
96 | var references = extractor
97 | .GetPackageReferences(@"../../../TestProjectFiles/Dg.Returns.xml")
98 | .Select(r => r.To)
99 | .ToList();
100 |
101 | Assert.NotEmpty(references);
102 | Assert.Equal(4, references.Count);
103 | Assert.Contains("devinite.Tools.Roslyn.Analyzers.InProcessApiGuideline", references);
104 | Assert.Contains("Dg", references);
105 | Assert.Contains("SimpleInjector", references);
106 | Assert.Contains("Deblazer", references);
107 | }
108 |
109 | [Theory]
110 | [InlineData("Dg.Component.Contracts.xml", "netstandard2.0")]
111 | [InlineData("Dg.Component.xml", "netstandard2.0")]
112 | [InlineData("Dg.Returns.Contracts.xml", "netstandard2.0")]
113 | [InlineData("Dg.Returns.xml", "netstandard2.0")]
114 | [InlineData("Duplicates.xml", "netcoreapp2.1", "netstandard2.0", "net472")]
115 | [InlineData("Empty.xml")]
116 | public void GetTargetFrameworks_WithExampleFile_ReturnsTargetFrameworksFromFile(string projectFileName, params string[] expectedTargetFrameworks)
117 | {
118 | var extractor = new CsprojReferencesExtractor();
119 |
120 | var projectProperties = extractor.GetProjectProperties(@"../../../TestProjectFiles/" + projectFileName);
121 |
122 | Assert.Equal(expectedTargetFrameworks, projectProperties.TargetFrameworks);
123 | }
124 |
125 | [Theory]
126 | [InlineData("Dg.Component.Contracts.xml")]
127 | [InlineData("Dg.Component.xml", @"$(MonolithRootDir)build/LibraryNoNetStandard.props")]
128 | [InlineData("Duplicates.xml", @"$(MonolithRootDir)build/LibraryNoNetStandard.props", @"$(MonolithRootDir)build/LibraryNetStandard.props")]
129 | [InlineData("Empty.xml")]
130 | public void GetImportedProps_WithExampleFile_ReturnsImportedPropsFromFile(string projectFileName, params string[] expectedImportedProps)
131 | {
132 | var extractor = new CsprojReferencesExtractor();
133 |
134 | var projectProperties = extractor.GetProjectProperties(@"../../../TestProjectFiles/" + projectFileName);
135 |
136 | Assert.Equal(expectedImportedProps, projectProperties.ImportedProps.ToList());
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/Dogfooding/PackageReferenceTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 | using ProjectReferencesRuler.Rules;
4 | using ProjectReferencesRuler.Rules.References;
5 | using Xunit;
6 |
7 | namespace ProjectReferencesRuler.Dogfooding
8 | {
9 | public class PackageReferenceTests
10 | {
11 | [Fact]
12 | public void AllPackageReferencesHaveToSetPrivateAssetsToAll()
13 | {
14 | AssertReferenceRules(
15 | new ReferenceRule(@"*", @"*", RuleKind.Forbidden, description: "All packages have to have PrivateAssets=\"All\" set.", isPrivateAssetsAllSet: false)
16 | );
17 | }
18 |
19 | [Fact]
20 | public void ProjectsRulerCanOnlyReferenceAllowedPackages()
21 | {
22 | AssertReferenceRules(
23 | new ReferenceRule(@"*", @"Dg*", RuleKind.Forbidden, description: "Please do not couple this project with any company stuff."),
24 | new ReferenceRule(@"*", @"Chabis*", RuleKind.Forbidden, description: "Please do not couple this project with any company stuff."),
25 | new ReferenceRule(@"ProjectReferencesRuler", @"*", RuleKind.Forbidden, description: "When possible, please keep the nuget without any other dependencies.")
26 |
27 | // allowed exceptions
28 | );
29 | }
30 |
31 | ///
32 | /// This whole test doesn't make much sense. The sole purpose is to test the rules. It will make much more sense
33 | /// when we move from the exact version checking to the minimum version checking.
34 | ///
35 | [Fact]
36 | public void PackagesShouldNotDowngrade()
37 | {
38 | AssertReferenceRules(
39 | new ReferenceRule(@"*", @"Microsoft.NET.Test.Sdk", RuleKind.Forbidden, description: "Packages should not downgrade.", version: "15.7.0"),
40 | new ReferenceRule(@"*", @"Moq*", RuleKind.Forbidden, description: "Packages should not downgrade.", version: "4.9.0"),
41 | new ReferenceRule(@"*", @"xunit", RuleKind.Forbidden, description: "Packages should not downgrade.", version: "2.3.0"),
42 | new ReferenceRule(@"*", @"xunit.runner.visualstudio", RuleKind.Forbidden, description: "Packages should not downgrade.", version: "2.4.0"),
43 |
44 | // allowed exceptions
45 | new ReferenceRule(@"ProjectReferencesRuler.Tests", @"xunit.runner.visualstudio", RuleKind.Allowed, description: "The sole purpose of this rule is to test the rule. It doesn't make much sense.", version: "2.4.0")
46 | );
47 | }
48 |
49 | [Fact]
50 | public void ReferenceRulesCanBeCreatedFluentlyToo()
51 | {
52 | var rule = ReferenceRule.For("*")
53 | .Referencing("Chabis.*")
54 | .IsForbidden()
55 | .Because("Nothing should reference Chabis")
56 | .BuildRule();
57 |
58 | AssertReferenceRules(rule);
59 | }
60 |
61 | private void AssertReferenceRules(params ReferenceRule[] rules)
62 | {
63 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
64 | var solutionDir = Path.Combine(dir, @"../../../");
65 |
66 | var complaints = ProjectsRuler.GetPackageReferencesComplaints(solutionDir, rules);
67 |
68 | Assert.Empty(complaints);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/Dogfooding/ProjectReferenceTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 | using ProjectReferencesRuler.Rules;
4 | using ProjectReferencesRuler.Rules.References;
5 | using Xunit;
6 |
7 | namespace ProjectReferencesRuler.Dogfooding
8 | {
9 | public class ProjectReferenceTests
10 | {
11 | [Fact]
12 | public void RulerProjectsShouldNotReferenceOtherProjects()
13 | {
14 | AssertReferenceRules(
15 | new ReferenceRule(@"*", @"*", RuleKind.Forbidden, description: "Projects shouldn't reference other projects."),
16 |
17 | // exceptions
18 | new ReferenceRule(@"*.Tests", @"*", RuleKind.Allowed, description: "Test projects must reference other projects in order to test them."),
19 | new ReferenceRule(@"*.Console", @"*", RuleKind.Allowed, description: "Console projects must reference other projects in order to test them."),
20 |
21 | // explicit override
22 | new ReferenceRule(@"*.Console", @"*.Tests", RuleKind.ExplicitlyForbidden, description: "Console projects must not reference test projects.")
23 | );
24 | }
25 | [Fact]
26 | public void AllProjectsShouldHavePrivateAssetsSetOnProjectReferences()
27 | {
28 | AssertReferenceRules(
29 | new ReferenceRule(@"*", @"*", RuleKind.Forbidden, description: "All references have to have PrivateAssets=\"All\" set.", isPrivateAssetsAllSet: false)
30 | );
31 | }
32 |
33 | private void AssertReferenceRules(params ReferenceRule[] rules)
34 | {
35 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
36 | var solutionDir = Path.Combine(dir, @"../../../../");
37 |
38 | var complaints = ProjectsRuler.GetProjectReferencesComplaints(solutionDir, rules);
39 |
40 | Assert.Empty(complaints);
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/Dogfooding/ProjectsExistanceCheckTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using System.Reflection;
4 | using ProjectReferencesRuler.ProjectParsing;
5 | using ProjectReferencesRuler.SolutionParsing;
6 | using Xunit;
7 |
8 | namespace ProjectReferencesRuler.Dogfooding
9 | {
10 | public class ProjectsExistanceCheckTests
11 | {
12 | [Fact]
13 | public void CheckForBrokenReferences()
14 | {
15 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
16 | var solutionDir = Path.Combine(dir, @"../../../../");
17 | var fullWithoutExternal = Path.Combine(solutionDir, "ProjectsRuler.sln");
18 | var checker = new ReferencesExistenceChecker(
19 | new SolutionParser(),
20 | new CsprojReferencesExtractor());
21 |
22 | var messages = checker.CheckProjectReferencesExistenceInSolution(fullWithoutExternal, "*.csproj").ToList();
23 |
24 | //, $"Check for broken references failed. See messages:\n{string.Join("\n", messages)}"
25 | Assert.Empty(messages);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/Dogfooding/SolutionCheckTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 | using ProjectReferencesRuler.ProjectRunners;
4 | using ProjectReferencesRuler.SolutionParsing;
5 | using Xunit;
6 |
7 | namespace ProjectReferencesRuler.Dogfooding
8 | {
9 | public class SolutionCheckTests
10 | {
11 | [Fact]
12 | public void CheckIfSolutionHasAllValidProjectGuids()
13 | {
14 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
15 | var solutionDir = Path.Combine(dir, @"../../../../");
16 | var projectGuidChecker = new SolutionProjectGuidChecker(new SolutionParser());
17 | var complaints = new ProjectFilesRunner(solutionDir, "*.sln").CollectComplaintsForFiles(
18 | filePath => new[] { projectGuidChecker.CheckSolutionProjectGuids(filePath, allowedProjectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", checkProjectFilesExtensions: ".csproj") });
19 |
20 | Assert.Empty(complaints);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/ProjectReferencesRuler.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 |
7 | ProjectReferencesRuler
8 |
9 | net5.0
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/ReferenceDuplicatesCheckerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Moq;
4 | using ProjectReferencesRuler.ProjectParsing;
5 | using ProjectReferencesRuler.ProjectRunners;
6 | using Xunit;
7 |
8 | namespace ProjectReferencesRuler
9 | {
10 | public class ReferenceDuplicatesCheckerTests
11 | {
12 | [Fact]
13 | public void CheckProjectReferenceDuplicates_NoProjectDuplicates_ReturnsEmptyString()
14 | {
15 | var filesRunnerMock = new Mock();
16 | filesRunnerMock.Setup(fr => fr.CollectComplaintsForFiles(It.IsAny>>()))
17 | .Returns((Func> func) => string.Join("\n", func(@"../../../TestProjectFiles/Dg.Component.xml")));
18 | var checker = new ReferenceDuplicatesChecker(new CsprojReferencesExtractor(), filesRunnerMock.Object);
19 |
20 | var complaints = checker.CheckProjectReferenceDuplicates();
21 |
22 | Assert.Empty(complaints);
23 | }
24 |
25 | [Fact]
26 | public void CheckProjectReferenceDuplicates_1ProjectDuplicate_Returns1Complaint()
27 | {
28 | var filesRunnerMock = new Mock();
29 | filesRunnerMock.Setup(fr => fr.CollectComplaintsForFiles(It.IsAny>>()))
30 | .Returns((Func> func) => string.Join("\n", func(@"../../../TestProjectFiles/Duplicates.xml")));
31 | var checker = new ReferenceDuplicatesChecker(new CsprojReferencesExtractor(), filesRunnerMock.Object);
32 |
33 | var complaints = checker.CheckProjectReferenceDuplicates();
34 |
35 | Assert.NotEmpty(complaints);
36 | Assert.Equal(@"There is a duplicate ProjectReference Dg.Component.Contracts in ../../../TestProjectFiles/Duplicates.xml. Please clean this up!", complaints);
37 | }
38 | [Fact]
39 | public void CheckPackageReferenceDuplicates_NoPackageDuplicates_ReturnsEmptyString()
40 | {
41 | var filesRunnerMock = new Mock();
42 | filesRunnerMock.Setup(fr => fr.CollectComplaintsForFiles(It.IsAny>>()))
43 | .Returns((Func> func) => string.Join("\n", func(@"../../../TestProjectFiles/Empty.xml")));
44 | var checker = new ReferenceDuplicatesChecker(new CsprojReferencesExtractor(), filesRunnerMock.Object);
45 |
46 | var complaints = checker.CheckPackageReferenceDuplicates();
47 |
48 | Assert.Empty(complaints);
49 | }
50 |
51 | [Fact]
52 | public void CheckPackageReferenceDuplicates_1PackageDuplicate_Returns1Complaint()
53 | {
54 | var filesRunnerMock = new Mock();
55 | filesRunnerMock.Setup(fr => fr.CollectComplaintsForFiles(It.IsAny>>()))
56 | .Returns((Func> func) => string.Join("\n", func(@"../../../TestProjectFiles/Duplicates.xml")));
57 | var checker = new ReferenceDuplicatesChecker(new CsprojReferencesExtractor(), filesRunnerMock.Object);
58 |
59 | var complaints = checker.CheckPackageReferenceDuplicates();
60 |
61 | Assert.NotEmpty(complaints);
62 | Assert.Equal(@"There is a duplicate PackageReference Dg in ../../../TestProjectFiles/Duplicates.xml. Please clean this up!", complaints);
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/ReferencesExistenceCheckerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Moq;
4 | using ProjectReferencesRuler.ProjectParsing;
5 | using ProjectReferencesRuler.SolutionParsing;
6 | using Xunit;
7 |
8 | namespace ProjectReferencesRuler
9 | {
10 | public class ReferencesExistenceCheckerTests
11 | {
12 | [Fact]
13 | public void CheckProjectReferencesInSolution_OneProjectInSolutionWithoutReferences_AllGood()
14 | {
15 | var solutionParserMock = new Mock();
16 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
17 | .Returns(new[] { new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "SomeProjectFilePath", isFolder: false) });
18 | var referencesExtractor = new Mock();
19 | var checker = new ReferencesExistenceChecker(solutionParserMock.Object, referencesExtractor.Object);
20 |
21 | var messages = checker.CheckProjectReferencesExistenceInSolution(@"../../../TestSolutionFiles/README.txt", ".xml");
22 |
23 | Assert.Empty(messages);
24 | }
25 | [Fact]
26 | public void CheckProjectReferencesInSolution_MultipleProjectAllowedProjectsInSolution_AllGood()
27 | {
28 | var solutionParserMock = new Mock();
29 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
30 | .Returns(new[]
31 | {
32 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "SomeProjectFilePath1.csproj", isFolder: false),
33 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "SomeProjectFilePath2.csproj", isFolder: false),
34 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "SomeProjectFilePath3.csproj", isFolder: false)
35 | });
36 | var referencesExtractor = new Mock();
37 | var checker = new ReferencesExistenceChecker(solutionParserMock.Object, referencesExtractor.Object);
38 |
39 | var messages = checker.CheckProjectReferencesExistenceInSolution(@"../../../TestSolutionFiles/README.txt", ".xml");
40 |
41 | Assert.Empty(messages);
42 | }
43 |
44 | [Fact]
45 | public void CheckProjectReferencesInSolution_TwoProjectsInMockedSolutionOneReferencesOther_AllGood()
46 | {
47 | var solutionParserMock = new Mock();
48 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
49 | .Returns(new[]
50 | {
51 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "project1", isFolder: false),
52 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "project2", isFolder: false)
53 | });
54 | var referencesExtractorMock = new Mock();
55 | referencesExtractorMock.Setup(re => re.GetProjectReferencePaths("project1"))
56 | .Returns(new[] {"project2"});
57 | var checker = new ReferencesExistenceChecker(solutionParserMock.Object, referencesExtractorMock.Object);
58 |
59 | var messages = checker.CheckProjectReferencesExistenceInSolution(@"../../../TestSolutionFiles/README.txt", ".xml");
60 |
61 | Assert.Empty(messages);
62 | }
63 |
64 | [Fact]
65 | public void CheckProjectReferencesInSolution_TwoProjectsInSolutionOneReferencesOther_AllGood()
66 | {
67 | var solutionParser = new SolutionParser();
68 | var referencesExtractorMock = new CsprojReferencesExtractor();
69 | var checker = new ReferencesExistenceChecker(solutionParser, referencesExtractorMock);
70 |
71 | var messages =
72 | checker.CheckProjectReferencesExistenceInSolution(@"../../../TestSolutionFiles/SmallValidTestSolution.txt",
73 | ".xml");
74 |
75 | Assert.Empty(messages);
76 | }
77 |
78 | [Fact]
79 | public void CheckProjectReferencesInSolution_TwoProjectsInSolutionOneReferenceWrong_OneMessageReturned()
80 | {
81 | var solutionParser = new SolutionParser();
82 | var referencesExtractorMock = new CsprojReferencesExtractor();
83 | var checker = new ReferencesExistenceChecker(solutionParser, referencesExtractorMock);
84 |
85 | var messages =
86 | checker.CheckProjectReferencesExistenceInSolution(@"../../../TestSolutionFiles/SmallInvalidTestSolution.txt",
87 | ".xml");
88 |
89 | // this will not work on all machines.
90 | // Assert.Equal(new[]
91 | // {
92 | // @"Project Dg.Returns.xml has a broken reference to C:/Development/MonolithRuler/Dg.DgConsolidate/Dg.DgConsolidate.csproj. This reference is completely missing from the solution SmallInvalidTestSolution.txt",
93 | // @"Project Dg.Returns.xml has a broken reference to C:/Development/MonolithRuler/Dg.InversionOfControl/Dg.InversionOfControl.csproj. This reference is completely missing from the solution SmallInvalidTestSolution.txt",
94 | // @"Project Dg.Returns.xml has a broken reference to C:/Development/MonolithRuler/ProjectReferencesRuler.Tests/Dg.Returns.Contracts/Dg.Returns.Contracts.csproj. This reference is completely missing from the solution SmallInvalidTestSolution.txt"
95 | // }, messages);
96 | Assert.Equal(3, messages.Count());
97 | }
98 |
99 | [Fact]
100 | public void CheckProjectReferencesInSolution_FolderPathPassedForSolution_ThrowsException()
101 | {
102 | var solutionParserMock = new Mock();
103 | var referencesExtractor = new Mock();
104 | var checker = new ReferencesExistenceChecker(solutionParserMock.Object, referencesExtractor.Object);
105 |
106 | Assert.Throws(() => checker.CheckProjectReferencesExistenceInSolution(@"../../../TestSolutionFiles/", ".xml"));
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/ReferencesRulerRunnerTests.cs:
--------------------------------------------------------------------------------
1 | using Moq;
2 | using ProjectReferencesRuler.ProjectParsing;
3 | using ProjectReferencesRuler.ProjectRunners;
4 | using ProjectReferencesRuler.Rules.References;
5 | using Xunit;
6 |
7 | namespace ProjectReferencesRuler
8 | {
9 | public class ReferencesRulerRunnerTests
10 | {
11 | [Fact]
12 | public void GetComplaintsForProjectReferences_GivenTestProjectFilesDirectory_ExtractsReferencesFromAllGivenFiles()
13 | {
14 | var extractorMock = new Mock();
15 | var rulerMock = new Mock();
16 | var runner = new ReferencesRulerRunner(
17 | extractor: extractorMock.Object,
18 | referencesRuler: rulerMock.Object,
19 | filesRunner: new ProjectFilesRunner(
20 | solutionPath: @"../../../TestProjectFiles/",
21 | filesExtension: "*.xml"));
22 |
23 | var complaints = runner.GetComplaintsForProjectReferences();
24 |
25 | Assert.True(string.IsNullOrEmpty(complaints));
26 | extractorMock.Verify(e => e.GetProjectReferences(It.IsAny()), Times.Exactly(7));
27 | }
28 |
29 | [Fact]
30 | public void GetComplaintsForProjectReferences_RulerHasNoComplaints_ReturnsNoComplaints()
31 | {
32 | var extractorMock = new Mock();
33 | extractorMock
34 | .Setup(e => e.GetProjectReferences(It.IsAny()))
35 | .Returns(new[] { new Reference(from: "source", to: "someReference", isPrivateAssetsAllSet: true, versionOrNull: null), });
36 | var rulerMock = new Mock();
37 | rulerMock
38 | .Setup(r => r.GiveMeComplaints(It.IsAny()))
39 | .Returns(string.Empty);
40 | var runner = new ReferencesRulerRunner(
41 | extractor: extractorMock.Object,
42 | referencesRuler: rulerMock.Object,
43 | filesRunner: new ProjectFilesRunner(
44 | solutionPath: @"../../../TestProjectFiles/",
45 | filesExtension: "*.xml"));
46 |
47 | var complaints = runner.GetComplaintsForProjectReferences();
48 |
49 | Assert.True(string.IsNullOrEmpty(complaints));
50 | rulerMock.Verify(e => e.GiveMeComplaints(It.IsAny()), Times.Exactly(7));
51 | }
52 |
53 | [Fact]
54 | public void GetComplaintsForProjectReferences_RulerHasComplaints_RunnerCollectsThemAll()
55 | {
56 | var extractorMock = new Mock();
57 | extractorMock
58 | .Setup(e => e.GetProjectReferences(It.IsAny()))
59 | .Returns(new[] { new Reference(from: "source", to: "someReference", isPrivateAssetsAllSet: true, versionOrNull: null), });
60 | var rulerMock = new Mock();
61 | rulerMock
62 | .Setup(r => r.GiveMeComplaints(It.IsAny()))
63 | .Returns("Aarrr!");
64 | var runner = new ReferencesRulerRunner(
65 | extractor: extractorMock.Object,
66 | referencesRuler: rulerMock.Object,
67 | filesRunner: new ProjectFilesRunner(
68 | solutionPath: @"../../../TestProjectFiles/",
69 | filesExtension: "*.xml"));
70 |
71 | var complaints = runner.GetComplaintsForProjectReferences();
72 |
73 | Assert.Equal("Aarrr!\nAarrr!\nAarrr!\nAarrr!\nAarrr!\nAarrr!\nAarrr!", complaints);
74 | }
75 | [Fact]
76 | public void GetComplaintsForPackageReferences_GivenTestProjectFilesDirectory_ExtractsReferencesFromAllGivenFiles()
77 | {
78 | var extractorMock = new Mock();
79 | var rulerMock = new Mock();
80 | var runner = new ReferencesRulerRunner(
81 | extractor: extractorMock.Object,
82 | referencesRuler: rulerMock.Object,
83 | filesRunner: new ProjectFilesRunner(
84 | solutionPath: @"../../../TestProjectFiles/",
85 | filesExtension: "*.xml"));
86 |
87 | var complaints = runner.GetComplaintsForPackageReferences();
88 |
89 | Assert.True(string.IsNullOrEmpty(complaints));
90 | extractorMock.Verify(e => e.GetPackageReferences(It.IsAny()), Times.Exactly(7));
91 | }
92 |
93 | [Fact]
94 | public void GetComplaintsForPackageReferences_RulerHasNoComplaints_ReturnsNoComplaints()
95 | {
96 | var extractorMock = new Mock();
97 | extractorMock
98 | .Setup(e => e.GetPackageReferences(It.IsAny()))
99 | .Returns(new[] { new Reference(from: "source", to: "someReference", isPrivateAssetsAllSet: true, versionOrNull: null), });
100 | var rulerMock = new Mock();
101 | rulerMock
102 | .Setup(r => r.GiveMeComplaints(It.IsAny()))
103 | .Returns(string.Empty);
104 | var runner = new ReferencesRulerRunner(
105 | extractor: extractorMock.Object,
106 | referencesRuler: rulerMock.Object,
107 | filesRunner: new ProjectFilesRunner(
108 | solutionPath: @"../../../TestProjectFiles/",
109 | filesExtension: "*.xml"));
110 |
111 | var complaints = runner.GetComplaintsForPackageReferences();
112 |
113 | Assert.True(string.IsNullOrEmpty(complaints));
114 | rulerMock.Verify(e => e.GiveMeComplaints(It.IsAny()), Times.Exactly(7));
115 | }
116 |
117 | [Fact]
118 | public void GetComplaintsForPackageReferences_RulerHasComplaints_RunnerCollectsThemAll()
119 | {
120 | var extractorMock = new Mock();
121 | extractorMock
122 | .Setup(e => e.GetPackageReferences(It.IsAny()))
123 | .Returns(new[] { new Reference(from: "source", to: "someReference", isPrivateAssetsAllSet: true, versionOrNull: null), });
124 | var rulerMock = new Mock();
125 | rulerMock
126 | .Setup(r => r.GiveMeComplaints(It.IsAny()))
127 | .Returns("Aarrr!");
128 | var runner = new ReferencesRulerRunner(
129 | extractor: extractorMock.Object,
130 | referencesRuler: rulerMock.Object,
131 | filesRunner: new ProjectFilesRunner(
132 | solutionPath: @"../../../TestProjectFiles/",
133 | filesExtension: "*.xml"));
134 |
135 | var complaints = runner.GetComplaintsForPackageReferences();
136 |
137 | Assert.Equal("Aarrr!\nAarrr!\nAarrr!\nAarrr!\nAarrr!\nAarrr!\nAarrr!", complaints);
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/Rules/References/ReferenceRuleBuilderTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | namespace ProjectReferencesRuler.Rules.References
5 | {
6 | public class ReferenceRuleBuilderTest
7 | {
8 | [Fact]
9 | public void BuildRule_KindNotSet_ThrowsInvalidOperationException()
10 | {
11 | var ruleBuilder = ReferenceRule.For("ProjectA")
12 | .Referencing("ProjectB")
13 | .Because("A cool rule I made");
14 |
15 | Assert.Throws(() => ruleBuilder.BuildRule());
16 | }
17 |
18 | [Fact]
19 | public void BuildRule_AllFieldsSpecified_ReturnsCorrectRule()
20 | {
21 | var ruleBuilder = ReferenceRule.For("ProjectA")
22 | .Referencing("ProjectB", version: "12.0.0", withPrivateAssetsAll: true)
23 | .IsForbidden()
24 | .Because("A cool rule I made");
25 |
26 | var rule = ruleBuilder.BuildRule();
27 |
28 | Assert.Equal("ProjectA", rule.PatternFrom);
29 | Assert.Equal("ProjectB", rule.PatternTo);
30 | Assert.Equal("A cool rule I made", rule.Description);
31 | Assert.Equal(RuleKind.Forbidden, rule.Kind);
32 | Assert.Equal(true, rule.IsPrivateAssetsAllSet);
33 | Assert.Equal("12.0.0", rule.Version);
34 | }
35 |
36 | [Fact]
37 | public void BuildRule_KindSpecifiedUsingIsAllowed_ReturnsRuleWithCorrectKind()
38 | {
39 | var ruleBuilder = ReferenceRule.For("ProjectA")
40 | .Referencing("ProjectB", version: "12.0.0", withPrivateAssetsAll: true)
41 | .IsAllowed()
42 | .Because("A cool rule I made");
43 |
44 | var rule = ruleBuilder.BuildRule();
45 |
46 | Assert.Equal(RuleKind.Allowed, rule.Kind);
47 | }
48 |
49 | [Fact]
50 | public void BuildRule_KindSpecifiedUsingIsForbidden_ReturnsRuleWithCorrectKind()
51 | {
52 | var ruleBuilder = ReferenceRule.For("ProjectA")
53 | .Referencing("ProjectB", version: "12.0.0", withPrivateAssetsAll: true)
54 | .IsForbidden()
55 | .Because("A cool rule I made");
56 |
57 | var rule = ruleBuilder.BuildRule();
58 |
59 | Assert.Equal(RuleKind.Forbidden, rule.Kind);
60 | }
61 |
62 | [Fact]
63 | public void BuildRule_KindSpecifiedUsingIsExplicitlyForbidden_ReturnsRuleWithCorrectKind()
64 | {
65 | var ruleBuilder = ReferenceRule.For("ProjectA")
66 | .Referencing("ProjectB", version: "12.0.0", withPrivateAssetsAll: true)
67 | .IsExplicitlyForbidden()
68 | .Because("A cool rule I made");
69 |
70 | var rule = ruleBuilder.BuildRule();
71 |
72 | Assert.Equal(RuleKind.ExplicitlyForbidden, rule.Kind);
73 | }
74 |
75 | [Fact]
76 | public void BuildRule_PrivateAssetsAndVersionNotSpecified_ReturnsRuleWithThoseAsNull()
77 | {
78 | var ruleBuilder = ReferenceRule.For("ProjectA")
79 | .Referencing("ProjectB")
80 | .IsForbidden()
81 | .Because("A cool rule I made");
82 |
83 | var rule = ruleBuilder.BuildRule();
84 |
85 | Assert.Null(rule.IsPrivateAssetsAllSet);
86 | Assert.Null(rule.Version);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/Rules/References/ReferencesRulerTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Xunit;
3 |
4 | namespace ProjectReferencesRuler.Rules.References
5 | {
6 | public class ReferencesRulerTests
7 | {
8 | [Fact]
9 | public void GiveMeComplaints_NoRules_NoComplaints()
10 | {
11 | var ruler = new ReferencesRuler(
12 | patternParser: new RegexPatternParser(),
13 | rules: new List());
14 |
15 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
16 |
17 | Assert.True(string.IsNullOrEmpty(complaints));
18 | }
19 |
20 | [Fact]
21 | public void GiveMeComplaints_ForbiddenExplicitRule_ReturnsComplaints()
22 | {
23 | var ruler = new ReferencesRuler(
24 | patternParser: new RegexPatternParser(),
25 | rules: new []{ new ReferenceRule("a", "b", RuleKind.Forbidden, description: "no no") });
26 |
27 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
28 |
29 | Assert.False(string.IsNullOrEmpty(complaints));
30 | Assert.Equal("Reference from a to b broke:\nno no", complaints);
31 | }
32 |
33 | [Fact]
34 | public void GiveMeComplaints_ForbiddenGeneralRule_ReturnsComplaints()
35 | {
36 | var ruler = new ReferencesRuler(
37 | patternParser: new RegexPatternParser(),
38 | rules: new []{ new ReferenceRule(".*", "b", RuleKind.Forbidden, description: "no no") });
39 |
40 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
41 |
42 | Assert.False(string.IsNullOrEmpty(complaints));
43 | Assert.Equal("Reference from a to b broke:\nno no", complaints);
44 | }
45 |
46 | [Fact]
47 | public void GiveMeComplaints_ForbiddenExplicitRuleWithExplicitOverridingRule_NoComplaints()
48 | {
49 | var ruler = new ReferencesRuler(
50 | patternParser: new RegexPatternParser(),
51 | rules: new []
52 | {
53 | new ReferenceRule("a", "b", RuleKind.Forbidden, description: "no no"),
54 | new ReferenceRule("a", "b", RuleKind.Allowed, description: "yes yes")
55 | });
56 |
57 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
58 |
59 | Assert.True(string.IsNullOrEmpty(complaints));
60 | }
61 |
62 | [Fact]
63 | public void GiveMeComplaints_ForbiddenGeneralRuleWithExplicitOverridingRule_NoComplaints()
64 | {
65 | var ruler = new ReferencesRuler(
66 | patternParser: new RegexPatternParser(),
67 | rules: new []
68 | {
69 | new ReferenceRule("*", "b", RuleKind.Forbidden, description: "no no"),
70 | new ReferenceRule("a", "b", RuleKind.Allowed, description: "yes yes")
71 | });
72 |
73 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
74 |
75 | Assert.True(string.IsNullOrEmpty(complaints));
76 | }
77 |
78 | [Theory]
79 | [InlineData(true, true)]
80 | [InlineData(false, false)]
81 | [InlineData(null, true)]
82 | public void GiveMeComplaints_ForbiddenExplicitRuleWithPrivateAssets_MatchesPrivateAssetsToo(bool? isPrivateAssetsAllSet, bool hasComplaints)
83 | {
84 | var ruler = new ReferencesRuler(
85 | patternParser: new RegexPatternParser(),
86 | rules: new []{ new ReferenceRule("a", "b", RuleKind.Forbidden, description: "no no", isPrivateAssetsAllSet: isPrivateAssetsAllSet) });
87 |
88 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
89 |
90 | Assert.Equal(hasComplaints, !string.IsNullOrEmpty(complaints));
91 | }
92 |
93 | [Theory]
94 | [InlineData(true, true)]
95 | [InlineData(false, false)]
96 | [InlineData(null, true)]
97 | public void GiveMeComplaints_ForbiddenExplicitRuleWithPrivateAssetsAndGenericRule_MatchesPrivateAssetsToo(bool? isPrivateAssetsAllSet, bool hasComplaints)
98 | {
99 | var ruler = new ReferencesRuler(
100 | patternParser: new RegexPatternParser(),
101 | rules: new []{ new ReferenceRule(".*", ".*", RuleKind.Forbidden, description: "no no", isPrivateAssetsAllSet: isPrivateAssetsAllSet) });
102 |
103 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: null));
104 |
105 | Assert.Equal(hasComplaints, !string.IsNullOrEmpty(complaints));
106 | }
107 |
108 | [Theory]
109 | [InlineData("1.2.3", true)]
110 | [InlineData("3.2.1", false)]
111 | [InlineData(null, true)]
112 | public void GiveMeComplaints_ForbiddenExplicitRuleWithVersion_MatchesVersion(string version, bool hasComplaints)
113 | {
114 | var ruler = new ReferencesRuler(
115 | patternParser: new RegexPatternParser(),
116 | rules: new []{ new ReferenceRule(".*", ".*", RuleKind.Forbidden, description: "no no", version: version) });
117 |
118 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: "1.2.3"));
119 |
120 | Assert.Equal(hasComplaints, !string.IsNullOrEmpty(complaints));
121 | }
122 |
123 | [Fact]
124 | public void GiveMeComplaints_ForbiddenExplicitRuleWithVersionAndException_MatchesVersionAndAllowsForTheException()
125 | {
126 | var ruler = new ReferencesRuler(
127 | patternParser: new RegexPatternParser(),
128 | rules: new []
129 | {
130 | new ReferenceRule(".*", ".*", RuleKind.Forbidden, description: "no no", version: "3.2.1"),
131 | new ReferenceRule(".*", ".*", RuleKind.Allowed, description: "no no", version: "1.2.3")
132 | });
133 |
134 | var complaints = ruler.GiveMeComplaints(new Reference(from: "a", to: "b", isPrivateAssetsAllSet: true, versionOrNull: "1.2.3"));
135 |
136 | Assert.True(string.IsNullOrEmpty(complaints));
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/SolutionParserTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using ProjectReferencesRuler.SolutionParsing;
4 | using Xunit;
5 |
6 | namespace ProjectReferencesRuler
7 | {
8 | public class SolutionParserTests
9 | {
10 | [Fact]
11 | public void ExtractProjectPaths_PathInvalid_ThrowsException()
12 | {
13 | var path = "this does not work";
14 | var solutionParser = new SolutionParser();
15 |
16 | var projects = solutionParser.ExtractSolutionProjects(path, ".csproj");
17 |
18 | Assert.Throws(() => projects.ToList());
19 | }
20 |
21 | [Fact]
22 | public void ExtractProjectPaths_NoLineStartsWithProject_ReturnsEmptyEnumerable()
23 | {
24 | var path = @"../../../TestSolutionFiles/README.txt";
25 | var solutionParser = new SolutionParser();
26 |
27 | var projects = solutionParser.ExtractSolutionProjects(path, ".csproj");
28 |
29 | Assert.Empty(projects);
30 | }
31 |
32 | [Fact]
33 | public void ExtractProjectPaths_SimpleSln_ReturnsPaths()
34 | {
35 | var path = @"../../../TestSolutionFiles/SmallSolution.txt";
36 | var solutionParser = new SolutionParser();
37 |
38 | var paths = solutionParser.ExtractSolutionProjects(path, ".csproj").Select(sp => sp.ProjectPath).ToList();
39 |
40 | Assert.NotEmpty(paths);
41 | Assert.Equal(
42 | new []
43 | {
44 | @"../../../TestSolutionFiles/devinite.PortalSystem/devinite.PortalSystem.csproj",
45 | @"../../../TestSolutionFiles/CustomerService/Dg.CustomerService.Contracts/Dg.CustomerService.Contracts.csproj",
46 | @"../../../TestSolutionFiles/CustomerService/Dg.CustomerService.Monolith/Dg.CustomerService.Monolith.csproj"
47 | },
48 | paths);
49 | }
50 |
51 | [Fact]
52 | public void ExtractProjectGuids_SimpleSln_ReturnsGuids()
53 | {
54 | var path = @"../../../TestSolutionFiles/SmallSolution.txt";
55 | var solutionParser = new SolutionParser();
56 |
57 | var paths = solutionParser.ExtractSolutionProjects(path, ".csproj").Select(sp => sp.ProjectGuid).ToList();
58 |
59 | Assert.NotEmpty(paths);
60 | Assert.Equal(
61 | new []
62 | {
63 | @"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC",
64 | @"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC",
65 | @"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"
66 | },
67 | paths);
68 | }
69 |
70 | [Fact]
71 | public void ExtractProjectPaths_SlnWithFolders_ReturnsOnlyProjectPaths()
72 | {
73 | var path = @"../../../TestSolutionFiles/SolutionWithFolders.txt";
74 | var solutionParser = new SolutionParser();
75 |
76 | var paths = solutionParser.ExtractSolutionProjects(path, ".csproj").Select(sp => sp.ProjectPath).ToList();
77 |
78 | Assert.NotEmpty(paths);
79 | Assert.Equal(
80 | new []
81 | {
82 | @"../../../TestSolutionFiles/devinite.PortalSystem/devinite.PortalSystem.csproj",
83 | },
84 | paths);
85 | }
86 |
87 | [Fact]
88 | public void ExtractProjectPaths_LargeSolution_ParsesWithoutErrors()
89 | {
90 | var path = @"../../../TestSolutionFiles/LargeSolution.txt";
91 | var solutionParser = new SolutionParser();
92 |
93 | var projects = solutionParser.ExtractSolutionProjects(path, ".csproj").ToList();
94 |
95 | Assert.NotEmpty(projects);
96 | Assert.Equal(142, projects.Count);
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/SolutionProjectGuidCheckerTests.cs:
--------------------------------------------------------------------------------
1 | using Moq;
2 | using ProjectReferencesRuler.ProjectParsing;
3 | using Xunit;
4 |
5 | namespace ProjectReferencesRuler
6 | {
7 | public class SolutionProjectGuidCheckerTests
8 | {
9 | [Fact]
10 | public void CheckSolutionProjectGuids_AllGuidsAllowed_AllGood()
11 | {
12 | var solutionParserMock = new Mock();
13 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
14 | .Returns(new[]
15 | {
16 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "project1", isFolder: false),
17 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "project2", isFolder: false)
18 | });
19 | var referencesExtractorMock = new Mock();
20 | referencesExtractorMock.Setup(re => re.GetProjectReferencePaths("project1"))
21 | .Returns(new[] {"project2"});
22 | var checker = new SolutionProjectGuidChecker(solutionParserMock.Object);
23 |
24 | var messages = checker.CheckSolutionProjectGuids(@"../../../TestSolutionFiles\README.txt", "9A19103F-16F7-4668-BE54-9A1E7A4F7556", ".csproj");
25 |
26 | Assert.Empty(messages);
27 | }
28 |
29 | [Fact]
30 | public void CheckSolutionProjectGuids_OneGuidNotAllowed_OneComplaint()
31 | {
32 | var solutionParserMock = new Mock();
33 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
34 | .Returns(new[]
35 | {
36 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "project1.csproj", isFolder: false),
37 | new SolutionProject(projectGuid: "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC", projectPath: "project2.csproj", isFolder: false)
38 | });
39 | var referencesExtractorMock = new Mock();
40 | referencesExtractorMock.Setup(re => re.GetProjectReferencePaths("project1"))
41 | .Returns(new[] {"project2"});
42 | var checker = new SolutionProjectGuidChecker(solutionParserMock.Object);
43 |
44 | var messages = checker.CheckSolutionProjectGuids(@"../../../TestSolutionFiles\README.txt", "9A19103F-16F7-4668-BE54-9A1E7A4F7556", ".csproj");
45 |
46 | Assert.NotEmpty(messages);
47 | }
48 |
49 | [Fact]
50 | public void CheckSolutionProjectGuids_OneProjectIsFolder_AllGood()
51 | {
52 | var solutionParserMock = new Mock();
53 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
54 | .Returns(new[]
55 | {
56 | new SolutionProject(projectGuid: "9A19103F-16F7-4668-BE54-9A1E7A4F7556", projectPath: "project1", isFolder: false),
57 | new SolutionProject(projectGuid: "2150E333-8FDC-42A3-9474-1A3956D46DE8", projectPath: "project2", isFolder: true)
58 | });
59 | var referencesExtractorMock = new Mock();
60 | referencesExtractorMock.Setup(re => re.GetProjectReferencePaths("project1"))
61 | .Returns(new[] {"project2"});
62 | var checker = new SolutionProjectGuidChecker(solutionParserMock.Object);
63 |
64 | var messages = checker.CheckSolutionProjectGuids(@"../../../TestSolutionFiles\README.txt", "9A19103F-16F7-4668-BE54-9A1E7A4F7556", ".csproj");
65 |
66 | Assert.Empty(messages);
67 | }
68 |
69 | [Fact]
70 | public void CheckSolutionProjectGuids_TwoGuidsNotAllowedOnlyOneCsproj_OneComplaint()
71 | {
72 | var solutionParserMock = new Mock();
73 | solutionParserMock.Setup(sp => sp.ExtractSolutionProjects(It.IsAny(), It.IsAny()))
74 | .Returns(new[]
75 | {
76 | new SolutionProject(projectGuid: "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC", projectPath: "project1.vcxproj", isFolder: false),
77 | new SolutionProject(projectGuid: "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC", projectPath: "project2.csproj", isFolder: false)
78 | });
79 | var referencesExtractorMock = new Mock();
80 | referencesExtractorMock.Setup(re => re.GetProjectReferencePaths("project1"))
81 | .Returns(new[] {"project2"});
82 | var checker = new SolutionProjectGuidChecker(solutionParserMock.Object);
83 |
84 | var messages = checker.CheckSolutionProjectGuids(@"../../../TestSolutionFiles\README.txt", "9A19103F-16F7-4668-BE54-9A1E7A4F7556", ".csproj");
85 |
86 | Assert.NotEmpty(messages);
87 | Assert.Contains("project2.csproj", messages);
88 | Assert.DoesNotContain("project1.vcxproj", messages);
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/Dg.Component.Contracts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/Dg.Component.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | ..\Dg.Returns.ruleset
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/Dg.Returns.Contracts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/Dg.Returns.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | ..\Dg.Returns.ruleset
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/Duplicates.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | netstandard2.0,net472
6 | netcoreapp2.1
7 | ..\Dg.Returns.ruleset
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/Empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/README.txt:
--------------------------------------------------------------------------------
1 | Here are just some example project files for unit tests. They are renamed to be XMLs in order
2 | not to confuse the IDE. Please don't judge files by content. They are just examples.
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestProjectFiles/ReferenceWithoutPath.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | ..\Dg.Returns.ruleset
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestSolutionFiles/README.txt:
--------------------------------------------------------------------------------
1 | Here are just some example solution files for unit tests. They are renamed to be TXTs in order
2 | not to confuse the IDE. Please don't judge files by content. They are just examples.
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestSolutionFiles/SmallInvalidTestSolution.txt:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dg.CustomerService.Contracts", "..\TestProjectFiles\Dg.Returns.xml", "{91E2913F-6342-47C9-8EB6-362DCD0466C7}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dg.CustomerService.Monolith", "..\TestProjectFiles\Dg.Component.Contracts.xml", "{23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.ActiveCfg = Release|Any CPU
64 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.Build.0 = Release|Any CPU
65 | EndGlobalSection
66 | EndGlobal
67 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestSolutionFiles/SmallSolution.txt:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "devinite.PortalSystem", "devinite.PortalSystem\devinite.PortalSystem.csproj", "{0071A9DC-8296-4AE7-86D2-03C6E5944177}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dg.CustomerService.Contracts", "CustomerService\Dg.CustomerService.Contracts\Dg.CustomerService.Contracts.csproj", "{91E2913F-6342-47C9-8EB6-362DCD0466C7}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dg.CustomerService.Monolith", "CustomerService\Dg.CustomerService.Monolith\Dg.CustomerService.Monolith.csproj", "{23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}"
8 | EndProject
9 | Global
10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
11 | Debug|Any CPU = Debug|Any CPU
12 | Release|Any CPU = Release|Any CPU
13 | EndGlobalSection
14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
15 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
16 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.Build.0 = Debug|Any CPU
17 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.ActiveCfg = Release|Any CPU
18 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.Build.0 = Release|Any CPU
19 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
53 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
58 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.Build.0 = Release|Any CPU
59 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.ActiveCfg = Release|Any CPU
62 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.Build.0 = Release|Any CPU
63 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.Build.0 = Debug|Any CPU
65 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.Build.0 = Release|Any CPU
67 | EndGlobalSection
68 | EndGlobal
69 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestSolutionFiles/SmallValidTestSolution.txt:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dg.CustomerService.Contracts", "..\TestProjectFiles\Dg.Component.xml", "{91E2913F-6342-47C9-8EB6-362DCD0466C7}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dg.CustomerService.Monolith", "..\TestProjectFiles\Dg.Component.Contracts.xml", "{23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.ActiveCfg = Release|Any CPU
64 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.Build.0 = Release|Any CPU
65 | EndGlobalSection
66 | EndGlobal
67 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/TestSolutionFiles/SolutionWithFolders.txt:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "devinite.PortalSystem", "devinite.PortalSystem\devinite.PortalSystem.csproj", "{0071A9DC-8296-4AE7-86D2-03C6E5944177}"
4 | EndProject
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dg.DgConsolidate", "Dg.DgConsolidate", "{BC6B8BF3-87D9-4FA9-9266-7B1DC1E161B6}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {0071A9DC-8296-4AE7-86D2-03C6E5944177}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {91E2913F-6342-47C9-8EB6-362DCD0466C7}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {23F017C2-EB2F-4A25-B5DD-9D0BF3C9F5B2}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {32FE36D9-F2B7-4D60-ABBA-B5DC8F2E57F1}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {E9C3E322-040A-42E2-8C6B-D5F041601F61}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {B9C51F36-C810-4F70-9F30-F194A5D44498}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {FD52D6BD-BDE1-4433-BD6C-1ADA5DA26927}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {6194BCBE-7688-462C-99CE-D721FBDC56C6}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {A004F595-26AC-4C59-A449-CC5E0800D308}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {2E04C3D9-C06D-4CAE-BA1C-2B79AD19E8A8}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {4C8D4C69-B417-4EA9-A572-26730A2790E5}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {AA04F4A0-40B9-4272-BCC4-B3C141B0251C}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.ActiveCfg = Release|Any CPU
64 | {96AD0FDF-C0D2-4E92-98EE-AE0A2BCC2A42}.Release|Any CPU.Build.0 = Release|Any CPU
65 | EndGlobalSection
66 | EndGlobal
67 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler.Tests/WildcardPatternParserTests.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace ProjectReferencesRuler
4 | {
5 | public class WildcardPatternParserTests
6 | {
7 | [Theory]
8 | [InlineData(@"Dg.*", @"^Dg\..*$")]
9 | [InlineData(@"devinite.*", @"^devinite\..*$")]
10 | [InlineData(@"Dg.*.Contracts", @"^Dg\..*\.Contracts$")]
11 | [InlineData(@"*", @"^.*$")]
12 | [InlineData(@"Dg.*Test*", @"^Dg\..*Test.*$")]
13 | [InlineData(@"Dg.*.Erp", @"^Dg\..*\.Erp$")]
14 | [InlineData(@"Dg.*.Monolith", @"^Dg\..*\.Monolith$")]
15 | [InlineData(@"Dg.*.Website", @"^Dg\..*\.Website$")]
16 | [InlineData(@"Dg.Logistics", @"^Dg\.Logistics$")]
17 | [InlineData(@"Dg.CategoryManagement", @"^Dg\.CategoryManagement$")]
18 | [InlineData(@"Dg.Finance", @"^Dg\.Finance$")]
19 | [InlineData(@"Dg.HumanResources", @"^Dg\.HumanResources$")]
20 | [InlineData(@"Dg.Pdm", @"^Dg\.Pdm$")]
21 | [InlineData(@"Dg.MessageReceivers", @"^Dg\.MessageReceivers$")]
22 | [InlineData(@"Dg.Wms", @"^Dg\.Wms$")]
23 | [InlineData(@"Dg.???", @"^Dg\....$")]
24 | public void GetRegex_GivenWildcardPattern_ReturnsMatchingRegex(string wildcardPattern, string expectedRegex)
25 | {
26 | var parser = new WildcardPatternParser();
27 | Assert.Equal(expectedRegex, parser.GetRegex(wildcardPattern));
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/PatternParsing/IPatternParser.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler
2 | {
3 | public interface IPatternParser
4 | {
5 | string GetRegex(string rawPattern);
6 | }
7 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/PatternParsing/RegexPatternParser.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler
2 | {
3 | public class RegexPatternParser : IPatternParser
4 | {
5 | public string GetRegex(string rawPattern)
6 | {
7 | return rawPattern;
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/PatternParsing/WildcardPatternParser.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace ProjectReferencesRuler
4 | {
5 | public class WildcardPatternParser : IPatternParser
6 | {
7 | public string GetRegex(string plainWildcardPattern)
8 | {
9 | return $"^{Regex.Escape(plainWildcardPattern).Replace("\\*", ".*").Replace("\\?", ".")}$";
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectParsing/CsprojReferencesExtractor.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Xml.Linq;
5 | using ProjectReferencesRuler.Rules.Project;
6 | using ProjectReferencesRuler.Rules.References;
7 |
8 | namespace ProjectReferencesRuler.ProjectParsing
9 | {
10 | public class CsprojReferencesExtractor : IReferencesExtractor
11 | {
12 | // csproj elements
13 | private const string ItemGroup = "ItemGroup";
14 | private const string PropertyGroup = "PropertyGroup";
15 | private const string ProjectReference = "ProjectReference";
16 | private const string PackageReference = "PackageReference";
17 | private const string Include = "Include";
18 | private const string PrivateAssets = "PrivateAssets";
19 | private const string Version = "Version";
20 | private const string TargetFramework = "TargetFramework";
21 | private const string TargetFrameworks = "TargetFrameworks";
22 | private const string Import = "Import";
23 | private const string Project = "Project";
24 |
25 | public IEnumerable GetProjectReferences(string csprojPath)
26 | {
27 | var from = Path.GetFileNameWithoutExtension(csprojPath);
28 | return GetReferences(csprojPath, ProjectReference)
29 | .Where(tuple => !string.IsNullOrEmpty(tuple.Item1))
30 | .Select(tuple => new Reference(
31 | from: from,
32 | to: Path.GetFileNameWithoutExtension(CleanPath(tuple.Item1)),
33 | isPrivateAssetsAllSet: tuple.Item2 == "All",
34 | versionOrNull: null));
35 | }
36 |
37 | public IEnumerable GetPackageReferences(string csprojPath)
38 | {
39 | var from = Path.GetFileNameWithoutExtension(csprojPath);
40 | return GetReferences(csprojPath, PackageReference)
41 | .Select(tuple => new Reference(
42 | from: from,
43 | to: tuple.Item1,
44 | isPrivateAssetsAllSet: tuple.Item2 == "All",
45 | versionOrNull: tuple.Item3));
46 | }
47 |
48 | public IEnumerable GetProjectReferencePaths(string csprojPath)
49 | {
50 | var doc = ParseXml(csprojPath);
51 |
52 | var projectReferenceItemGroup = GetItemGroups(doc, elementName: ProjectReference);
53 |
54 | return projectReferenceItemGroup
55 | .ElementsIgnoreNamespace(ProjectReference)
56 | .Select(GetIncludeContent)
57 | .Where(r => !string.IsNullOrEmpty(r));
58 | }
59 |
60 | private static XDocument ParseXml(string csprojPath)
61 | {
62 | var xmlString = File.ReadAllText(csprojPath);
63 | var doc = XDocument.Parse(xmlString);
64 | return doc;
65 | }
66 |
67 | public Project GetProjectProperties(string csprojPath)
68 | {
69 | var doc = ParseXml(csprojPath);
70 | return new Project(
71 | name: CleanPath(Path.GetFileNameWithoutExtension(csprojPath)),
72 | importedProps: GetImportedProps(doc).Select(CleanPath).ToList(),
73 | targetFrameworks: GetTargetFrameworks(doc).ToList());
74 | }
75 |
76 | ///
77 | /// Replaces \ with / in order for this same code to work on both Windows and Linux.
78 | ///
79 | private static string CleanPath(string path)
80 | {
81 | return path.Replace("\\", "/");
82 | }
83 |
84 | private IEnumerable GetTargetFrameworks(XDocument doc)
85 | {
86 | foreach (var propertyGroup in doc.Root.ElementsIgnoreNamespace(PropertyGroup))
87 | {
88 | foreach (var targetFrameworkElement in propertyGroup.ElementsIgnoreNamespace(TargetFramework))
89 | {
90 | yield return targetFrameworkElement.Value.Trim();
91 | }
92 |
93 | foreach (var targetFrameworks in propertyGroup.ElementsIgnoreNamespace(TargetFrameworks))
94 | {
95 | foreach (var targetFramework in targetFrameworks.Value.Split(','))
96 | {
97 | yield return targetFramework.Trim();
98 | }
99 | }
100 | }
101 | }
102 |
103 | private IEnumerable GetImportedProps(XDocument doc)
104 | {
105 | foreach (var import in doc.Root.ElementsIgnoreNamespace(Import))
106 | {
107 | yield return import.Attribute(Project)?.Value;
108 | }
109 | }
110 |
111 | private IEnumerable<(string, string, string)> GetReferences(string csprojPath, string referenceType)
112 | {
113 | var doc = ParseXml(csprojPath);
114 |
115 | var projectReferenceItemGroup = GetItemGroups(doc, elementName: referenceType);
116 |
117 | return projectReferenceItemGroup
118 | .ElementsIgnoreNamespace(referenceType)
119 | .Select(xel => (GetIncludeContent(xel), GetPrivateAssetsContent(xel), GetVersionOrNull(xel)));
120 | }
121 |
122 | private static IEnumerable GetItemGroups(XDocument doc, string elementName)
123 | {
124 | return doc.Root.ElementsIgnoreNamespace(ItemGroup)
125 | .Where(ig => ig.ElementsIgnoreNamespace(elementName).Any());
126 | }
127 |
128 | private static string GetIncludeContent(XElement pr)
129 | {
130 | return pr.Attribute(Include)?.Value;
131 | }
132 |
133 | private static string GetPrivateAssetsContent(XElement pr)
134 | {
135 | return pr.Attribute(PrivateAssets)?.Value;
136 | }
137 |
138 | private static string GetVersionOrNull(XElement pr)
139 | {
140 | return pr.Attribute(Version)?.Value;
141 | }
142 | }
143 |
144 | internal static class Extensions
145 | {
146 | public static IEnumerable ElementsIgnoreNamespace(this XElement element, string name)
147 | {
148 | return element.Elements()
149 | .Where(e => e.Name.LocalName == name);
150 | }
151 |
152 | public static IEnumerable ElementsIgnoreNamespace(this IEnumerable elements, string name)
153 | {
154 | return elements.Elements()
155 | .Where(e => e.Name.LocalName == name);
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectParsing/IReferencesExtractor.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ProjectReferencesRuler.Rules.Project;
3 | using ProjectReferencesRuler.Rules.References;
4 |
5 | namespace ProjectReferencesRuler.ProjectParsing
6 | {
7 | public interface IReferencesExtractor
8 | {
9 | ///
10 | /// Gets all project references from a csproj file.
11 | ///
12 | /// Absolute path to the project file.
13 | /// Returns all project references file names without path or extension.
14 | IEnumerable GetProjectReferences(string csprojPath);
15 |
16 | ///
17 | /// Gets all project references paths from a csproj file. This is intended for further analysis of the references.
18 | ///
19 | /// Absolute path to the project file.
20 | /// Returns all project references file names with full path and extension.
21 | IEnumerable GetProjectReferencePaths(string csprojPath);
22 |
23 | ///
24 | /// Gets all (nuget) package references from a csproj file.
25 | ///
26 | /// Absolute path to the project file.
27 | /// Returns all package references without versions or other additional data.
28 | IEnumerable GetPackageReferences(string csprojPath);
29 |
30 | ///
31 | /// Returns project properties needed to apply rules.
32 | ///
33 | ///
34 | ///
35 | Project GetProjectProperties(string csprojPath);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectReferencesRuler.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectRunners/IProjectFilesRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace ProjectReferencesRuler.ProjectRunners
5 | {
6 | public interface IProjectFilesRunner
7 | {
8 | string CollectComplaintsForFiles(Func> getComplaints);
9 | }
10 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectRunners/ProjectFilesRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace ProjectReferencesRuler.ProjectRunners
8 | {
9 | public class ProjectFilesRunner : IProjectFilesRunner
10 | {
11 | private readonly string _solutionPath;
12 | private readonly string _filesExtension;
13 | private readonly string _excludedProjectsRegex;
14 |
15 | public ProjectFilesRunner(
16 | string solutionPath,
17 | string filesExtension,
18 | string excludedProjectsRegex = null)
19 | {
20 | _solutionPath = solutionPath;
21 | _filesExtension = filesExtension;
22 | _excludedProjectsRegex = excludedProjectsRegex;
23 | }
24 | public string CollectComplaintsForFiles(Func> getComplaints)
25 | {
26 | var allComplaints = new List();
27 | foreach (var filePath in Directory.GetFiles(_solutionPath, _filesExtension, SearchOption.AllDirectories)
28 | .Where(f => _excludedProjectsRegex is null || !Regex.IsMatch(f, pattern: _excludedProjectsRegex)))
29 | {
30 | allComplaints.AddRange(getComplaints(filePath));
31 | }
32 |
33 | return string.Join("\n", allComplaints.Where(s => s.Length > 0));
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectRunners/ReferencesRulerRunner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using ProjectReferencesRuler.ProjectParsing;
5 | using ProjectReferencesRuler.Rules.References;
6 |
7 | namespace ProjectReferencesRuler.ProjectRunners
8 | {
9 | public class ReferencesRulerRunner
10 | {
11 | private readonly IReferencesExtractor _extractor;
12 | private readonly IReferencesRuler referencesRuler;
13 | private readonly IProjectFilesRunner _filesRunner;
14 |
15 | public ReferencesRulerRunner(
16 | IReferencesExtractor extractor,
17 | IReferencesRuler referencesRuler,
18 | IProjectFilesRunner filesRunner)
19 | {
20 | _extractor = extractor;
21 | this.referencesRuler = referencesRuler;
22 | _filesRunner = filesRunner;
23 | }
24 |
25 | public string GetComplaintsForProjectReferences()
26 | {
27 | return GetComplaintsForReferences(filePath => _extractor.GetProjectReferences(filePath));
28 | }
29 |
30 | public string GetComplaintsForPackageReferences()
31 | {
32 | return GetComplaintsForReferences(filePath => _extractor.GetPackageReferences(filePath));
33 | }
34 |
35 | private string GetComplaintsForReferences(Func> getReferences)
36 | {
37 | return _filesRunner.CollectComplaintsForFiles(fileName => GetReferencesComplaints(getReferences, fileName));
38 | }
39 |
40 | private IEnumerable GetReferencesComplaints(
41 | Func> getReferences,
42 | string filePath)
43 | {
44 | return getReferences(filePath)
45 | .Select(reference => referencesRuler.GiveMeComplaints(reference))
46 | .Where(complaints => !string.IsNullOrEmpty(complaints));
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ProjectsRuler.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ProjectReferencesRuler.ProjectParsing;
3 | using ProjectReferencesRuler.ProjectRunners;
4 | using ProjectReferencesRuler.Rules.References;
5 |
6 | namespace ProjectReferencesRuler
7 | {
8 | public static class ProjectsRuler
9 | {
10 | public static string GetProjectReferencesComplaints(string solutionDir, params ReferenceRule[] rules)
11 | {
12 | return GetProjectReferencesComplaints(solutionDir, null, rules);
13 | }
14 |
15 | public static string GetProjectReferencesComplaints(
16 | string solutionDir,
17 | string excludedProjects = null,
18 | params ReferenceRule[] rules)
19 | {
20 | return GetRunner(solutionDir, excludedProjects, rules).GetComplaintsForProjectReferences();
21 | }
22 |
23 | public static string GetPackageReferencesComplaints(string solutionDir, params ReferenceRule[] rules)
24 | {
25 | return GetPackageReferencesComplaints(solutionDir, null, rules);
26 | }
27 |
28 | public static string GetPackageReferencesComplaints(
29 | string solutionDir,
30 | string excludedProjects = null,
31 | params ReferenceRule[] rules)
32 | {
33 | return GetRunner(solutionDir, excludedProjects, rules).GetComplaintsForPackageReferences();
34 | }
35 |
36 | private static ReferencesRulerRunner GetRunner(
37 | string solutionDir,
38 | string excludedProjectsRegex,
39 | IReadOnlyList rules)
40 | {
41 | return new ReferencesRulerRunner(
42 | extractor: new CsprojReferencesExtractor(),
43 | referencesRuler: new ReferencesRuler(
44 | patternParser: new WildcardPatternParser(),
45 | rules: rules),
46 | filesRunner: new ProjectFilesRunner(
47 | solutionPath: solutionDir,
48 | filesExtension: "*.csproj",
49 | excludedProjectsRegex));
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ReferenceDuplicatesChecker.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ProjectReferencesRuler.ProjectParsing;
3 | using ProjectReferencesRuler.ProjectRunners;
4 | using ProjectReferencesRuler.Rules.References;
5 |
6 | namespace ProjectReferencesRuler
7 | {
8 | public class ReferenceDuplicatesChecker
9 | {
10 | private readonly IReferencesExtractor extractor;
11 | private readonly IProjectFilesRunner filesRunner;
12 |
13 | public ReferenceDuplicatesChecker(
14 | IReferencesExtractor extractor,
15 | IProjectFilesRunner filesRunner)
16 | {
17 | this.extractor = extractor;
18 | this.filesRunner = filesRunner;
19 | }
20 |
21 | public string CheckProjectReferenceDuplicates()
22 | {
23 | return filesRunner.CollectComplaintsForFiles(
24 | fileName => CheckReferencesDuplicates(
25 | extractor.GetProjectReferences(fileName),
26 | fileName,
27 | "ProjectReference"));
28 | }
29 |
30 | public string CheckPackageReferenceDuplicates()
31 | {
32 | return filesRunner.CollectComplaintsForFiles(
33 | fileName => CheckReferencesDuplicates(
34 | extractor.GetPackageReferences(fileName),
35 | fileName,
36 | "PackageReference"));
37 | }
38 |
39 | private IEnumerable CheckReferencesDuplicates(IEnumerable references, string fileName, string referenceType)
40 | {
41 | var projectReferences = new HashSet();
42 | foreach (var projectReference in references)
43 | {
44 | if (projectReferences.Contains(projectReference.To))
45 | {
46 | yield return $"There is a duplicate {referenceType} {projectReference.To} in {fileName}. Please clean this up!";
47 | }
48 | else
49 | {
50 | projectReferences.Add(projectReference.To);
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/ReferencesExistenceChecker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using ProjectReferencesRuler.ProjectParsing;
6 |
7 | namespace ProjectReferencesRuler
8 | {
9 | ///
10 | /// Checks if the project references fit to references in solutions in the repository.
11 | ///
12 | public class ReferencesExistenceChecker
13 | {
14 | private readonly ISolutionParser _solutionParser;
15 | private readonly IReferencesExtractor _referencesExtractor;
16 |
17 | public ReferencesExistenceChecker(ISolutionParser solutionParser, IReferencesExtractor referencesExtractor)
18 | {
19 | _solutionParser = solutionParser;
20 | _referencesExtractor = referencesExtractor;
21 | }
22 |
23 | public IEnumerable CheckProjectReferencesExistenceInSolution(string solutionPath, string projectFileExtension)
24 | {
25 | if (File.GetAttributes(solutionPath).HasFlag(FileAttributes.Directory))
26 | {
27 | throw new ArgumentException($"You passed a folder path {solutionPath} to the {nameof(solutionPath)} parameter. Please pass the solution file path.");
28 | }
29 |
30 | var messages = new List();
31 | var absoluteProjectPaths = new HashSet(_solutionParser.ExtractSolutionProjects(solutionPath, projectFileExtension).Select(sp => Path.GetFullPath(sp.ProjectPath)));
32 | foreach (var projectPath in absoluteProjectPaths)
33 | {
34 | var projectDir = Path.GetDirectoryName(projectPath);
35 | var absoluteReferencePaths = _referencesExtractor.GetProjectReferencePaths(projectPath).Select(p => Path.GetFullPath(Path.Combine(projectDir, p)));
36 | foreach (var referencePath in absoluteReferencePaths)
37 | {
38 | if (!absoluteProjectPaths.Contains(referencePath))
39 | {
40 | var shouldBeTowards = absoluteProjectPaths.SingleOrDefault(pp => pp.EndsWith(Path.GetFileName(referencePath)));
41 | var message = $"Project {Path.GetFileName(projectPath)} has a broken reference to {referencePath}.";
42 | if (string.IsNullOrEmpty(shouldBeTowards))
43 | {
44 | message += $" This reference is completely missing from the solution {Path.GetFileName(solutionPath)}";
45 | }
46 | else
47 | {
48 | message += $" The reference should be towards {shouldBeTowards}";
49 | }
50 | messages.Add(message);
51 | }
52 | }
53 | }
54 |
55 | return messages;
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/Project/Project.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ProjectReferencesRuler.Rules.Project
4 | {
5 | public class Project
6 | {
7 | public readonly string Name;
8 | public readonly IReadOnlyList ImportedProps;
9 | public readonly IReadOnlyList TargetFrameworks;
10 |
11 | // This might become a thing, but for now, let's leave that away.
12 | // private readonly IReadOnlyList projectReferences;
13 | // private readonly IReadOnlyList packageReferences;
14 |
15 | public Project(string name, IReadOnlyList importedProps, IReadOnlyList targetFrameworks)
16 | {
17 | Name = name;
18 | ImportedProps = importedProps;
19 | TargetFrameworks = targetFrameworks;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/References/IReferencesRuler.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler.Rules.References
2 | {
3 | public interface IReferencesRuler
4 | {
5 | string GiveMeComplaints(Reference reference);
6 | }
7 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/References/Reference.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler.Rules.References
2 | {
3 | public class Reference
4 | {
5 | public readonly string From;
6 | public readonly string To;
7 | public readonly bool IsPrivateAssetsAllSet;
8 | public readonly string VersionOrNull;
9 |
10 | public Reference(
11 | string @from,
12 | string to,
13 | bool isPrivateAssetsAllSet,
14 | string versionOrNull)
15 | {
16 | From = @from;
17 | To = to;
18 | IsPrivateAssetsAllSet = isPrivateAssetsAllSet;
19 | VersionOrNull = versionOrNull;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/References/ReferenceRule.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler.Rules.References
2 | {
3 | public class ReferenceRule
4 | {
5 | public readonly string PatternFrom;
6 | public readonly string PatternTo;
7 | public readonly RuleKind Kind;
8 | public readonly string Description;
9 | public readonly bool? IsPrivateAssetsAllSet;
10 | public readonly string Version;
11 |
12 | public ReferenceRule(string patternFrom, string patternTo, RuleKind kind, string description, bool? isPrivateAssetsAllSet = null, string version = null)
13 | {
14 | PatternFrom = patternFrom;
15 | PatternTo = patternTo;
16 | Kind = kind;
17 | Description = description;
18 | IsPrivateAssetsAllSet = isPrivateAssetsAllSet;
19 | Version = version;
20 | }
21 |
22 | public static ReferenceRuleBuilder For(string patternFrom)
23 | {
24 | return new ReferenceRuleBuilder(patternFrom);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/References/ReferenceRuleBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ProjectReferencesRuler.Rules.References
4 | {
5 | public class ReferenceRuleBuilder
6 | {
7 | private readonly string _patternFrom;
8 | private string _patternTo;
9 | private RuleKind? _kind;
10 | private string _description;
11 | private bool? _isPrivateAssetsAllSet;
12 | private string _version;
13 |
14 | internal ReferenceRuleBuilder(string patternFrom)
15 | {
16 | _patternFrom = patternFrom;
17 | }
18 |
19 | public ReferenceRuleBuilder Referencing(
20 | string patternTo,
21 | string version = null,
22 | bool? withPrivateAssetsAll = null)
23 | {
24 | _patternTo = patternTo;
25 | _version = version;
26 | _isPrivateAssetsAllSet = withPrivateAssetsAll;
27 | return this;
28 | }
29 |
30 | private ReferenceRuleBuilder Is(RuleKind kind)
31 | {
32 | _kind = kind;
33 | return this;
34 | }
35 |
36 | public ReferenceRuleBuilder IsAllowed() => Is(RuleKind.Allowed);
37 | public ReferenceRuleBuilder IsForbidden() => Is(RuleKind.Forbidden);
38 | public ReferenceRuleBuilder IsExplicitlyForbidden() => Is(RuleKind.ExplicitlyForbidden);
39 |
40 | public ReferenceRuleBuilder Because(string description)
41 | {
42 | _description = description;
43 | return this;
44 | }
45 |
46 | public ReferenceRule BuildRule()
47 | {
48 | return new ReferenceRule(
49 | patternFrom: _patternFrom,
50 | patternTo: _patternTo,
51 | kind: _kind ?? throw new InvalidOperationException($"Please specify a {nameof(ReferenceRule.Kind)} using e.g. {nameof(IsForbidden)}"),
52 | description: _description,
53 | isPrivateAssetsAllSet: _isPrivateAssetsAllSet,
54 | version: _version);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/References/ReferencesRuler.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text.RegularExpressions;
4 |
5 | namespace ProjectReferencesRuler.Rules.References
6 | {
7 | ///
8 | /// Iterates through all the project references and challenges them against the given set of rules. If a rule is
9 | /// broken it generates a complaint. There can be forbidding and allowing rules. The forbidding rules are the main
10 | /// ones whereas the allowing ones are exceptions from the main rules and can override them.
11 | ///
12 | public class ReferencesRuler : IReferencesRuler
13 | {
14 | private readonly IPatternParser _patternParser;
15 | private readonly IReadOnlyList _forbiddingRules;
16 | private readonly IReadOnlyList _allowingRules;
17 | private readonly IReadOnlyList _explicitlyForbiddenRules;
18 |
19 | public ReferencesRuler(IPatternParser patternParser, IReadOnlyList rules)
20 | {
21 | _patternParser = patternParser;
22 | _forbiddingRules = rules.Where(r => r.Kind == RuleKind.Forbidden).ToList();
23 | _allowingRules = rules.Where(r => r.Kind == RuleKind.Allowed).ToList();
24 | _explicitlyForbiddenRules = rules.Where(r => r.Kind == RuleKind.ExplicitlyForbidden).ToList();
25 | }
26 |
27 | public string GiveMeComplaints(Reference reference)
28 | {
29 | var explicitlyForbiddenRules = GetExplicitlyForbiddenRules(reference).ToList();
30 |
31 | var generallyForbiddenRules = GetGenerallyForbiddenRules(reference);
32 | var areGeneralRolesOverriddenByExplicitlyAllowedRule = GetExplicitlyAllowedRules(reference).Any();
33 | if (!explicitlyForbiddenRules.Any() && areGeneralRolesOverriddenByExplicitlyAllowedRule)
34 | {
35 | return string.Empty;
36 | }
37 |
38 | var violatedRules = areGeneralRolesOverriddenByExplicitlyAllowedRule
39 | ? explicitlyForbiddenRules
40 | : explicitlyForbiddenRules.Union(generallyForbiddenRules);
41 |
42 | var complaints = string.Join("\n", violatedRules.Select(r => r.Description));
43 | if (string.IsNullOrEmpty(complaints))
44 | {
45 | return string.Empty;
46 | }
47 |
48 | return $"Reference from {reference.From} to {reference.To} broke:\n{complaints}";
49 | }
50 |
51 | private IEnumerable GetGenerallyForbiddenRules(Reference reference)
52 | {
53 | return GetMatchingRules(rules: _forbiddingRules, reference: reference, kind: RuleKind.Forbidden);
54 | }
55 |
56 | private IEnumerable GetExplicitlyAllowedRules(Reference reference)
57 | {
58 | return GetMatchingRules(rules: _allowingRules, reference: reference, kind: RuleKind.Allowed);
59 | }
60 |
61 | private IEnumerable GetExplicitlyForbiddenRules(Reference reference)
62 | {
63 | return GetMatchingRules(rules: _explicitlyForbiddenRules, reference: reference, kind: RuleKind.ExplicitlyForbidden);
64 | }
65 |
66 | private IEnumerable GetMatchingRules(IReadOnlyList rules, Reference reference, RuleKind kind)
67 | {
68 | foreach (var rule in rules)
69 | {
70 | if (Regex.IsMatch(reference.From, _patternParser.GetRegex(rule.PatternFrom))
71 | && Regex.IsMatch(reference.To, _patternParser.GetRegex(rule.PatternTo))
72 | && DoesPrivateAssetsRuleMatch(reference, rule)
73 | && DoesVersionRuleMatch(reference, rule)
74 | && rule.Kind == kind)
75 | {
76 | yield return rule;
77 | }
78 | }
79 | }
80 |
81 | private static bool DoesPrivateAssetsRuleMatch(Reference reference,
82 | ReferenceRule rule)
83 | {
84 | // if the value is not set, it is neutral -> true
85 | if (!rule.IsPrivateAssetsAllSet.HasValue)
86 | {
87 | return true;
88 | }
89 |
90 | return rule.IsPrivateAssetsAllSet.Value == reference.IsPrivateAssetsAllSet;
91 | }
92 |
93 | private static bool DoesVersionRuleMatch(Reference reference,
94 | ReferenceRule rule)
95 | {
96 | // if the value is not set, it is neutral -> true
97 | if (rule.Version == null)
98 | {
99 | return true;
100 | }
101 |
102 | return rule.Version == reference.VersionOrNull;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/ProjectReferencesRuler/Rules/RuleKind.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler.Rules
2 | {
3 | public enum RuleKind
4 | {
5 | Forbidden = 1,
6 | Allowed = 2,
7 | // Obsolete = 3,
8 | // Warning = 4,
9 | ExplicitlyForbidden = 5
10 | }
11 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/SolutionParsing/ISolutionParser.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace ProjectReferencesRuler
4 | {
5 | public interface ISolutionParser
6 | {
7 | ///
8 | /// Extracts solution projects with relative paths from the solution file.
9 | ///
10 | IEnumerable ExtractSolutionProjects(string solutionPath, string projectFileExtension);
11 | }
12 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/SolutionParsing/SolutionParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace ProjectReferencesRuler.SolutionParsing
6 | {
7 | public class SolutionParser : ISolutionParser
8 | {
9 | public IEnumerable ExtractSolutionProjects(string solutionPath, string projectFileExtension)
10 | {
11 | var solutionDir = Path.GetDirectoryName(CleanPath(solutionPath));
12 | foreach (var line in File.ReadLines(CleanPath(solutionPath)))
13 | {
14 | if (line.StartsWith("Project"))
15 | {
16 | var projectPath = ParseProjectPath(line);
17 | var projectGuid = CleanPath(ParseProjectGuid(line));
18 | if (projectPath != null && projectPath.EndsWith(projectFileExtension, StringComparison.InvariantCultureIgnoreCase))
19 | {
20 | yield return new SolutionProject(
21 | projectGuid: projectGuid,
22 | projectPath: CleanPath(Path.Combine(solutionDir, projectPath)),
23 | isFolder: projectGuid == "2150E333-8FDC-42A3-9474-1A3956D46DE8");
24 | }
25 | }
26 | }
27 | }
28 |
29 | private string ParseProjectGuid(string line)
30 | {
31 | return line.Substring(10, 36);
32 | }
33 |
34 | ///
35 | /// Parses the solution file line with the project.
36 | ///
37 | /// Line in format Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dg.TestsWithDb", "Dg.TestsWithDb\Dg.TestsWithDb.csproj", "{AB999FEB-AB60-4857-840A-4E0FEB9F145D}"
38 | ///
39 | private string ParseProjectPath(string line)
40 | {
41 | // the first 53 characters are always fix in the format Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") =
42 | var projectContent = line.Substring(53);
43 |
44 | // takes the middle part of "Dg.TestsWithDb", "Dg.TestsWithDb\Dg.TestsWithDb.csproj", "{AB999FEB-AB60-4857-840A-4E0FEB9F145D}"
45 | var pathInParenthesis = projectContent.Split(',')[1];
46 |
47 | // removes the parenthesis
48 | return pathInParenthesis.Trim().Substring(1, pathInParenthesis.Length - 3);
49 | }
50 |
51 | ///
52 | /// Replaces \ with / in order for this same code to work on both Windows and Linux.
53 | ///
54 | private static string CleanPath(string path)
55 | {
56 | return path.Replace("\\", "/");
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/SolutionParsing/SolutionProject.cs:
--------------------------------------------------------------------------------
1 | namespace ProjectReferencesRuler
2 | {
3 | public class SolutionProject
4 | {
5 | public readonly string ProjectGuid;
6 | public readonly string ProjectPath;
7 | public readonly bool IsFolder;
8 |
9 | public SolutionProject(
10 | string projectGuid,
11 | string projectPath,
12 | bool isFolder)
13 | {
14 | ProjectGuid = projectGuid;
15 | ProjectPath = projectPath;
16 | IsFolder = isFolder;
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/ProjectReferencesRuler/SolutionProjectGuidChecker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace ProjectReferencesRuler
5 | {
6 | public class SolutionProjectGuidChecker
7 | {
8 | private readonly ISolutionParser solutionParser;
9 |
10 | public SolutionProjectGuidChecker(ISolutionParser solutionParser)
11 | {
12 | this.solutionParser = solutionParser;
13 | }
14 |
15 | public string CheckSolutionProjectGuids(
16 | string solutionPath,
17 | string allowedProjectGuid,
18 | string checkProjectFilesExtensions)
19 | {
20 | var checkLog = new StringBuilder();
21 | foreach (var solutionProject in solutionParser.ExtractSolutionProjects(solutionPath, projectFileExtension: ""))
22 | {
23 | if (!solutionProject.IsFolder && solutionProject.ProjectGuid != allowedProjectGuid && solutionProject.ProjectPath.Contains(checkProjectFilesExtensions))
24 | {
25 | checkLog.AppendLine(
26 | $"Solution project {solutionProject.ProjectPath} has the project GUID {solutionProject.ProjectGuid}. It should be {allowedProjectGuid}!");
27 | }
28 | }
29 |
30 | var complaints = checkLog.ToString();
31 | if (!string.IsNullOrEmpty(complaints))
32 | {
33 | complaints = $"Complaints for the solution: {solutionPath}" + Environment.NewLine + complaints;
34 | }
35 |
36 | return complaints;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/ProjectsRuler.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28124.53
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectReferencesRuler", "ProjectReferencesRuler\ProjectReferencesRuler.csproj", "{762A45C8-DC12-4DA7-8612-CEF0E7CD3364}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectReferencesRuler.Tests", "ProjectReferencesRuler.Tests\ProjectReferencesRuler.Tests.csproj", "{3B667E27-D904-4614-A325-DFB2F2FCAD60}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {762A45C8-DC12-4DA7-8612-CEF0E7CD3364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {762A45C8-DC12-4DA7-8612-CEF0E7CD3364}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {762A45C8-DC12-4DA7-8612-CEF0E7CD3364}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {762A45C8-DC12-4DA7-8612-CEF0E7CD3364}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {3B667E27-D904-4614-A325-DFB2F2FCAD60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {3B667E27-D904-4614-A325-DFB2F2FCAD60}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {3B667E27-D904-4614-A325-DFB2F2FCAD60}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {3B667E27-D904-4614-A325-DFB2F2FCAD60}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {664728BB-6FE1-40A8-8E6B-493EF115C0C1}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProjectsRuler
2 |
3 | 
4 | 
5 |
6 | # Introduction
7 |
8 | ReferencesRuler is a set of tools for the governance in a .NET solution. It is intended to run as an unit test since it parses project and solution files locally. It undestands the .NET Core csproj format and covers the following checks:
9 |
10 | 1. ProjectReferences including *PrivateAssets="All"* check.
11 | 2. PackageReferences including *PrivateAssets="All"* and *Version* check.
12 | 3. ProjectReferences existence check: checks if a project reference is still a valid project in the solution.
13 | 4. Project GUID in the Solution file. It should always be *"9A19103F-16F7-4668-BE54-9A1E7A4F7556"* and not *"FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"*.
14 | 5. Project and PackageReferences duplicates.
15 |
16 | The ReferencesRuler is highly modular and extensible. You can use its components as you like. For example: you can use either the WildcardPatternParser or RegexPatternParser. You can write your own references extractor and use it (inject) instead of CsprojReferencesExtractor - if you want to support some other language, framework, framework version etc.
17 |
18 | The main limitation: **It relies on having the sourcecode while running the tests!**
19 |
20 | # Getting Started
21 |
22 | 1. Get the nuget package [ReferencesRuler](https://www.nuget.org/packages/ReferencesRuler/)
23 | 2. [Create a unit test](#Enforcing-rules)
24 | 3. Enjoy your stable architecture :)
25 |
26 | # Rules
27 |
28 | The rules are declarative and can be used for either project or package references. There are:
29 |
30 | 1. **Forbidden** - main rule
31 | 2. **Allowed** - exception from the main rule
32 | 3. **ExplicitlyForbidden** - override of the exception which cannot be overriden.
33 |
34 | The rules are declared in that particular order, because each kind of rule is stronger than the previous one. The obligatory fields in each rule are *from*, *to*, *kind* and *description*. There are also two optional fields:
35 |
36 | * **isPrivateAssetsAllSet** - can be used for either project or package references. It checks if the attribute *PrivateAssets="All"* is set.
37 | * **version** - makes sense only with package references. If set with project references the ruler can deliver unexpected results.
38 |
39 | ## Rules examples
40 |
41 | * A typical project/package reference rule:
42 | `new ReferenceRule(@"*B*", @"*A*", RuleKind.Forbidden, description: "B Projects must not reference A, they must use the new Framework Projects")`
43 | * *PrivateAssets="All"* rule:
44 | `new ReferenceRule(@"*", @"*", RuleKind.Forbidden, description: "All references have to have PrivateAssets set to All.")`
45 | * Version rule - only checks for the exact version:
46 | `new ReferenceRule(@"*", @"SomeNugetPackage", RuleKind.Forbidden, description: "Package version 1.2.3 is forbidden.", version="1.2.3")`
47 |
48 | ## Fluent builder
49 |
50 | Rules can also be creating using the fluent builder API:
51 |
52 | ```C#
53 | var rule = ReferenceRule.For("Project.A")
54 | .Referencing("Project.B")
55 | .IsForbidden()
56 | .Because("A should not reference B")
57 | .BuildRule();
58 | ```
59 |
60 | # Enforcing rules
61 |
62 | In order to enforce rules, the ReferencesRuler is used. There are two separate methods for project and for package references. It is highly modular and extensible. You can use parsers and runners that suits your use case the best. Here is the typical .NET use case.
63 |
64 | ```C#
65 | [Test]
66 | public void ItIsNotAllowedToReferenceProjectAFromProjectB()
67 | {
68 | AssertReferenceRules(
69 | // The rules
70 | new ReferenceRule(
71 | patternFrom: @"*B*",
72 | patternTo: @"*A*",
73 | RuleKind.Forbidden,
74 | description: "B Projects must not reference A, they must use the new Framework Projects")
75 | );
76 | }
77 |
78 | private void AssertReferenceRules(params ReferenceRule[] rules)
79 | {
80 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
81 | var solutionDir = Path.Combine(dir, @"..\..\..\");
82 |
83 | var complaints = ProjectsRuler.GetProjectReferencesComplaints(solutionDir, rules);
84 | // or var complaints = ProjectsRuler.GetPackageReferencesComplaints(solutionDir, rules);
85 |
86 | Assert.IsEmpty(complaints);
87 | }
88 | ```
89 |
90 | # Project references exitence check
91 |
92 | There is a dedicated checker for that. It uses the same csproj parser as all the other tools in the ruler: **CsprojReferencesExtractor**.
93 |
94 | ```C#
95 | [Test]
96 | public void CheckForBrokenReferences()
97 | {
98 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
99 | var solutionDir = Path.Combine(dir, @"..\..\..\");
100 | var solutionFilePath = Path.Combine(solutionDir, "MySolution.sln");
101 | var checker = new ReferencesExistenceChecker(
102 | new SolutionParser(),
103 | new CsprojReferencesExtractor());
104 |
105 | var messages = checker.CheckProjectReferencesExistenceInSolution(solutionFilePath, "*.csproj").ToList;
106 |
107 | if (messages.Any())
108 | {
109 | Assert.Fail($"Check for broken references failed. See messages:\n{string.Join("\n", messages)}");
110 | }
111 | }
112 | ```
113 |
114 | # Solution file validity check
115 |
116 | There is a change in the solution file format since the .net core. This code snippet checks all the solution files in the repository rood directory. It ignores folders. It only checks the projects with the given file extension.
117 |
118 | ```C#
119 | [Test]
120 | public void CheckIfSolutionHasAllValidProjectGuids()
121 | {
122 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
123 | var solutionDir = Path.Combine(dir, @"..\..\..\");
124 |
125 | var projectGuidChecker = new SolutionProjectGuidChecker(new SolutionParser());
126 | var complaints = new ProjectFilesRunner(solutionDir, "*.sln")
127 | .CollectComplaintsForFiles(
128 | filePath => new[]
129 | {
130 | projectGuidChecker.CheckSolutionProjectGuids(
131 | filePath,
132 | "9A19103F-16F7-4668-BE54-9A1E7A4F7556",
133 | ".csproj")
134 | });
135 |
136 | Assert.IsEmpty(complaints);
137 | }
138 | ```
139 |
140 | # Advanced extensibility
141 |
142 | In case of a scenario that some of the ruler components would need to be replaced, it can be easily injected and the whole setup can be done like this:
143 |
144 | ```C#
145 | var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
146 | var solutionDir = Path.Combine(dir, @"..\..\..\");
147 |
148 | var runner = new ReferencesRulerRunner(
149 | extractor: new CsprojReferencesExtractor(),
150 | referencesRuler: new ReferencesRuler(
151 | patternParser: new WildcardPatternParser(),
152 | rules: rules),
153 | filesRunner: new ProjectFilesRunner(
154 | solutionPath: solutionDir,
155 | filesExtension: "*.csproj"));
156 |
157 | var complaints = runner.GetPackageReferencesComplaints();
158 | ```
159 |
160 | Remark: this is identical setup as in the ProjectsRuler static class. Do this only if your setup is different in any way. E.g. you want to use the Regex instead of the Wildcard patterns for the rules. ¯\\_(ツ)_/¯
161 |
162 | # Build and Test
163 |
164 | Please keep this project .NET Standard 2.0 so as many people as possible can use this tool.
165 |
166 | # Contribute
167 |
168 | 1. Before starting please [create an issue first](https://github.com/DigitecGalaxus/ProjectsRuler/issues). That way we can discuss the feature before implementing it.
169 | 2. Create a pull request.
170 | 3. After creating a release, the new version will be available on the [nuget.org package page](https://www.nuget.org/packages/ReferencesRuler/).
171 |
--------------------------------------------------------------------------------
/ReferencesRuler.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ReferencesRuler
5 | Project references ruler
6 | 1.1.8
7 | Boško Stupar
8 | bosko.stupar, DigitecGalaxus
9 | MIT
10 | https://github.com/DigitecGalaxus/ProjectsRuler
11 | images\logo.png
12 | false
13 | Governance library for writing unit tests to check for forbidden .csproj references.
14 | Governance library for writing unit tests to check for forbidden .csproj references.
15 | It allows the developer to write declarative rules to steer the architecture within the same application.
16 | It relies on having the source code around when running unit tests.
17 | en-US
18 |
19 | governance architecture dotnet dotnetcore unittest rules references digitec galaxus digitecgalaxus
20 | 2019-2020 Digitec Galaxus AG
21 | Use newer (correct) logo
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/THIRD-PARTY-NOTICES.txt:
--------------------------------------------------------------------------------
1 | https://github.com/moq/moq
2 |
3 | MIT License
4 |
5 | Copyright (c) 2017 Daniel Cazzulino and Contributors
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
25 | -------------------------------------
26 |
27 | https://github.com/xunit/xunit
28 |
29 | Unless otherwise noted, the source code here is covered by the following license:
30 |
31 | Copyright (c) .NET Foundation and Contributors
32 | All Rights Reserved
33 |
34 | Licensed under the Apache License, Version 2.0 (the "License");
35 | you may not use this file except in compliance with the License.
36 | You may obtain a copy of the License at
37 |
38 | http://www.apache.org/licenses/LICENSE-2.0
39 |
40 | Unless required by applicable law or agreed to in writing, software
41 | distributed under the License is distributed on an "AS IS" BASIS,
42 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43 | See the License for the specific language governing permissions and
44 | limitations under the License.
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DigitecGalaxus/ProjectsRuler/b26fa829f0a7fe8d6dff2ade57aa5571e8163417/logo.png
--------------------------------------------------------------------------------