Repository: realvizu/NsDepCop Branch: master Commit: 155a9b5b7411 Files: 297 Total size: 468.7 KB Directory structure: gitextract_m8768ds6/ ├── .gitignore ├── CHANGELOG.md ├── CLAUDE.md ├── Contribute.md ├── LICENSE ├── README.md ├── appveyor.yml ├── doc/ │ ├── DependencyControl.md │ ├── Diagnostics.md │ ├── Help.md │ └── Troubleshooting.md ├── images/ │ ├── docs/ │ │ ├── Figures.vsdx │ │ └── NsDepCop_Example_w600.xcf │ └── icons/ │ ├── NsDepCop_128.xcf │ ├── NsDepCop_128_flat.xcf │ ├── NsDepCop_256.xcf │ ├── NsDepCop_40.xcf │ ├── NsDepCop_48.xcf │ ├── NsDepCop_ItemTemplate_40.xcf │ ├── SetupBackground.xcf │ └── SetupTopBanner.xcf └── source/ ├── .editorconfig ├── Directory.Build.targets ├── NsDepCop.Analyzer/ │ ├── Analysis/ │ │ ├── AssemblyDependency.cs │ │ ├── Factory/ │ │ │ ├── AssemblyDependencyAnalyzerFactory.cs │ │ │ └── DependencyAnalyzerFactory.cs │ │ ├── IAssemblyDependencyAnalyzer.cs │ │ ├── IAssemblyDependencyAnalyzerFactory.cs │ │ ├── IDependencyAnalyzer.cs │ │ ├── IDependencyAnalyzerFactory.cs │ │ ├── ITypeDependencyEnumerator.cs │ │ ├── Implementation/ │ │ │ ├── AssemblyDependencyAnalyzer.cs │ │ │ ├── AssemblyDependencyValidator.cs │ │ │ ├── CachingTypeDependencyValidator.cs │ │ │ ├── DependencyAnalyzer.cs │ │ │ ├── DependencyStatus.cs │ │ │ ├── IAssemblyDependencyValidator.cs │ │ │ ├── ITypeDependencyValidator.cs │ │ │ ├── IllegalTypeDependency.cs │ │ │ └── TypeDependencyValidator.cs │ │ ├── Messages/ │ │ │ ├── AnalyzerMessageBase.cs │ │ │ ├── ConfigDisabledMessage.cs │ │ │ ├── ConfigErrorMessage.cs │ │ │ ├── IllegalAssemblyDependencyMessage.cs │ │ │ ├── IllegalDependencyMessage.cs │ │ │ ├── NoConfigFileMessage.cs │ │ │ └── ToolDisabledMessage.cs │ │ ├── SourceSegment.cs │ │ └── TypeDependency.cs │ ├── Config/ │ │ ├── AnalyzerConfigState.cs │ │ ├── ConfigDefaults.cs │ │ ├── DependencyRule.cs │ │ ├── Domain.cs │ │ ├── DomainSpecification.cs │ │ ├── DomainSpecificationParser.cs │ │ ├── Factory/ │ │ │ └── ConfigProviderFactory.cs │ │ ├── IAnalyzerConfig.cs │ │ ├── IConfigProvider.cs │ │ ├── IConfigProviderFactory.cs │ │ ├── IDependencyRules.cs │ │ ├── IUpdateableConfigProvider.cs │ │ ├── Implementation/ │ │ │ ├── AnalyzerConfig.cs │ │ │ ├── AnalyzerConfigBuilder.cs │ │ │ ├── ConfigLoadResult.cs │ │ │ ├── ConfigProviderBase.cs │ │ │ ├── FileConfigProviderBase.cs │ │ │ ├── MultiLevelXmlFileConfigProvider.cs │ │ │ ├── RuleConfigToStringsFormatter.cs │ │ │ ├── XmlConfigParser.cs │ │ │ └── XmlFileConfigProvider.cs │ │ ├── RegexCompilationMode.cs │ │ ├── RegexDomain.cs │ │ ├── RegexUsageMode.cs │ │ ├── TypeNameSet.cs │ │ └── WildcardDomain.cs │ ├── GlobalSettings.cs │ ├── NsDepCop.Analyzer.csproj │ ├── ParserAdapter/ │ │ └── Roslyn/ │ │ ├── ISyntaxNodeAnalyzer.cs │ │ ├── SyntaxNodeAnalyzer.cs │ │ ├── SyntaxNodeExtensions.cs │ │ ├── TypeDependencyEnumerator.cs │ │ └── TypeDependencyEnumeratorSyntaxVisitor.cs │ ├── ProductConstants.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── RoslynAnalyzer/ │ │ ├── AnalyzerProvider.cs │ │ ├── DiagnosticDefinitions.cs │ │ ├── IAnalyzerProvider.cs │ │ └── NsDepCopAnalyzer.cs │ ├── Util/ │ │ ├── ConcurrentDictionaryExtensions.cs │ │ ├── DictionaryExtensions.cs │ │ ├── EnumerableExtensions.cs │ │ ├── ICacheStatistics.cs │ │ ├── IDateTimeProvider.cs │ │ ├── IDiagnosticSupport.cs │ │ ├── IndentHelper.cs │ │ ├── LinqExtensions.cs │ │ ├── MathHelper.cs │ │ └── MessageHandler.cs │ └── config.nsdepcop ├── NsDepCop.Benchmarks/ │ ├── NsDepCop.Benchmarks.RuleTypesBenchmarks-report-github.md │ ├── NsDepCop.Benchmarks.csproj │ ├── Program.cs │ ├── RuleTypesBenchmarks.cs │ └── readme.txt ├── NsDepCop.ConfigSchema/ │ ├── NsDepCopCatalog.xml │ └── NsDepCopConfig.xsd ├── NsDepCop.NuGet/ │ ├── NsDepCop.NuGet.csproj │ └── tools/ │ ├── install.ps1 │ └── uninstall.ps1 ├── NsDepCop.SourceTest/ │ ├── AnalyzerFeatureTests.cs │ ├── AnalyzerFeature_AllowedDependency/ │ │ ├── AnalyzerFeature_AllowedDependency.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_ChildCanDependOnParentImplicitly/ │ │ ├── AnalyzerFeature_ChildCanDependOnParentImplicitly.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_DisallowedDependency/ │ │ ├── AnalyzerFeature_DisallowedDependency.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_ExcludedFiles/ │ │ ├── AnalyzerFeature_ExcludedFiles.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_ExcludedFiles_WithWildcard/ │ │ ├── AnalyzerFeature_ExcludedFiles_WithWildcard.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_ParentCanDependOnChildImplicitly/ │ │ ├── AnalyzerFeature_ParentCanDependOnChildImplicitly.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_SameNamespaceAllowedEvenWhenVisibleMembersDefined/ │ │ ├── AnalyzerFeature_SameNamespaceAllowedEvenWhenVisibleMembersDefined.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_SameNamespaceAlwaysAllowed/ │ │ ├── AnalyzerFeature_SameNamespaceAlwaysAllowed.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_VisibleMembersOfAllowedRule/ │ │ ├── AnalyzerFeature_VisibleMembersOfAllowedRule.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_VisibleMembersOfNamespace/ │ │ ├── AnalyzerFeature_VisibleMembersOfNamespace.cs │ │ └── config.nsdepcop │ ├── AnalyzerFeature_WithTopLevelStatement/ │ │ ├── AnalyzerFeature_WithTopLevelStatement.cs │ │ └── config.nsdepcop │ ├── Cs6Tests.cs │ ├── Cs6_AliasQualifiedName/ │ │ ├── Cs6_AliasQualifiedName.cs │ │ └── config.nsdepcop │ ├── Cs6_ArrayType/ │ │ ├── Cs6_ArrayType.cs │ │ └── config.nsdepcop │ ├── Cs6_Attributes/ │ │ ├── Cs6_Attributes.cs │ │ └── config.nsdepcop │ ├── Cs6_Delegates/ │ │ ├── Cs6_Delegates.cs │ │ └── config.nsdepcop │ ├── Cs6_ElementAccess/ │ │ ├── Cs6_ElementAccess.cs │ │ └── config.nsdepcop │ ├── Cs6_EveryUserDefinedTypeKind/ │ │ ├── Cs6_EveryUserDefinedTypeKind.cs │ │ └── config.nsdepcop │ ├── Cs6_ExtensionMethodInvocation/ │ │ ├── Cs6_ExtensionMethodInvocation.cs │ │ └── config.nsdepcop │ ├── Cs6_GenericName/ │ │ ├── Cs6_GenericName.cs │ │ └── config.nsdepcop │ ├── Cs6_GenericTypeArgument/ │ │ ├── Cs6_GenericTypeArgument.cs │ │ └── config.nsdepcop │ ├── Cs6_InvocationExpression/ │ │ ├── Cs6_InvocationExpression.cs │ │ └── config.nsdepcop │ ├── Cs6_InvocationWithTypeArg/ │ │ ├── Cs6_InvocationWithTypeArg.cs │ │ └── config.nsdepcop │ ├── Cs6_MemberAccessExpression/ │ │ ├── Cs6_MemberAccessExpression.cs │ │ └── config.nsdepcop │ ├── Cs6_NestedType/ │ │ ├── Cs6_NestedType.cs │ │ └── config.nsdepcop │ ├── Cs6_NullableType/ │ │ ├── Cs6_NullableType.cs │ │ └── config.nsdepcop │ ├── Cs6_ObjectCreationExpression/ │ │ ├── Cs6_ObjectCreationExpression.cs │ │ └── config.nsdepcop │ ├── Cs6_PointerType/ │ │ ├── Cs6_PointerType.cs │ │ └── config.nsdepcop │ ├── Cs6_QualifiedName/ │ │ ├── Cs6_QualifiedName.cs │ │ └── config.nsdepcop │ ├── Cs6_StaticImport/ │ │ ├── Cs6_StaticImport.cs │ │ └── config.nsdepcop │ ├── Cs6_Var/ │ │ ├── Cs6_Var.cs │ │ └── config.nsdepcop │ ├── Cs6_VarWithConstructedGenericType/ │ │ ├── Cs6_VarWithConstructedGenericType.cs │ │ └── config.nsdepcop │ ├── Cs6_VeryComplexType/ │ │ ├── Cs6_VeryComplexType.cs │ │ └── config.nsdepcop │ ├── Cs7Tests.cs │ ├── Cs7_1_DefaultLiteral/ │ │ ├── Cs7_1_DefaultLiteral.cs │ │ └── config.nsdepcop │ ├── Cs7_1_InferredTupleNames/ │ │ ├── Cs7_1_InferredTupleNames.cs │ │ └── config.nsdepcop │ ├── Cs7_1_Tests.cs │ ├── Cs7_2_NonTrailingNamedArguments/ │ │ ├── Cs7_2_NonTrailingNamedArguments.cs │ │ └── config.nsdepcop │ ├── Cs7_2_Tests.cs │ ├── Cs7_3_AttributeOnPropertyBackingField/ │ │ ├── Cs7_3_AttributeOnPropertyBackingField.cs │ │ └── config.nsdepcop │ ├── Cs7_3_Tests.cs │ ├── Cs7_Deconstruction/ │ │ ├── Cs7_Deconstruction.cs │ │ └── config.nsdepcop │ ├── Cs7_IsExpressionWithPattern/ │ │ ├── Cs7_IsExpressionWithPattern.cs │ │ └── config.nsdepcop │ ├── Cs7_LocalFunction/ │ │ ├── Cs7_LocalFunction.cs │ │ └── config.nsdepcop │ ├── Cs7_Out/ │ │ ├── Cs7_Out.cs │ │ └── config.nsdepcop │ ├── Cs7_SwitchWithPattern/ │ │ ├── Cs7_SwitchWithPattern.cs │ │ └── config.nsdepcop │ ├── Cs7_ThrowExpression/ │ │ ├── Cs7_ThrowExpression.cs │ │ └── config.nsdepcop │ ├── Cs7_Tuples/ │ │ ├── Cs7_Tuples.cs │ │ └── config.nsdepcop │ ├── FileBasedTestsBase.cs │ ├── NsDepCop.SourceTest.csproj │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── SourceLineSegment.cs │ └── SourceTestSpecification.cs ├── NsDepCop.Test/ │ ├── FileBasedTestsBase.cs │ ├── Implementation/ │ │ ├── Analysis/ │ │ │ ├── AssemblyDependencyAnalyzerTests.cs │ │ │ ├── CachingTypeDependencyValidatorTests.cs │ │ │ ├── DependencyAnalyzerTests.cs │ │ │ ├── DependencyRulesBuilder.cs │ │ │ ├── TypeDependencyValidatorExtensions.cs │ │ │ └── TypeDependencyValidatorTests.cs │ │ └── Config/ │ │ ├── AnalyzerConfigBuilderTests.cs │ │ ├── MultiLevelXmlFileConfigProviderTests/ │ │ │ ├── Attributes_LowerLevelWins/ │ │ │ │ └── Level2/ │ │ │ │ ├── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── Attributes_MissingDoesNotOverwrite/ │ │ │ │ └── Level2/ │ │ │ │ ├── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── ConfigDisabledAtHigherLevelAndUndefinedAtProjectLevel/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── ConfigDisabledAtHigherLevelButEnabledAtProjectLevel/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── ConfigDisabledAtProjectLevel/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── ConfigEnabled/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── ConfigError/ │ │ │ │ ├── Level2/ │ │ │ │ │ ├── Level1/ │ │ │ │ │ │ └── config.nsdepcop │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── ExcludedFiles_AllCorrectlyRooted/ │ │ │ │ └── Level2/ │ │ │ │ ├── Excluded File 4.cs │ │ │ │ ├── ExcludedFile3.cs │ │ │ │ ├── Level1/ │ │ │ │ │ ├── Excluded File 2.cs │ │ │ │ │ ├── ExcludedFile1.cs │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── NoConfig/ │ │ │ │ └── Level2/ │ │ │ │ └── Level1/ │ │ │ │ └── placeholder.txt │ │ │ ├── RefreshConfig_EnabledToConfigError/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── RefreshConfig_EnabledToDisabled/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── RefreshConfig_EnabledToEnabledButChanged/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── RefreshConfig_EnabledToNoConfig/ │ │ │ │ └── Level2/ │ │ │ │ └── Level1/ │ │ │ │ └── placeholder.txt │ │ │ ├── RefreshConfig_InheritanceDepthChanged/ │ │ │ │ ├── Level2/ │ │ │ │ │ └── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── RefreshConfig_NoConfigToEnabled/ │ │ │ │ └── Level2/ │ │ │ │ └── Level1/ │ │ │ │ └── placeholder.txt │ │ │ ├── Rules_Merged/ │ │ │ │ └── Level2/ │ │ │ │ ├── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ ├── UpdateMaxIssueCount_Level1ContainsMaxIssueCount/ │ │ │ │ └── Level2/ │ │ │ │ ├── Level1/ │ │ │ │ │ └── config.nsdepcop │ │ │ │ └── config.nsdepcop │ │ │ └── UpdateMaxIssueCount_Level1ContainsNoMaxIssueCount/ │ │ │ └── Level2/ │ │ │ ├── Level1/ │ │ │ │ └── config.nsdepcop │ │ │ └── config.nsdepcop │ │ ├── MultiLevelXmlFileConfigProviderTests.cs │ │ ├── XmlConfigParserTests/ │ │ │ ├── AllowedAssemblyRules.nsdepcop │ │ │ ├── AllowedRuleForNamespaceWithVisibleMembersWithOfNamespaceAttribute.nsdepcop │ │ │ ├── AllowedRuleForWildcardNamespaceWithVisibleMembers.nsdepcop │ │ │ ├── AllowedRuleFromAttributeMissing.nsdepcop │ │ │ ├── AllowedRuleToAttributeMissing.nsdepcop │ │ │ ├── AllowedRules.nsdepcop │ │ │ ├── DisallowedAssemblyRules.nsdepcop │ │ │ ├── DisallowedRules.nsdepcop │ │ │ ├── InvalidAttributeValue.nsdepcop │ │ │ ├── InvalidDuplicatedWildcardNamespaceString.nsdepcop │ │ │ ├── InvalidNamespaceString.nsdepcop │ │ │ ├── NoRootAttributes.nsdepcop │ │ │ ├── NsDepCopConfigElementNotFound.nsdepcop │ │ │ ├── RootAttributes.nsdepcop │ │ │ ├── VisibleMembers.nsdepcop │ │ │ ├── VisibleMembersOfNamespaceMissing.nsdepcop │ │ │ └── VisibleMembersTypeNameAttributeMissing.nsdepcop │ │ ├── XmlConfigParserTests.cs │ │ ├── XmlFileConfigProviderTests/ │ │ │ ├── Disabled.nsdepcop │ │ │ ├── Enabled.nsdepcop │ │ │ ├── Erronous.nsdepcop │ │ │ ├── Excluded File 2.cs │ │ │ ├── ExcludedFile1.cs │ │ │ ├── ExcludedFiles.nsdepcop │ │ │ ├── RefreshConfig_EnabledToConfigError.nsdepcop │ │ │ └── RefreshConfig_EnabledToDisabled.nsdepcop │ │ ├── XmlFileConfigProviderTests.cs │ │ └── XmlFileConfigTestBase.cs │ ├── Interface/ │ │ └── Config/ │ │ ├── DomainSpecificationParserTests.cs │ │ ├── DomainSpecificationTests.cs │ │ ├── DomainTests.cs │ │ ├── RegexDomainTests.cs │ │ └── WildcardDomainTests.cs │ ├── NsDepCop.Test.csproj │ ├── Properties/ │ │ └── AssemblyInfo.cs │ └── RoslynAnalyzer/ │ └── AnalyzerProviderTests.cs ├── NsDepCop.Vsix/ │ ├── NsDepCop.Vsix.csproj │ ├── readme.txt │ └── source.extension.vsixmanifest ├── NsDepCop.sln ├── NsDepCop.sln.DotSettings ├── config.nsdepcop └── include/ ├── CommonAssemblyInfo.cs └── VersionInfo.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studo 2015 cache/options directory .vs/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others *.[Cc]ache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt /source/out *.local.* ================================================ FILE: CHANGELOG.md ================================================ # NsDepCop Change Log ## v2.7.0 (12/2025) - [x] Perf: Avoids unnecessary rebuilds when only a solution-level config.nsdepcop file is used (no project-level files). ## v2.6.0 (04/2025) - [x] New: Regex patterns for assemblies and namespaces. ## v2.5.0 (01/2025) - [x] New: Added assembly dependency checking. ## v2.4.0 (09/2024) - [x] New: Analyzing top level statements. ## v2.3.0 (07/2023) - [x] Changed: Added the allowed type names to the diagnostic message (if VisibleMembers are specified). ## v2.2.0 (09/2022) - [x] New: Wildcard patterns for namespaces. ## v2.1.0 (07/2022) - [x] New: ParentCanDependOnChildImplicitly config attribute. ## v2.0.1 (03/2022) - [x] Fix: #60 - Statically imported type dependency not detected. ## v2.0.0 (06/2021) The big change in this version is that the implementation changed from MSBuild task + Visual Studio Extension to a standard Roslyn analyzer. - [x] NsDepCop must be added to a project as a NuGet package. - [x] Appears in Solution Explorer: project / Dependencies / Analyzers / NsDepCop.Analyzer - [x] Issue severities can be configured that same way as other analyzers (use Visual Studio or .editorconfig files). - [x] Works both at build time and inside Visual Studio editor. - [x] Requires Visual Studio 2019 (16.10.0 or later). - [x] Supports .NET Core / .NET 5 / etc. projects too. - [x] Uses Roslyn 3.9.0. Stuff that was removed: - [x] No need for the NsDepCop Visual Studio Extension any more. - [x] No need for the out-of-process service host any more. - [x] Dropped support for VS 2015/2017. For those, use NsDepCop v1.11.0. - [x] Config attributes no longer supported (ignored): CodeIssueKind, MaxIssueCountSeverity, InfoImportance, AnalyzerServiceCallRetryTimeSpans. Other info: - [x] AutoLowerMaxIssueCount feature is temporarily not supported. ## v1.11.0 (04/2020) - [x] New: Disable/force NsDepCop with MSBuild property. ## v1.10.1 (02/2020) - [x] Fix: #51 - RemotingException when using different NsDepCop NuGet and VSIX version. ## v1.10.0 (04/2019) - [x] New: Support Visual Studio 2019 ## v1.9.0 (03/2019) - [x] New: Support incremental build - don't run the tool if there was no change in the source/config files. - [x] New: Global turn off switch - DisableNsDepCop environment variable. - [x] New: Excluding files from analysis - ExcludedFiles attribute in config.nsdepcop. ## v1.8.2 (01/2019) - [x] Fix: #43 (for real this time) - RemotingException when path contains space. ## v1.8.1 (12/2018) - [x] Fix: #43 - RemotingException when path contains space. ## v1.8.0 (07/2018) - [x] Enhancements in launching the out-of-process service host (configurable retry intervals, allow access for any user). - [x] MaxIssueCountSeverity - enables breaking the build when a threshold number of dependency violations has been reached. - [x] AutoLowerMaxIssueCount - automatically lower MaxIssueCount to encourage cleaning up dependency problems and prohibit introducing new ones. - [x] Supports C# up to 7.3. - [x] The Visual Studio Extension (VSIX) requires 15.7.4 or higher. ## v1.7.1 (07/2017) - [x] Performance enhancement when run by MSBuild. - [x] The analyzer keeps running in its own process to avoid repeated creation cost. Shuts down with parent process. ## v1.7.0 (07/2017) - [x] New: C# 7 support. - [x] New: Visual Studio 2017 support. - [x] Removed: MSI installer. - [x] Config XML schema support is now available as a Visual Studio Extension (only for VS2017). - [x] Global MSBuild-integration is discontinued. Please use per-project MSBuild integration via the NuGet package. ## v1.6.1 (04/2017) - [x] Fix: Type without a name caused exception in analyzer. ## v1.6.0 (03/2017) - [x] New: NuGet package. - [x] Enables per-project MSBuild integration. - [x] Enables zero install on build machine. - [x] New: Multi-level config file. - [x] Use the InheritanceDepth config attribute to specify the number of parent folder levels to merge config files from. - [x] Use it to get rid of redundant rules and settings in nsdepcop.config files and move them to a common place, eg. to solution level or repo root level. - [x] Changed: MSI installer modified. - [x] Option (default on): config.nsdepcop XML schema support updated. - [x] Option (default off): Machine-wide MSBuild integration. Not recommended any more, use the NuGet (per-project) distribution instead. - [x] Removed option: Visual Studio 2015 integration. Use the VSIX package directly instead. - [x] Fixed: - [x] Types in enum and delegate declarations were not analyzed. - [x] Constructed generic, array and pointer types are now analyzed recursively. - [x] Source and metadata file load errors are now handled gracefully. - [x] Changed: Roslyn version updated to 1.3.2. - [x] Changed: Removed NRefactory as a parser choice. ### Upgrading * To upgrade please uninstall the previous version first. * Your existing config.nsdepcop files will be preserved. * Then install the new version. * Recommended: change to per-project MSBuild integration with NuGet. See [README.md](README.md) for details. * Please note that if you don't uninstall the previous version then its machine-wide MSBuild integration will override the per-project integration provided by the NuGet package and the old analyzer version will run at build time. ## v1.5 (06/2016) - [x] Supports Visual Studio 2015 only. - [x] New: Use the VisibleMembers element to fine-tune the allowed dependencies at the type level. - [x] New: Added config.nsdepcop XML schema support to Visual Studio so it can validate config syntax and provide IntelliSense. - [x] Fixed: adding an nsdepcop.config file to a project now works for all C# projects (incl. portable lib). - [x] Changed: Roslyn version updated to 1.2.2. ## v1.4 (08/2015) - [x] Supports Visual Studio 2015 only. - [x] New: Info messages' level can be configured to suppress/enable them in the MSBuild output. - [x] New: ChildCanDependOnParentImplicitly config attribute. - [x] Fixed: - [x] MSBuild return code was success even if an error was detected. - [x] Extension method declaring type was not checked. - [x] Changed: Roslyn version updated to 1.0. - [x] Icons (tadaaa! :) ## v1.3 (01/2015) - [x] Supports Visual Studio 2013 only. - [x] New: Disallowed rules. ## v1.2 (08/2014) - [x] Supports Visual Studio 2013 only. - [x] Requires the Roslyn End User Preview of April 2014. ## v1.1 (07/2013) - [x] Supports Visual Studio 2012 only. - [x] Requires Roslyn September 2012 CTP. - [x] New: Added NRefactory as the default parser for the MSBuild task. ## v1.0 (03/2013) - [x] Supports Visual Studio 2012 only. - [x] Requires Roslyn September 2012 CTP. ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview NsDepCop is a Roslyn-based static analysis tool for C# that enforces namespace and assembly dependency rules. It ships as a NuGet package that integrates into the build process, reporting violations as compiler warnings/errors. ## Build and Test Commands Solution-level `dotnet build/test` fails due to a self-referencing NuGet cycle (`NsDepCop.Analyzer` depends on the `NsDepCop` NuGet package, which is produced by `NsDepCop.NuGet` in the same solution). CI avoids this with standalone `nuget.exe restore` + Framework `msbuild`; VS IDE handles it internally. From the command line, target individual projects: ```bash # Build the analyzer (restores and builds dependencies automatically) dotnet build source/NsDepCop.Analyzer/NsDepCop.Analyzer.csproj # Run unit tests dotnet test source/NsDepCop.Test/NsDepCop.Test.csproj # Run source-based integration tests dotnet test source/NsDepCop.SourceTest/NsDepCop.SourceTest.csproj # Run a single test by name dotnet test source/NsDepCop.Test/NsDepCop.Test.csproj --filter "FullyQualifiedName~TestMethodName" ``` **Prerequisites:** Visual Studio 2022 with **Visual Studio extension development** workload (includes the .NET SDK). ## Solution Structure All source code lives under `source/`. The solution is `source/NsDepCop.sln`. | Project | Target | Purpose | |---|---|---| | `NsDepCop.Analyzer` | netstandard2.0 | Core analyzer — the main product code | | `NsDepCop.Test` | net8.0 | Unit tests (xUnit, FluentAssertions, Moq) | | `NsDepCop.SourceTest` | net8.0 | Integration tests — verifies analyzer against C# source files | | `NsDepCop.NuGet` | netstandard2.0 | NuGet package packaging | | `NsDepCop.Benchmarks` | — | Performance benchmarks | | `NsDepCop.Vsix` | — | Visual Studio Extension wrapper | ## Architecture ### Analyzer Core (`NsDepCop.Analyzer`) Root namespace: `Codartis.NsDepCop`. Internal module dependencies are enforced by `config.nsdepcop` in the project itself. - **RoslynAnalyzer/** — Entry point. `NsDepCopAnalyzer` extends Roslyn `DiagnosticAnalyzer`, registered for `IdentifierName`, `GenericName`, and `DefaultLiteralExpression` syntax kinds. Wires together config and analysis. Must only depend on other modules via interfaces (not `.Implementation` namespaces). - **Config/** — XML config file parsing and rule model. `DependencyRule`, `Domain`, `WildcardDomain`, `RegexDomain` represent rules. `MultiLevelXmlFileConfigProvider` handles config inheritance (project → parent directories). Factory pattern separates creation from implementation. - **Analysis/** — Dependency validation logic. `DependencyAnalyzer` orchestrates type-level and assembly-level validation. `TypeDependencyValidator` checks namespace rules; `AssemblyDependencyValidator` checks assembly rules. - **ParserAdapter/Roslyn/** — Extracts type dependencies from Roslyn syntax trees. - **Util/** — Shared helpers. ### Dependency flow between modules ``` RoslynAnalyzer → Config (interfaces only), Analysis (interfaces only), ParserAdapter ParserAdapter → Analysis Analysis → Config Config.Factory → Config.Implementation ``` The `RoslynAnalyzer` layer is explicitly **disallowed** from depending on `*.Implementation` namespaces. ### Test Structure **Unit tests** (`NsDepCop.Test`): Standard xUnit tests for config parsing, validation logic, and analyzer behavior. Test data files (`.nsdepcop` configs) are in subdirectories named after their test class, copied to output via `CopyToOutputDirectory`. **Source tests** (`NsDepCop.SourceTest`): Each test case is a folder containing a `.cs` source file and a `config.nsdepcop` file. The `.cs` files are excluded from compilation (``) and instead copied to output as test data. Tests verify the analyzer produces expected diagnostics for various C# syntax patterns (C# 6, 7, 7.1, 7.2, 7.3, top-level statements). ### Self-referencing / Dogfooding The project references its own NuGet package (`NsDepCop 2.7.0`) and enforces dependency rules on its own code. `Directory.Build.targets` contains a workaround (`AvoidCycleErrorOnSelfReference`) that renames `PackageId` to `NsDepCop_temp` during build to break the cycle, restoring it before pack. This workaround is broken with current .NET SDK versions — `dotnet restore` and `msbuild /t:Restore` still detect the cycle. Only standalone `nuget.exe restore` (used by CI) and VS IDE's internal restore avoid the error. This is why command-line builds must target individual projects rather than the solution. ## Key Conventions - `TreatWarningsAsErrors` is enabled on all projects - Root namespace is `Codartis.NsDepCop` (with project-specific suffixes like `.Test`) - `config.nsdepcop` is the XML configuration file format — both the product config and test fixtures - CI runs on AppVeyor (Visual Studio 2022 image), Release configuration - Version is managed in `appveyor.yml` and patched into `source/Include/VersionInfo.cs` ## Diagnostics Diagnostic IDs: `NSDEPCOP01` (illegal namespace dependency), `NSDEPCOP02` (too many issues), `NSDEPCOP03` (no config), `NSDEPCOP04` (config disabled), `NSDEPCOP05` (config error), `NSDEPCOP06` (tool disabled), `NSDEPCOP07` (illegal assembly dependency). Definitions are in `DiagnosticDefinitions.cs`. ================================================ FILE: Contribute.md ================================================ ## How to build the source 1. Prerequisites * Visual Studio 2019 or later (16.10.0 or later, any edition) * With workload: **Visual Studio extension development** 1. [Download or clone the source](https://github.com/realvizu/NsDepCop) 1. Open "source\NsDepCop.sln" 1. Build the solution. ## How to debug the tool in Visual Studio 1. Set **NsDepCop.Vsix** as the StartUp project. 1. In the project file modify the **StartArguments** tag to point to a valid solution file and log path. 1. Run the solution. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: README.md ================================================ # NsDepCop - Namespace and Assembly Dependency Checker Tool for C# # NsDepCop is a static code analysis tool that enforces namespace and assembly dependency rules in C# projects. * It runs as part of the build process and reports any dependency problems. * No more unplanned or unnoticed dependencies in your system. What is this [**dependency control**](doc/DependencyControl.md) anyway? And [**why should you automate it**](https://www.plainionist.net/Dependency-Governance-DotNet/)? ## Getting Started 1. Add the [**NsDepCop NuGet package**](https://nuget.org/packages/NsDepCop) to your C# projects: [![NuGet Package](https://img.shields.io/nuget/v/NsDepCop.svg)](https://nuget.org/packages/NsDepCop) 1. Add a text file named **'config.nsdepcop'** to your project, then edit it to define your [**dependency rules**](doc/Help.md#dependency-rules). 1. Dependency violations will be underlined in the code editor and reported at build time just like compiler errors/warnings. See the [**Help**](doc/Help.md) for details. Or check out this step-by-step [**tutorial video**](https://www.youtube.com/watch?v=rkU7Hx20Dc0) by [plainionist](https://github.com/plainionist). ## Versions * See the [**Change Log**](CHANGELOG.md) for version history. ## Feedback * Use the [**Issue Tracker**](https://github.com/realvizu/NsDepCop/issues) to submit bugs and feature requests. * Use the [**Discussions forum**](https://github.com/realvizu/NsDepCop/discussions) for questions. ## More Info * [Diagnostics Reference](doc/Diagnostics.md) * [Configuring XML schema support for config.nsdepcop files](doc/Help.md#config-xml-schema-support-in-visual-studio) * [Troubleshooting](doc/Troubleshooting.md) * [How to contribute?](Contribute.md) ## Thanks to * [Roslyn](https://github.com/dotnet/roslyn) for the amazing parser API. * [DotNet.Glob](https://github.com/dazinator/DotNet.Glob) for the globbing library. ## License * [GPL-2.0](LICENSE) ## Other Tools * Check out my other project: [Codartis Diagram Tool](https://codartis.com/), a code visualization tool for C#. ================================================ FILE: appveyor.yml ================================================ image: Visual Studio 2022 environment: shortversion: '2.7.0' informationalversion: '2.7.0' version: '$(shortversion).{build}' configuration: Release install: - ps: (New-Object Net.WebClient).DownloadString("https://raw.githubusercontent.com/realvizu/Tools/master/Build/buildtools.ps1") | Invoke-Expression assembly_info: patch: true file: source\include\VersionInfo.cs assembly_version: '{version}' assembly_file_version: '{version}' assembly_informational_version: '$(informationalversion)' dotnet_csproj: patch: true file: source\NsDepCop.NuGet\NsDepCop.NuGet.csproj package_version: '$(informationalversion)' before_build: - cmd: nuget restore -Verbosity quiet source\NsDepCop.sln build: project: source\NsDepCop.sln verbosity: minimal test: assemblies: '**\*Test*.dll' artifacts: - path: 'source\NsDepCop.NuGet\bin\$(configuration)\NsDepCop*.nupkg' ================================================ FILE: doc/DependencyControl.md ================================================ # Dependency Control ## Why Care About Code Dependencies? The number one enemy of a developer is complexity. Among the many sources of complexity, lets focus now on the structure of the codebase and its dependencies. * To reduce complexity, the primary strategy is to decompose the code into smaller units (e.g., modules) and limit dependencies between them. * A codebase with well-defined modules and clear dependency rules is easier to understand, modify, and reuse. * However, without a dependency control tool acting as a safeguard, even a well-structured codebase can eventually degrade into a tangled mess. ## How to Control Code Dependencies? Dependency control tools allow you to define rules for permitted and prohibited dependencies and alert you to any violations. These tools can operate at various levels and on different input formats: * Physical Level: Projects, libraries, and assemblies. * Logical Level: Namespaces and types. * Input Formats: Source code, compiled binaries, or intermediary representations. **This tool performs the following dependency checks:** * Namespace dependencies in the C# source code. * Assembly dependencies in the C# projects. It also supports some fine-tuning at the type level (see details below). ## What Is a Namespace Dependency? Namespace **A** depends on Namespace **B** if any type declared in Namespace **A** uses any type declared in Namespace **B**. In the example below **Namespace A depends on Namespace B** because type A1 uses types B1, B2, B3, B4 and B5. ```csharp namespace A { using B; class A1 : B1 { B2 field1; B3 Property1 { get; set; } B4 MyMethod(B5 p) { ... } } } namespace B { interface B1 {} class B2 {} struct B3 {} enum B4 {} delegate void B5(); } ``` Note that a "using B" directive *does not automatically imply* a dependency from A to B. If no members of Namespace B are actually used in class A1, then no real dependency exists. Modern code editors can warn about unnecessary using directives. This tool only considers actual code dependencies. ## Recommended approach * **Define** the high-level structure of your system as a hierarchy of logical modules or packages. * It's better to do this upfront to avoid extensive refactoring later. * This tool won't assist with the initial design; use a modeling tool or even pen and paper for this step. * **Implement** these logical modules/packages using C# namespaces. * Ensure a one-to-one correspondence between logical units and namespaces. * [**Describe**](Help.md#dependency-rules) allowed namespace dependencies in config.nsdepcop files. * Use one config file per C# project. * Common dependency rules can be placed in a ["master" config file](Help.md#config-inheritance). * **Fix** illegal dependencies reported by the tool. * This may require rethinking or redesigning parts of your architecture. * Avoid circular dependencies. ## Why Namespace Dependencies Instead of Type Dependencies? Enforcing dependency rules at the type level can lead to verbose and fragile dependency descriptions. Namespaces provide a useful level of abstraction above types and offer a hierarchical structure that simplifies dependency rules. This allows dependencies to be specified not just between individual logical packages but also between groups or subgroups of packages. ## Why Does This Tool Support Some Type-Level Dependency Control? In cases where you are working with third-party or legacy code whose structure you cannot control, but still want to limit dependencies, this tool allows you to specify a subset of types within a namespace as the visible "surface" of that namespace. All other types in that namespace are then illegal to depend upon. ================================================ FILE: doc/Diagnostics.md ================================================ # NsDepCop Diagnostics Reference ### NSDEPCOP01 Title|Illegal namespace reference. :-|:- Default Severity|Warning Explanation|The designated type cannot reference the other type because their namespaces cannot depend on each other according to the current rules. To Do|Change the dependency rules in the 'config.nsdepcop' file or change your design to avoid this namespace dependency. ### NSDEPCOP02 Title|Too many dependency issues, analysis was stopped. :-|:- Default Severity|Warning Explanation|The number of dependency issues in this compilation has exceeded the configured maximum value. To Do|Correct the reported issues and run the build again or set the MaxIssueCount attribute in your 'config.nsdepcop' file to a higher number. ### NSDEPCOP03 Title|No config file found, analysis skipped. :-|:- Default Severity|Info Explanation|This analyzer requires that you add a file called 'config.nsdepcop' to your project with build action 'C# analyzer additional file'. To Do|None, this is just an informational message. ### NSDEPCOP04 Title|Analysis is disabled in the config file. :-|:- Default Severity|Info Explanation|The IsEnabled attribute was set to false in this project's 'config.nsdepcop' file, so the analyzer skips this project. To Do|None, this is just an informational message. ### NSDEPCOP05 Title|Error loading config. :-|:- Default Severity|Error Explanation|There was an error while loading the 'config.nsdepcop' file, see the message for details. Some common reasons: malformed content, file permission or file locking problem. To Do|Make sure that the file can be read by the user running the build or Visual Studio and make sure that its content is correct. ### NSDEPCOP06 Title|Analysis is disabled with environment variable. :-|:- Default Severity|Info Explanation|If the 'DisableNsDepCop' environment variable is set to 'True' or '1' then all analysis is skipped. To Do|None, this is just an informational message. ### NSDEPCOP07 Title|Illegal assembly reference. :-|:- Default Severity|Warning Explanation|The designated assembly cannot reference the other assembly because their dependency is prohibited according to the current rules. To Do|Change the dependency rules in the 'config.nsdepcop' file or change your design to avoid this assembly dependency. ================================================ FILE: doc/Help.md ================================================ # NsDepCop Help * [Supported project types](#supported-project-types) * [Dependency rules](#dependency-rules) * [Config inheritance](#config-inheritance) * [Dealing with a high number of dependency issues](#dealing-with-a-high-number-of-dependency-issues) * [Disabling with an environment variable](#disabling-with-an-environment-variable) * [Config XML schema](#config-xml-schema) * [Config XML schema support in Visual Studio](#config-xml-schema-support-in-visual-studio) * [v1.x only topics](#v1.x-only-topics) ## Supported project types * Projects with a **csproj** project file are supported. * .Net Core and .Net 5+ projects are supported in v2.0 or above. * Projects with an **xproj** project file are not supported. ## Dependency rules * Allowed and disallowed namespace and assembly dependencies are described with dependency rules in config files. * The rule config file must be named **config.nsdepcop** and its build action must be set to **C# analyzer additional file** (the NsDepCop NuGet package sets it automatically). * The default (and recommended) approach is [**allowlisting**](#allowlisting), that is, if a dependency is not explicitly allowed then it is disallowed. (See also: [denylisting](#denylisting)). * The config file can inherit other config files from parent folders, see [**config inheritance**](#config-inheritance). * Assembly dependency checking is disabled by default (for backward compatibility reason) and needs to be enabled with **CheckAssemblyDependencies** attribute on the root element. See [Example Two](#example-two). * Rules can specify namespaces in the following ways. Namespace specification type | Example -- | -- Exact| System.IO Wildcard | System.Collections.*
MyProduct.?.StorageModel Regex | /MyProduct(\.[\w]+)*\.StorageModel[\w]/ > Notice that Regex patterns must be enclosed in forward slashes ('/'). ### Rule notation Notation | Meaning -- | -- . (a single dot) | The global namespace. \* (a single star) | Any namespace. MyNamespace.\* | MyNamespace and any sub-namespaces. MyNamespace.?| All direct sub-namespaces of MyNamespace \*.MyNamespace | Any namespace named MyNamespace ?.MyNamespace | Any namespace named MyNamespace which has exactly one parent namespace. MyNamespace.*.MyOtherNamespace| Any namespace called MyOtherNamespace which has an ancestor named MyNamespace MyNamespace.?.MyOtherNamespace| Any namespace called MyOtherNamespace which has a grandparent named MyNamespace /RegexPattern/| Any namespace that matches the specified regular expression (Regex) pattern.
The pattern must be enclosed in forward slashes ('/').
Regex special characters must be escaped, e.g. '.' should be "\\.". See the [Regex reference](https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference) for details. ### Example One ```xml ``` Meaning: * **Any** namespace can reference the **System** namespace and any of its sub-namespaces. * The **NsDepCop** namespace and all of its sub-namespaces can reference the **Microsoft.CodeAnalysis** namespace and any of its sub-namespaces. * The **NsDepCop.ParserAdapter.Roslyn** namespace can reference the **NsDepCop.Analysis** namespace (but not its sub-namespaces). ### Example Two ```xml ``` Meaning: * **Any** assembly can reference each other with the following exception. * The **Repository** layer cannot reference the **Service** layer. Tip: When using the allowlisting approach for assemblies, don't forget the include the following rules: ```xml ``` ### Config attributes You can set the following attributes on the root element. (Bold marks the the **default** value.) Attribute | Values | Description --- | --- | --- **IsEnabled** | **true**, false | If set to false then analysis is not performed for the project. **ChildCanDependOnParentImplicitly** | true, **false** | If set to true then all child namespaces can depend on any of their parents without an explicit allowing rule. The recommended value is **true**. (False is default for backward compatibility.) **ParentCanDependOnChildImplicitly** | true, **false** | If set to true then all parent namespaces can depend on any of their children without an explicit allowing rule. The recommended value is **false**. **MaxIssueCount** | int (>0), default: **100** | Analysis stops when reaching this number of dependency issues. **AutoLowerMaxIssueCount** | true, **false** | If set to true then each successful build yielding fewer issues than MaxIssueCount sets MaxIssueCount to the current number of issues. **InheritanceDepth** | int (>=0), default: **0** | Sets the number of parent folder levels to inherit config from. 0 means no inheritance. **ExcludedFiles** | Comma separated list of [file patterns](https://github.com/dazinator/DotNet.Glob) | Defines which source files should be excluded from the analysis. Paths are relative to the config file's folder. E.g.: `**/*.g.cs,TestFiles/*.cs` **CheckAssemblyDependencies** | true, **false** | We adopt the 'disallowed-by-default' approach for assembly dependencies check, similar to how we handle namespace dependencies (where everything is disallowed unless explicitly permitted). To ensure the backward compatibility, this configuration attribute has been introduced to explicitly enable the assembly dependency checking. By default this attribute is false. ### Allowlisting * The **``** config element defines that **N1** namespace can depend on **N2** namespace. * If a dependency does not match any of the allowed rules then it's considered disallowed. Examples: Example | Meaning -|- `` | **MyNamespace** can depend on **System** `` | **MyNamespace** can depend on **System and any sub-namespace** `` | **MyNamespace** can depend on **any namespace** `` | **MyNamespace** can depend on the **global namespace** `` | **MyNamespace** can depend on all **Serialization** namespaces in **System** and their sub-namespaces ### Denylisting * The **``** config element defines that **N1** namespace **must not** depend on **N2** namespace. * To implement the denylisting behavior, you also have to define an "allow all" rule, otherwise no dependency will be allowed. * Only those dependencies are allowed that has a matching "Allowed" rule and no match with any of the "Disallowed" rules. * You can specify any number of "Allowed" and "Disallowed" rules in any order. * If both an "Allowed" and a "Disallowed" rule are matched then "Disallowed" is the "stronger". Example: ```xml ``` Meaning: * Every dependency is allowed but MyFrontEnd (and its sub-namespace) must not depend on MyDataAccess (and its sub-namespaces). ### Behavior of the wildcards '*' and '?' If any `Disallowed` rule matches, no `Allowed` rule is considered. If multiple `Allowed` rules match the same namespace, the one with best matching `From` rule is selected. The best matching rule is the one with the minimal edit distance between namespace pattern and namespace name. The edit distance is calculated as the sum of all edit operations which are needed to replace the wildcards with the namespace names. The costs are as follows: * Replacing a `?` has a cost of 1. * Replacing a `*` has a cost of 1 and additionaly a cost of 1 per sub-namespace that replaces the `*`. Example: When matching the namespace `A.B.C.D` the rule `A.?.?.D` (edit distance = 2) is preferred to the rule `A.*.D` (edit distance = 3). If multiple rules have the same edit distance, the behavior is undefined. ### Namespace surface * The *surface* of a namespace consists of the types that are visible to some other namespace. * The **``** config element defines the surface of a namespace. * In the following example **GameLogic** can use only **Vector2** and **Vector3** types of the **UnityEngine** namespace. ```xml ``` * Notice that the surface is defined in the context of a particular namespace dependency, that is, this surface of **UnityEngine** is accessible only to **GameLogic**. * You can define different surfaces for different other namespaces. * You can also define a **"global"** surface, that is, a surface that is applicable to all namespaces that otherwise are allowed to depend on **UnityEngine**. See the following example. ```xml ``` * Notice that when defining a "global" surface the `` element is not embedded in an `` element but you must specify the **OfNamespace** attribute. ### Allowing all child namespaces to depend on their parents You can specify the **ChildCanDependOnParentImplicitly** attribute on the NsDepCopConfig element. * True means that all child namespaces can depend on any of their parent namespaces without requiring an explicit Allowed rule. * True is in line with how C# type resolution works: it searches parent namespaces without requiring an explicit using statement. * False means that all dependencies between children and their parents must be explicitly allowed with a rule. * False is the default for backward compatibility. Example: ```xml ``` ### Allowing all parent namespaces to depend on their children You can specify the **ParentCanDependOnChildImplicitly** attribute on the NsDepCopConfig element. However, this is **not recommended**, because child namespaces are usually more concrete/specialized than their parents and the dependecies should point from the more concrete/specialized to the more abstract/generic and not the other way. ## Config inheritance From v1.6 NsDepCop supports config inheritance, aka multi-level config. * The goal is to achieve "DRY" configs, that is, **avoid redundant info** in config.nsdepcop files. * You can extract common config settings from project-level config.nsdepcop files and put them into a **"master"** config file, that must be in a folder that is a common ancestor of the project folders, e.g. the solution folder. * The "master" config file must also be named config.nsdepcop. * You have to **"turn on" inheritance** in the project-level configs by setting the `InheritanceDepth` attribute to a number that indicates the number of folder levels between the project folder and the master config's folder. * Typically you put the master config file into the solution folder which is the immediate parent of the project folders, so you set `InheritanceDepth="1"` in all project-level configs. Example: ```xml config.nsdepcop file in "C:\MySolution": config.nsdepcop file in "C:\MySolution\MyProject": ``` More info: * If there is a conflict between the project-level and the inherited settings then the project-level settings "wins". * The `IsEnabled` attribute has different meaning in the project-level config and in inherited configs. * If `IsEnabled="false"` in a project-level config then the project don't get analyzed. * If `IsEnabled="false"` in an inherited config then its content doesn't get inherited. * The inheritance is not limited to just a project and a solution level config; you can have any number of config.nsdepcop files at any folder levels. Just make sure you set the `InheritanceDepth` to a number that is great enough to find all the higher-level configs. * There must always be a config.nsdepcop file in the project folder if you want to analyze that project. Even if all the settings come from a higher-level config, you have to put **at least a minimal config to the project level**, that enables the inheritance in the first place. E.g.: `` ## Dealing with a high number of dependency issues If there are so many dependency issues that you cannot fix them all at once but you still want to control them somehow then try the following. * Prevent the introduction of more dependency issues. Set the current number of issues as the maximum and make it an error to create more. ```xml ``` * Encourage developers to gradually fix the dependency issues by automatically lowering the max issue count whenever possible. Turn on AutoLowerMaxIssueCount. ```xml ``` > Please note that when NsDepCop modifies the nsdepcop.config files their formatting will be reset (because of the XML deserialization/serialization roundtrip). ## Disabling with an environment variable To disable the tool **globally**, set the **DisableNsDepCop** environment variable to **true** or **1**. `setx DisableNsDepCop 1` It will affect both MSBuild integration (NuGet package) and Visual Studio integration (VSIX package). Note that it won't affect processes that are already running, only the newly started ones. ## Config XML schema See the XSD schema of config.nsdepcop [here](../source/NsDepCop.ConfigSchema/NsDepCopConfig.xsd). ## Config XML schema support in Visual Studio Add NsDepCop config XML schema to the Visual Studio schema cache to get validation and IntelliSense when editing NsDepCop config files. * Copy the following files into the Visual Studio schema cache folder, located at <VsInstallDir>/Xml/Schemas (eg. C:\Program Files\Microsoft Visual Studio\2022\Community\Xml\Schemas): * [NsDepCopCatalog.xml](../source/NsDepCop.ConfigSchema/NsDepCopCatalog.xml) * [NsDepCopConfig.xsd](../source/NsDepCop.ConfigSchema/NsDepCopConfig.xsd) ## v1.x only topics The following topics apply only to v1.x versions. ### Config attributes deprecated in v2.0 Attribute | Values | Description --- | --- | --- **CodeIssueKind** | Info, **Warning**, Error | Dependency violations are reported at this severity level. **InfoImportance** | Low, **Normal**, High | Info messages are reported to MSBuild at this level. This setting and the MSBuild verbosity (/v) swicth together determine whether a message appears on the output or not. See [Controlling verbosity](#controlling-verbosity) for details. **MaxIssueCountSeverity** | Info, **Warning**, Error | This is the severity of the issue of reaching MaxIssueCount. **AnalyzerServiceCallRetryTimeSpans** | Comma separated list of wait times in milliseconds, default: **100, 300, 1000, 3000, 10000** | These wait times are used between retries when the NsDepCop MsBuild Task cannot communicate with the out-of-process analyzer service. ### Controlling verbosity * Besides emitting dependency violation issues, the tool can emit diagnostic and info messages too. * **Info messages** tell you when was the tool started and finished. * **Diagnostic messages** help you debug config problems by dumping config contents and dependency validation result cache change events. * When the tool is run by MSBuild you can modify the [verbosity switch (/v:level)](https://msdn.microsoft.com/en-us/library/ms164311.aspx) to get more or less details in the output. * The verbosity levels defined by MSBuild are the following (ordered from less verbose to most verbose): q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] * Set it to **detailed** or higher to see NsDepCop **diagnostic messages**. * Set it to **normal** or higher to see NsDepCop **info messages**. Advanced settings: * You can also modify the importance level of NsDepCop info messages by setting the `InfoImportance` attribute in config.nsdepcop to Low, Normal or High. * This is useful if you want to keep the MSBuild verbosity level at a certain value for some reason (e.g.: because of other build steps you always want to keep verbosity at minimal level), and you want to control whether NsDepCop info messages are visible at that certain MSBuild verbosity level or not. * The following table shows which InfoImportance levels are shown at certain MSBuild verbosity levels. | MSBuild verbosity level| Low InfoImportance | Normal InfoImportance | High InfoImportance | | - | - | - | - | | q[uiet] | - | - | - | | m[inimal] | - | - | yes | | n[ormal] | - | yes | yes | | d[etailed] | yes | yes | yes | | diag[nostic] | yes | yes | yes | E.g.: if you want NsDepCop info messages to show up at minimal MSBuild verbosity then set `InfoImportance` to High. ### Disabling to tool with MSBuild property To disable the tool in MSBuild, set the **DisableNsDepCop** property to **true**. ```xml true ``` ### Overriding the disabled state with MSBuild property To force executing the tool in MSBuild, set the **ForceNsDepCop** property to **true**. `msbuild MySolution.sln -t:NsDepCop_Analyze -p:ForceNsDepCop=true` ### Running NsDepCop only as an explicit command If NsDepCop slows down the build too much then you can disable it as part of the build and run it explicitly before checking in. * Disable NsDepCop in every build by creating a file called [Directory.Build.Props](https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build) in your source root directory with the following content: ```xml true ``` * Create a cmd file that runs only NsDepCop. Run it before every check-in. `msbuild MySolution.sln -t:NsDepCop_Analyze -p:ForceNsDepCop=true` ### NsDepCop ServiceHost NsDepCop NuGet package **v1.7.1** have introduced the NsDepCop ServiceHost to improve build performance. * It runs in the background as a standalone process, communicates via named pipes and serves requests coming from NsDepCopTask instances running inside MSBuild processes. * It is started automatically when needed by an NsDepCopTask and quits automatically when the MSBuild process that started it exits. * By running continuously it avoids the repeated startup times which is significant. You can control the lifetime of NsDepCop ServiceHost by controlling the lifetime of the MSBuild processes by modifying the **MSBUILDDISABLENODEREUSE** environment variable. * If you set it to 1 then new MSBuild processes are started for each build and they exit when the build finishes. So do NsDepCop ServiceHost. * If you set it to **0** then MSBuild processes are kept alive until the Visual Studio instance that started them exits. **This option gives the best build (and NsDepCop) performance.** ================================================ FILE: doc/Troubleshooting.md ================================================ # NsDepCop Troubleshooting * [Exception: Unable to communicate with NsDepCop service](#item5) * [NsDepCop NuGet package is not adding config.nsdepcop file to the project](#item4) * [Anonymous types raise false alarms](#item3) ## Exception: Unable to communicate with NsDepCop service > Applies only to versions before v2.0. This problem is either caused by a bug or by the analyzer client not waiting enough time for the analyzer server started in a separate process to spin up. To fix it: * Update to v1.10.1 or later. * Try to set longer and/or more wait intervals in your config.nsdepcop file(s) by adding the AnalyzerServiceCallRetryTimeSpans attribute to the root element and fiddling with its value. The value should be a comma separated list of wait times between retries (in milliseconds). * E.g. this config waits 100ms, then 1sec, then 10sec: ```xml ``` ## NsDepCop NuGet package is not adding config.nsdepcop file to the project > Applies only to versions before v2.0. If the project uses the **PackageReference** package manager format then content files are not added to the project. Workaround: * **Add** a file called **config.nsdepcop** and fill it in using the examples in [Help](Help.md). * Install the NsDepCop Visual Studio Extension [![Visual Studio extension](https://img.shields.io/badge/Visual%20Studio%20Marketplace-NsDepCop%20VS2017-green.svg)](https://marketplace.visualstudio.com/items?itemName=FerencVizkeleti.NsDepCopVS2017-CodedependencycheckerforC) and then: * Right-click on project >> Add >> New Item... >> NsDepCop Config File ## Anonymous types raise false alarms > Applies only to versions before v1.6. For anonymous types the compiler generates a class with no namespace so they will belong to the 'global namespace'. If your NsDepCop config does not allow referencing the global namespace (denoted with a single dot) then it will raise an alarm that you may consider a false positive. To avoid alarms caused by anonymous types you have to add a rule that you allow referencing the global namespace: ```xml ``` Or to be more lax: ```xml ``` ================================================ FILE: source/.editorconfig ================================================ root = true [*.cs] # Default severity for analyzer diagnostics with category 'MicrosoftCodeAnalysisReleaseTracking' dotnet_analyzer_diagnostic.category-MicrosoftCodeAnalysisReleaseTracking.severity = none # NSDEPCOP01: Illegal namespace reference. dotnet_diagnostic.NSDEPCOP01.severity = error ================================================ FILE: source/Directory.Build.targets ================================================  $(MSBuildProjectName) $(PackageId) $(PackageId)_temp $(PackageIdTemp) ================================================ FILE: source/NsDepCop.Analyzer/Analysis/AssemblyDependency.cs ================================================ using System; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.Analysis { [Serializable] public struct AssemblyDependency { public static AssemblyDependency Empty; public AssemblyIdentity FromAssembly { get; } public AssemblyIdentity ToAssembly { get; } public AssemblyDependency(AssemblyIdentity fromAssembly, AssemblyIdentity toAssembly) { FromAssembly = fromAssembly ?? throw new ArgumentNullException(nameof(fromAssembly)); ToAssembly = toAssembly ?? throw new ArgumentNullException(nameof(toAssembly)); } public override string ToString() => $"{FromAssembly?.Name}->{ToAssembly?.Name}"; public bool Equals(AssemblyDependency other) { return string.Equals(FromAssembly?.Name, other.FromAssembly?.Name) && string.Equals(ToAssembly?.Name, other.ToAssembly?.Name); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is AssemblyDependency && Equals((AssemblyDependency)obj); } public override int GetHashCode() { unchecked { var hashCode = (FromAssembly != null ? FromAssembly.Name.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (ToAssembly != null ? ToAssembly.Name.GetHashCode() : 0); return hashCode; } } public static bool operator ==(AssemblyDependency left, AssemblyDependency right) { return left.Equals(right); } public static bool operator !=(AssemblyDependency left, AssemblyDependency right) { return !left.Equals(right); } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Factory/AssemblyDependencyAnalyzerFactory.cs ================================================ using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Config; namespace Codartis.NsDepCop.Analysis.Factory { public sealed class AssemblyDependencyAnalyzerFactory : IAssemblyDependencyAnalyzerFactory { public IAssemblyDependencyAnalyzer Create(IUpdateableConfigProvider configProvider) { return new AssemblyDependencyAnalyzer(configProvider); } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Factory/DependencyAnalyzerFactory.cs ================================================ using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Analysis.Factory { /// /// Creates dependency analyzer objects. /// public sealed class DependencyAnalyzerFactory : IDependencyAnalyzerFactory { private readonly MessageHandler _traceMessageHandler; public DependencyAnalyzerFactory(MessageHandler traceMessageHandler) { _traceMessageHandler = traceMessageHandler; } public IDependencyAnalyzer Create(IUpdateableConfigProvider configProvider, ITypeDependencyEnumerator typeDependencyEnumerator) { return new DependencyAnalyzer(configProvider, typeDependencyEnumerator, _traceMessageHandler); } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/IAssemblyDependencyAnalyzer.cs ================================================ using System; using System.Collections.Generic; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.Analysis { /// /// Performs assembly dependency analysis on a project. /// public interface IAssemblyDependencyAnalyzer { IEnumerable AnalyzeProject(AssemblyIdentity sourceAssembly, IReadOnlyList referencedAssemblies); /// /// Re-reads the config. /// void RefreshConfig(); /// /// Gets the current config state. /// AnalyzerConfigState ConfigState { get; } /// /// Gets the config exception or null if there was no exception. /// Exception ConfigException { get; } /// /// Gets the current analyzer config or null if there was an error. /// IAnalyzerConfig Config { get; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/IAssemblyDependencyAnalyzerFactory.cs ================================================ using Codartis.NsDepCop.Config; namespace Codartis.NsDepCop.Analysis { public interface IAssemblyDependencyAnalyzerFactory { IAssemblyDependencyAnalyzer Create(IUpdateableConfigProvider configProvider); } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/IDependencyAnalyzer.cs ================================================ using System; using System.Collections.Generic; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.Analysis { /// /// Performs dependency analysis on a project or a syntax node. /// public interface IDependencyAnalyzer { /// /// Analyzes a project (source files and referenced assemblies). /// /// A collection of the full path of source files. /// A collection of the full path of referenced assemblies. /// Issue and info messages, including illegal dependency issues. IEnumerable AnalyzeProject(IEnumerable sourceFilePaths, IEnumerable referencedAssemblyPaths); /// /// Analyzes a syntax node. /// /// A syntax node. /// The semantic model of the project being analyzed. /// Issue and info messages, including illegal dependency issues. IEnumerable AnalyzeSyntaxNode(SyntaxNode syntaxNode, SemanticModel semanticModel); /// /// Re-reads the config. /// void RefreshConfig(); /// /// Gets the current config state. /// AnalyzerConfigState ConfigState { get; } /// /// Gets the config exception or null if there was no exception. /// Exception ConfigException { get; } /// /// Gets the current analyzer config or null if there was an error. /// IAnalyzerConfig Config { get; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/IDependencyAnalyzerFactory.cs ================================================ using Codartis.NsDepCop.Config; namespace Codartis.NsDepCop.Analysis { /// /// Creates dependency analyzer objects. /// public interface IDependencyAnalyzerFactory { IDependencyAnalyzer Create(IUpdateableConfigProvider configProvider, ITypeDependencyEnumerator typeDependencyEnumerator); } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/ITypeDependencyEnumerator.cs ================================================ using System.Collections.Generic; using DotNet.Globbing; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.Analysis { /// /// Enumerates type dependencies for a project or a syntax node. /// public interface ITypeDependencyEnumerator { /// /// Enumerates type dependencies for a project (source files and referenced assemblies). /// /// A collection of the full path of source files. /// A collection of the full path of referenced assemblies. /// A collection of file path patterns (globs) for excluding source files from analysis. /// A collection of type dependencies. IEnumerable GetTypeDependencies( IEnumerable sourceFilePaths, IEnumerable referencedAssemblyPaths, IEnumerable sourcePathExclusionGlobs); /// /// Enumerates type dependencies for a syntax node. /// /// A syntax node. /// The semantic model of the project being analyzed. /// A collection of file path patterns (globs) for excluding source files from analysis. /// A collection of type dependencies. IEnumerable GetTypeDependencies( SyntaxNode syntaxNode, SemanticModel semanticModel, IEnumerable sourcePathExclusionGlobs); } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/AssemblyDependencyAnalyzer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.Analysis.Implementation { public sealed class AssemblyDependencyAnalyzer : IAssemblyDependencyAnalyzer { private readonly IUpdateableConfigProvider _configProvider; private readonly object _configRefreshLock = new(); private IAssemblyDependencyValidator _assemblyDependencyValidator; private IAnalyzerConfig _config; public AssemblyDependencyAnalyzer( IUpdateableConfigProvider configProvider) { _configProvider = configProvider ?? throw new ArgumentNullException(nameof(configProvider)); UpdateConfig(); } public AnalyzerConfigState ConfigState { get { lock (_configRefreshLock) { return _configProvider.ConfigState; } } } public Exception ConfigException { get { lock (_configRefreshLock) { return _configProvider.ConfigException; } } } public IAnalyzerConfig Config { get { lock (_configRefreshLock) { return _configProvider.Config; } } } public IEnumerable AnalyzeProject( AssemblyIdentity sourceAssembly, IReadOnlyList referencedAssemblies) { if (sourceAssembly == null) throw new ArgumentNullException(nameof(sourceAssembly)); if (referencedAssemblies == null) throw new ArgumentNullException(nameof(referencedAssemblies)); if (GlobalSettings.IsToolDisabled()) return [new ToolDisabledMessage()]; lock (_configRefreshLock) { var assemblyDependencyEnumerable = GetAssemblyDependencies(sourceAssembly, referencedAssemblies); var illegalAssemblyDependencyEnumerable = GetIllegalAssemblyDependencies(assemblyDependencyEnumerable); return AnalyzeCore(illegalAssemblyDependencyEnumerable); } } private IEnumerable GetAssemblyDependencies(AssemblyIdentity sourceAssembly, IReadOnlyList referencedAssemblies) { foreach (AssemblyIdentity referencedAssembly in referencedAssemblies) { yield return new AssemblyDependency(sourceAssembly, referencedAssembly); } } public void RefreshConfig() { lock (_configRefreshLock) { _configProvider.RefreshConfig(); UpdateConfig(); } } private void UpdateConfig() { var oldConfig = _config; _config = _configProvider.Config; if (oldConfig == _config) return; _assemblyDependencyValidator = CreateTypeDependencyValidator(); } private AssemblyDependencyValidator CreateTypeDependencyValidator() { return _configProvider.ConfigState == AnalyzerConfigState.Enabled ? new AssemblyDependencyValidator(_configProvider.Config) : null; } private IEnumerable AnalyzeCore(IEnumerable illegalTypeDependencyEnumerable) { return _configProvider.ConfigState switch { AnalyzerConfigState.NoConfig => new NoConfigFileMessage().ToEnumerable(), AnalyzerConfigState.Disabled => new ConfigDisabledMessage().ToEnumerable(), AnalyzerConfigState.ConfigError => new ConfigErrorMessage(_configProvider.ConfigException).ToEnumerable(), AnalyzerConfigState.Enabled => _configProvider.Config.CheckAssemblyDependencies ? PerformAnalysis(illegalTypeDependencyEnumerable) : new ConfigDisabledMessage().ToEnumerable(), _ => throw new Exception($"Unexpected ConfigState: {_configProvider.ConfigState}") }; } private static IEnumerable PerformAnalysis(IEnumerable illegalTypeDependencyEnumerator) { return illegalTypeDependencyEnumerator.Select(element => new IllegalAssemblyDependencyMessage(element)); } private IEnumerable GetIllegalAssemblyDependencies(IEnumerable assemblyDependencyEnumerator) { foreach (AssemblyDependency assemblyDependency in assemblyDependencyEnumerator) { DependencyStatus dependencyStatus = _assemblyDependencyValidator.IsDependencyAllowed(assemblyDependency); if (!dependencyStatus.IsAllowed) { yield return assemblyDependency; } } } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/AssemblyDependencyValidator.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Analysis.Implementation { public sealed class AssemblyDependencyValidator : IAssemblyDependencyValidator { private readonly HashSet _allowRules; private readonly HashSet _disallowRules; public AssemblyDependencyValidator(IDependencyRules dependencyRules) { if (dependencyRules is null) throw new ArgumentNullException(nameof(dependencyRules)); _allowRules = dependencyRules.AllowedAssemblyRules; _disallowRules = dependencyRules.DisallowedAssemblyRules; } public DependencyStatus IsDependencyAllowed(AssemblyDependency assemblyDependency) { if (assemblyDependency == AssemblyDependency.Empty) { throw new ArgumentException("The parameter is empty.", nameof(assemblyDependency)); } // These assembly names are coming from a compiler so we don't have to validate them. var fromAssembly = new Domain(assemblyDependency.FromAssembly.Name, validate: false); var toAssembly = new Domain(assemblyDependency.ToAssembly.Name, validate: false); var disallowRule = GetDisallowRule(fromAssembly, toAssembly); if (disallowRule is not null) return DependencyStatus.Disallowed; var allowRule = GetMostSpecificAllowRule(fromAssembly, toAssembly); if (allowRule is null) return DependencyStatus.Disallowed; return DependencyStatus.Allowed; } private DependencyRule GetMostSpecificAllowRule(Domain from, Domain to) { return _allowRules .Where(element => element.From.Matches(from) && element.To.Matches(to)) .MaxByOrDefault(element => element.From.GetMatchRelevance(from)); } private DependencyRule GetDisallowRule(Domain from, Domain to) { return _disallowRules.FirstOrDefault(element => element.From.Matches(from) && element.To.Matches(to)); } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/CachingTypeDependencyValidator.cs ================================================ using System.Collections.Concurrent; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Analysis.Implementation { /// /// Validates type dependencies to a set of allowed/disallowed rules and caches the results. /// public class CachingTypeDependencyValidator : TypeDependencyValidator, ICacheStatisticsProvider { private readonly MessageHandler _traceMessageHandler; private readonly ConcurrentDictionary _dependencyValidationCache; public int HitCount { get; private set; } public int MissCount { get; private set; } public CachingTypeDependencyValidator(IDependencyRules dependencyRules, MessageHandler traceMessageHandler) : base(dependencyRules) { _traceMessageHandler = traceMessageHandler; _dependencyValidationCache = new ConcurrentDictionary(); } public double EfficiencyPercent => MathHelper.CalculatePercent(HitCount, HitCount + MissCount); public override DependencyStatus IsAllowedDependency(TypeDependency typeDependency) { if (typeDependency.FromNamespaceName == typeDependency.ToNamespaceName) return DependencyStatus.Allowed; var isAllowedDependency = _dependencyValidationCache.GetOrAdd(typeDependency, base.IsAllowedDependency, out var added); if (added) { MissCount++; LogTraceMessage($"Dependency {typeDependency} added to cache as {isAllowedDependency}."); } else { HitCount++; } return isAllowedDependency; } private void LogTraceMessage(string message) => _traceMessageHandler?.Invoke(message); } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/DependencyAnalyzer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; using DotNet.Globbing; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.Analysis.Implementation { /// /// Abstract base class for dependency analyzers. /// public sealed class DependencyAnalyzer : IDependencyAnalyzer { private readonly IUpdateableConfigProvider _configProvider; private readonly MessageHandler _traceMessageHandler; private readonly ITypeDependencyEnumerator _typeDependencyEnumerator; private readonly object _configRefreshLock = new(); private CachingTypeDependencyValidator _typeDependencyValidator; private IAnalyzerConfig _config; private Glob[] _sourcePathExclusionGlobs; public DependencyAnalyzer( IUpdateableConfigProvider configProvider, ITypeDependencyEnumerator typeDependencyEnumerator, MessageHandler traceMessageHandler) { _configProvider = configProvider ?? throw new ArgumentNullException(nameof(configProvider)); _traceMessageHandler = traceMessageHandler; _typeDependencyEnumerator = typeDependencyEnumerator ?? throw new ArgumentNullException(nameof(typeDependencyEnumerator)); UpdateConfig(); } public AnalyzerConfigState ConfigState { get { lock (_configRefreshLock) { return _configProvider.ConfigState; } } } public Exception ConfigException { get { lock (_configRefreshLock) { return _configProvider.ConfigException; } } } public IAnalyzerConfig Config { get { lock (_configRefreshLock) { return _configProvider.Config; } } } public IEnumerable AnalyzeProject( IEnumerable sourceFilePaths, IEnumerable referencedAssemblyPaths) { if (sourceFilePaths == null) throw new ArgumentNullException(nameof(sourceFilePaths)); if (referencedAssemblyPaths == null) throw new ArgumentNullException(nameof(referencedAssemblyPaths)); if (GlobalSettings.IsToolDisabled()) return new[] {new ToolDisabledMessage()}; lock (_configRefreshLock) { return AnalyzeCore( () => GetIllegalTypeDependencies( () => _typeDependencyEnumerator.GetTypeDependencies(sourceFilePaths, referencedAssemblyPaths, _sourcePathExclusionGlobs)) ); } } public IEnumerable AnalyzeSyntaxNode(SyntaxNode syntaxNode, SemanticModel semanticModel) { if (syntaxNode == null) throw new ArgumentNullException(nameof(syntaxNode)); if (semanticModel == null) throw new ArgumentNullException(nameof(semanticModel)); lock (_configRefreshLock) { return AnalyzeCore( () => GetIllegalTypeDependencies( () => _typeDependencyEnumerator.GetTypeDependencies(syntaxNode, semanticModel, _sourcePathExclusionGlobs)) ); } } public void RefreshConfig() { lock (_configRefreshLock) { _configProvider.RefreshConfig(); UpdateConfig(); } } private void UpdateConfig() { var oldConfig = _config; _config = _configProvider.Config; if (oldConfig == _config) return; _typeDependencyValidator = CreateTypeDependencyValidator(); _sourcePathExclusionGlobs = _config.SourcePathExclusionPatterns.Select(Glob.Parse).ToArray(); } private CachingTypeDependencyValidator CreateTypeDependencyValidator() { return _configProvider.ConfigState == AnalyzerConfigState.Enabled ? new CachingTypeDependencyValidator(_configProvider.Config, _traceMessageHandler) : null; } private IEnumerable AnalyzeCore(Func> illegalTypeDependencyEnumerator) { return _configProvider.ConfigState switch { AnalyzerConfigState.NoConfig => new NoConfigFileMessage().ToEnumerable(), AnalyzerConfigState.Disabled => new ConfigDisabledMessage().ToEnumerable(), AnalyzerConfigState.ConfigError => new ConfigErrorMessage(_configProvider.ConfigException).ToEnumerable(), AnalyzerConfigState.Enabled => PerformAnalysis(illegalTypeDependencyEnumerator), _ => throw new Exception($"Unexpected ConfigState: {_configProvider.ConfigState}") }; } private static IEnumerable PerformAnalysis(Func> illegalTypeDependencyEnumerator) { return illegalTypeDependencyEnumerator().Select(i => new IllegalDependencyMessage(i.TypeDependency, i.AllowedMembers)); // TODO: AutoLowerMaxIssueCount logic should be moved to NsDepCopAnalyzer to act at the end of a compilation. // This method is called multiple times during a compilation so we don't know the final issue count here //var finalIssueCount = GetInterlocked(ref issueCount); //if (config.AutoLowerMaxIssueCount && finalIssueCount < maxIssueCount) // ConfigProvider.UpdateMaxIssueCount(finalIssueCount); } private IEnumerable GetIllegalTypeDependencies(Func> typeDependencyEnumerator) { var allDependencies = typeDependencyEnumerator() .Select(dep => (Dependency: dep, Status: _typeDependencyValidator.IsAllowedDependency(dep))); var excessIllegalDependencies = allDependencies .Where(i => !i.Status.IsAllowed) .Take(_config.MaxIssueCount + 1); foreach (var illegalDependency in excessIllegalDependencies) { yield return new IllegalTypeDependency(illegalDependency.Dependency, illegalDependency.Status.AllowedTypeNames); } _traceMessageHandler?.Invoke(GetCacheStatisticsMessage(_typeDependencyValidator)); } private static string GetCacheStatisticsMessage(ICacheStatisticsProvider i) => $"Cache hits: {i.HitCount}, misses: {i.MissCount}, efficiency (hits/all): {i.EfficiencyPercent:P}"; } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/DependencyStatus.cs ================================================ using System; namespace Codartis.NsDepCop.Analysis.Implementation { public class DependencyStatus { private DependencyStatus() { } public bool IsAllowed { get; private set; } public string[] AllowedTypeNames { get; private set; } = Array.Empty(); public static readonly DependencyStatus Allowed = new() { IsAllowed = true }; public static readonly DependencyStatus Disallowed = new() { IsAllowed = false }; public static DependencyStatus DisallowedUseOfMember(string[] allowedTypeNames) => new() { IsAllowed = false, AllowedTypeNames = allowedTypeNames }; } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/IAssemblyDependencyValidator.cs ================================================ namespace Codartis.NsDepCop.Analysis.Implementation { public interface IAssemblyDependencyValidator { DependencyStatus IsDependencyAllowed(AssemblyDependency assemblyDependency); } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/ITypeDependencyValidator.cs ================================================ namespace Codartis.NsDepCop.Analysis.Implementation { /// /// Determines whether a type-to-type dependency is allowed or not. /// internal interface ITypeDependencyValidator { /// /// Decides whether a dependency is allowed based on the rule configuration. /// /// A dependency of two types. /// True if the dependency is allowed, false otherwise. DependencyStatus IsAllowedDependency(TypeDependency typeDependency); } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/IllegalTypeDependency.cs ================================================ namespace Codartis.NsDepCop.Analysis.Implementation { public class IllegalTypeDependency { public TypeDependency TypeDependency { get; } public string[] AllowedMembers { get; } public IllegalTypeDependency(TypeDependency typeDependency, string[] allowedMembers) { TypeDependency = typeDependency; AllowedMembers = allowedMembers; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Implementation/TypeDependencyValidator.cs ================================================ using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Analysis.Implementation { /// /// Determines whether a given type-to-type dependency is allowed. /// public class TypeDependencyValidator : ITypeDependencyValidator { private readonly Dictionary _allowRules; private readonly HashSet _disallowRules; private readonly Dictionary _visibleTypesPerNamespaces; private readonly bool _childCanDependOnParentImplicitly; private readonly bool _parentCanDependOnChildImplicitly; public TypeDependencyValidator(IDependencyRules dependencyRules) { _allowRules = dependencyRules.AllowRules; _disallowRules = dependencyRules.DisallowRules; _visibleTypesPerNamespaces = dependencyRules.VisibleTypesByNamespace; _childCanDependOnParentImplicitly = dependencyRules.ChildCanDependOnParentImplicitly; _parentCanDependOnChildImplicitly = dependencyRules.ParentCanDependOnChildImplicitly; } /// /// Decides whether a dependency is allowed based on the rule configuration. /// /// A dependency of two types. /// True if the dependency is allowed, false otherwise. public virtual DependencyStatus IsAllowedDependency(TypeDependency typeDependency) { // Inside a namespace all dependencies are allowed. if (typeDependency.FromNamespaceName == typeDependency.ToNamespaceName) return DependencyStatus.Allowed; // These namespace names are coming from a compiler so we don't have to validate them. var fromNamespace = new Domain(typeDependency.FromNamespaceName, validate: false); var toNamespace = new Domain(typeDependency.ToNamespaceName, validate: false); var disallowRule = GetDisallowRule(fromNamespace, toNamespace); if (disallowRule != null) return DependencyStatus.Disallowed; if (IsAllowedBecauseChildCanDependOnParent(fromNamespace, toNamespace)) return DependencyStatus.Allowed; if (IsAllowedBecauseParentCanDependOnChild(fromNamespace, toNamespace)) return DependencyStatus.Allowed; var allowRule = GetMostSpecificAllowRule(fromNamespace, toNamespace); if (allowRule == null) return DependencyStatus.Disallowed; TypeNameSet visibleMembers = GetVisibleMembers(allowRule, toNamespace); if (visibleMembers == null || visibleMembers.Count == 0) return DependencyStatus.Allowed; bool isUsingVisibleMember = visibleMembers.Contains(typeDependency.ToTypeName); return isUsingVisibleMember ? DependencyStatus.Allowed : DependencyStatus.DisallowedUseOfMember(visibleMembers.ToArray()); } private bool IsAllowedBecauseChildCanDependOnParent(Domain fromNamespace, Domain toNamespace) { return _childCanDependOnParentImplicitly && fromNamespace.IsSubDomain(toNamespace); } private bool IsAllowedBecauseParentCanDependOnChild(Domain fromNamespace, Domain toNamespace) { return _parentCanDependOnChildImplicitly && toNamespace.IsSubDomain(fromNamespace); } private DependencyRule GetMostSpecificAllowRule(Domain from, Domain to) { return _allowRules.Keys .Where(i => i.From.Matches(from) && i.To.Matches(to)) .MaxByOrDefault(i => i.From.GetMatchRelevance(from)); } private DependencyRule GetDisallowRule(Domain from, Domain to) { return _disallowRules .FirstOrDefault(i => i.From.Matches(from) && i.To.Matches(to)); } private TypeNameSet GetVisibleMembers(DependencyRule allowRule, Domain targetNamespace) { TypeNameSet allowedTypeNameSet; if (_allowRules.TryGetValue(allowRule, out allowedTypeNameSet) && allowedTypeNameSet != null && allowedTypeNameSet.Any()) return allowedTypeNameSet; if (_visibleTypesPerNamespaces.TryGetValue(targetNamespace, out allowedTypeNameSet)) return allowedTypeNameSet; return null; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/AnalyzerMessageBase.cs ================================================ namespace Codartis.NsDepCop.Analysis.Messages { /// /// Abstract base class for messages returned by the dependency analyzer. /// public abstract class AnalyzerMessageBase { } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/ConfigDisabledMessage.cs ================================================ namespace Codartis.NsDepCop.Analysis.Messages { /// /// A message indicating that analysis was disabled in the config file. /// public sealed class ConfigDisabledMessage : AnalyzerMessageBase { } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/ConfigErrorMessage.cs ================================================ using System; namespace Codartis.NsDepCop.Analysis.Messages { /// /// A message that describes a config exception. /// public sealed class ConfigErrorMessage : AnalyzerMessageBase { public Exception Exception { get; } public ConfigErrorMessage(Exception exception) { Exception = exception; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/IllegalAssemblyDependencyMessage.cs ================================================ namespace Codartis.NsDepCop.Analysis.Messages { public sealed class IllegalAssemblyDependencyMessage : AnalyzerMessageBase { public AssemblyDependency IllegalAssemblyDependency { get; } public IllegalAssemblyDependencyMessage(AssemblyDependency illegalAssemblyDependency) { IllegalAssemblyDependency = illegalAssemblyDependency; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/IllegalDependencyMessage.cs ================================================ using System; namespace Codartis.NsDepCop.Analysis.Messages { /// /// A message containing an illegal type dependency. /// public sealed class IllegalDependencyMessage : AnalyzerMessageBase { public TypeDependency IllegalDependency { get; } public string[] AllowedMemberNames { get; } = Array.Empty(); public IllegalDependencyMessage(TypeDependency illegalDependency, string[] allowedMemberNames) { IllegalDependency = illegalDependency; AllowedMemberNames = allowedMemberNames; } } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/NoConfigFileMessage.cs ================================================ namespace Codartis.NsDepCop.Analysis.Messages { /// /// A message indicating that no config file was found for a project or location. /// public sealed class NoConfigFileMessage : AnalyzerMessageBase { } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/Messages/ToolDisabledMessage.cs ================================================ namespace Codartis.NsDepCop.Analysis.Messages { /// /// A message indicating that analysis was disabled with environment variable. /// public sealed class ToolDisabledMessage : AnalyzerMessageBase { } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/SourceSegment.cs ================================================ using System; namespace Codartis.NsDepCop.Analysis { /// /// Describes a certain segment of a source file. Immutable. /// [Serializable] public struct SourceSegment { /// /// The line number of the segment's start (1-based). /// public int StartLine { get; } /// /// The column number of the segment's start (1-based). /// public int StartColumn { get; } /// /// The line number of the segment's end (1-based). /// public int EndLine { get; } /// /// The column number of the segment's end (1-based). /// public int EndColumn { get; } /// /// The text of the segment. /// public string Text { get; } /// /// The full path of the source file. /// public string Path { get; } public SourceSegment(int startLine, int startColumn, int endLine, int endColumn, string text, string path) { StartLine = startLine; StartColumn = startColumn; EndLine = endLine; EndColumn = endColumn; Text = text; Path = path; } public override string ToString() => $"{Path} ({StartLine},{StartColumn},{EndLine},{EndColumn})"; } } ================================================ FILE: source/NsDepCop.Analyzer/Analysis/TypeDependency.cs ================================================ using System; namespace Codartis.NsDepCop.Analysis { /// /// Describes a dependency between two types at a source segment. /// Immutable. /// [Serializable] public struct TypeDependency { /// /// The namespace of the referencing type. /// public string FromNamespaceName { get; } /// /// The name of the referencing type. /// public string FromTypeName { get; } /// /// The namespace of the referenced type. /// public string ToNamespaceName { get; } /// /// The name of the referenced type. /// public string ToTypeName { get; } /// /// The source segment where the dependency was found. /// public SourceSegment SourceSegment { get; } public TypeDependency(string fromNamespaceName, string fromTypeName, string toNamespaceName, string toTypeName, SourceSegment sourceSegment) { if (string.IsNullOrWhiteSpace(fromTypeName)) throw new ArgumentException("Should not be null or whitespace.", nameof(fromTypeName)); if (string.IsNullOrWhiteSpace(toTypeName)) throw new ArgumentException("Should not be null or whitespace.", nameof(toTypeName)); FromNamespaceName = fromNamespaceName ?? throw new ArgumentNullException(nameof(fromNamespaceName)); FromTypeName = fromTypeName; ToNamespaceName = toNamespaceName ?? throw new ArgumentNullException(nameof(toNamespaceName)); ToTypeName = toTypeName; SourceSegment = sourceSegment; } // TODO: should print source segment too public override string ToString() => $"{FromNamespaceName}.{FromTypeName}->{ToNamespaceName}.{ToTypeName}"; // TODO: should consider source segment too public bool Equals(TypeDependency other) { return string.Equals(FromNamespaceName, other.FromNamespaceName) && string.Equals(FromTypeName, other.FromTypeName) && string.Equals(ToNamespaceName, other.ToNamespaceName) && string.Equals(ToTypeName, other.ToTypeName); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is TypeDependency && Equals((TypeDependency) obj); } // TODO: should consider source segment too public override int GetHashCode() { unchecked { var hashCode = (FromNamespaceName != null ? FromNamespaceName.GetHashCode() : 0); hashCode = (hashCode*397) ^ (FromTypeName != null ? FromTypeName.GetHashCode() : 0); hashCode = (hashCode*397) ^ (ToNamespaceName != null ? ToNamespaceName.GetHashCode() : 0); hashCode = (hashCode*397) ^ (ToTypeName != null ? ToTypeName.GetHashCode() : 0); return hashCode; } } public static bool operator ==(TypeDependency left, TypeDependency right) { return left.Equals(right); } public static bool operator !=(TypeDependency left, TypeDependency right) { return !left.Equals(right); } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/AnalyzerConfigState.cs ================================================ namespace Codartis.NsDepCop.Config { /// /// Enumerates the possible states of an analyzer's config. /// public enum AnalyzerConfigState { Enabled, Disabled, NoConfig, ConfigError } } ================================================ FILE: source/NsDepCop.Analyzer/Config/ConfigDefaults.cs ================================================ namespace Codartis.NsDepCop.Config { /// /// Defines the default values of the config properties. /// public static class ConfigDefaults { public const int InheritanceDepth = 0; public const bool IsEnabled = true; public const bool CheckAssemblyDependencies = false; public const int MaxIssueCount = 100; public const bool AutoLowerMaxIssueCount = false; public const bool ChildCanDependOnParentImplicitly = false; public const bool ParentCanDependOnChildImplicitly = false; } } ================================================ FILE: source/NsDepCop.Analyzer/Config/DependencyRule.cs ================================================ using System; using System.Text; namespace Codartis.NsDepCop.Config { /// /// Represents a dependency rule between two domain specifications. Immutable. /// /// /// The 'From' domain specification depends on the 'To' domain specification. /// A domain specification can represent more than just a single domain (eg. a subtree of namespaces). /// [Serializable] public class DependencyRule { /// /// The dependency points from this domain to the other. /// public DomainSpecification From { get; } /// /// The dependency points into this domain. /// public DomainSpecification To { get; } /// /// Initializes a new instance. /// /// The source of the dependency. /// The target of the dependency. public DependencyRule(DomainSpecification from, DomainSpecification to) { From = from ?? throw new ArgumentNullException(nameof(from)); To = to ?? throw new ArgumentNullException(nameof(to)); } /// /// Initializes a new instance by converting the string parameters to NamespaceSpecification objects. /// /// A namespace specification in string format. The source of the dependency. /// A namespace specification in string format. The target of the dependency. public DependencyRule(string from, string to) : this(DomainSpecificationParser.Parse(from), DomainSpecificationParser.Parse(to)) { } /// /// Returns the string representation of a namespace dependency. /// /// The string representation of a namespace dependency. public override string ToString() { var builder = new StringBuilder(); builder.Append(From); builder.Append("->"); builder.Append(To); return builder.ToString(); } public bool Equals(DependencyRule other) { return Equals(From, other.From) && Equals(To, other.To); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((DependencyRule)obj); } public override int GetHashCode() { unchecked { return ((From != null ? From.GetHashCode() : 0) * 397) ^ (To != null ? To.GetHashCode() : 0); } } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Domain.cs ================================================ using System; using System.Linq; namespace Codartis.NsDepCop.Config { /// /// Represents a domain, eg. 'A.B'. Immutable. /// /// /// The global domain is also a domain and it's represented by '.' (a dot) /// [Serializable] public sealed class Domain : DomainSpecification { public const string RootDomainMarker = "."; /// /// Represents the global domain. /// public static readonly Domain GlobalDomain = new Domain(RootDomainMarker); /// /// Creates a new instance from a string representation. /// /// The string representation of a domain. /// True means validate the input string. public Domain(string domainAsString, bool validate = true) : base(Normalize(domainAsString), validate, IsValid) { } public override int GetMatchRelevance(Domain domain) { return this == domain ? int.MaxValue : 0; } /// /// Determines whether this domain is a sub-domain of the given other one. /// /// The domain to test whether it's a parent of the current domain. /// True if this domain is a sub-domain of the given one, false otherwise. public bool IsSubDomain(Domain parentCandidate) { if (this == GlobalDomain) return false; if (parentCandidate == GlobalDomain) return true; var parentPrefix = parentCandidate.Value + DomainPartSeparator; return Value.StartsWith(parentPrefix); } /// /// Validates that the given string represents a valid domain specification. /// /// A domain specification in string format. /// True if the given string represents a valid domain specification. public static bool IsValid(string domainAsString) { if (domainAsString == RootDomainMarker) return true; if (domainAsString.Any(c => c == WildcardDomain.AnyDomainMarker[0] || c == WildcardDomain.SingleDomainMarker[0])) return false; var pieces = domainAsString.Split(new[] { DomainPartSeparator }, StringSplitOptions.None); return pieces.All(i => !string.IsNullOrWhiteSpace(i)); } /// /// Converts the input string into a standard representation (removes ambiguities). /// /// A domain specification in string format. /// The standard representation of the given domain specification string. private static string Normalize(string domainAsString) { // Global domain representations: // Roslyn: "" // NRefactory: "" (empty string) // NsDepCop: "." (dot) if (domainAsString == "" || domainAsString == "") domainAsString = RootDomainMarker; return domainAsString; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/DomainSpecification.cs ================================================ using System; namespace Codartis.NsDepCop.Config { /// /// Represents a domain or a domain pattern. Immutable. /// [Serializable] public abstract class DomainSpecification { public const char DomainPartSeparator = '.'; /// /// The domain specification stored as a string. /// protected readonly string Value; /// /// Initializes a new instance. Also validates the input format if needed. /// /// The string representation of the domain specification. /// True means validate the input string. /// A delegate that validates the input string. protected DomainSpecification(string value, bool validate, Func validator) { if (value == null) throw new ArgumentNullException(nameof(value)); if (validate && validator != null && !validator.Invoke(value)) throw new FormatException($"'{value}' is not a valid {GetType().Name}."); Value = value; } /// /// Returns a number indicating how well this domain specification matches a concrete domain. /// /// A domain. /// Zero means no match. Higher value means more relevant match. public abstract int GetMatchRelevance(Domain domain); /// /// Returns a value indicating whether this domain specification matches a given domain. /// /// A domain. /// True if this domain specification matches the given domain. public bool Matches(Domain domain) => GetMatchRelevance(domain) > 0; public override string ToString() => Value; public bool Equals(DomainSpecification other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return String.Equals(Value, other.Value); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((DomainSpecification)obj); } public override int GetHashCode() { return (Value != null ? Value.GetHashCode() : 0); } public static bool operator ==(DomainSpecification left, DomainSpecification right) { return Equals(left, right); } public static bool operator !=(DomainSpecification left, DomainSpecification right) { return !Equals(left, right); } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/DomainSpecificationParser.cs ================================================ namespace Codartis.NsDepCop.Config { /// /// Converts a string to a domain specification. /// public static class DomainSpecificationParser { /// /// Creates a domain specification from a string representation. /// /// A domain specification in string format. /// The domain specification created from the given string. /// /// Throws an exception if the string cannot be parsed. /// public static DomainSpecification Parse(string domainSpecificationAsString) { if (domainSpecificationAsString.StartsWith(RegexDomain.Delimiter) && domainSpecificationAsString.EndsWith(RegexDomain.Delimiter)) return new RegexDomain(domainSpecificationAsString); if (domainSpecificationAsString.Contains(WildcardDomain.SingleDomainMarker) || domainSpecificationAsString.Contains(WildcardDomain.AnyDomainMarker)) return new WildcardDomain(domainSpecificationAsString); return new Domain(domainSpecificationAsString); } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Factory/ConfigProviderFactory.cs ================================================ using Codartis.NsDepCop.Config.Implementation; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Factory { /// /// Creates config provider objects. /// public class ConfigProviderFactory : IConfigProviderFactory { private readonly MessageHandler _traceMessageHandler; public ConfigProviderFactory(MessageHandler traceMessageHandler) { _traceMessageHandler = traceMessageHandler; } public IUpdateableConfigProvider CreateFromXmlConfigFile(string configFilePath) { return new XmlFileConfigProvider(configFilePath, _traceMessageHandler); } public IUpdateableConfigProvider CreateFromMultiLevelXmlConfigFile(string folderPath) { return new MultiLevelXmlFileConfigProvider(folderPath, _traceMessageHandler); } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/IAnalyzerConfig.cs ================================================ using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config { /// /// The configuration for an analyzer. /// public interface IAnalyzerConfig : IDependencyRules, IDiagnosticSupport { /// /// Gets a value indicating whether analysis is enabled. /// bool IsEnabled { get; } /// /// Gets the max number of issues reported. /// int MaxIssueCount { get; } /// /// Gets a value indicating whether MaxIssueCount is automatically updated whenever a lower count is achieved. /// bool AutoLowerMaxIssueCount { get; } /// /// Gets an array of file path exclusions patterns. Source files that match any of these patterns won't be analyzed. /// /// /// Uses https://github.com/dazinator/DotNet.Glob patterns. /// string[] SourcePathExclusionPatterns { get; } /// /// Gets a value indicating whether the assembly dependencies check should be performed. /// bool CheckAssemblyDependencies { get; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/IConfigProvider.cs ================================================ using System; namespace Codartis.NsDepCop.Config { /// /// Provides config info and config state from some kind of repository. /// public interface IConfigProvider { /// /// Gets the config used by the analyzers. /// IAnalyzerConfig Config { get; } /// /// Gets the state of the analyzer's config. /// AnalyzerConfigState ConfigState { get; } /// /// Gets the config exception or null if there was no exception. /// Exception ConfigException { get; } /// /// Gets the config location as a string. /// string ConfigLocation { get; } /// /// Reloads the config from its repository. /// void RefreshConfig(); } } ================================================ FILE: source/NsDepCop.Analyzer/Config/IConfigProviderFactory.cs ================================================ namespace Codartis.NsDepCop.Config { /// /// Creates config provider objects. /// public interface IConfigProviderFactory { /// /// Creates a config provider for an xml config file. /// /// The full path of an xml config file. /// A config provider. IUpdateableConfigProvider CreateFromXmlConfigFile(string configFilePath); /// /// Creates a multi level config provider for the xml config files found in the specified folder and its parents. /// /// The full path of the folder where the search for config files begins. /// A config provider. IUpdateableConfigProvider CreateFromMultiLevelXmlConfigFile(string folderPath); } } ================================================ FILE: source/NsDepCop.Analyzer/Config/IDependencyRules.cs ================================================ using System.Collections.Generic; namespace Codartis.NsDepCop.Config { /// /// Describes dependency rules. /// public interface IDependencyRules { /// /// True means that all child namespaces can depend on any of their parent namespaces without an explicit Allowed rule. /// True is in line with how C# type resolution works: it searches parent namespaces without an explicit using statement. /// False means that all dependencies must be explicitly allowed with a rule. /// False is the default for backward compatibility. /// bool ChildCanDependOnParentImplicitly { get; } bool ParentCanDependOnChildImplicitly { get; } /// /// Dictionary of allowed dependency rules. The key is a namespace dependency rule, /// the value is a set of type names defined in the target namespace and visible for the source namespace(s). /// Dictionary AllowRules { get; } /// /// The set of disallowed dependency rules. /// HashSet DisallowRules { get; } /// /// Dictionary of visible types by target namespace. The key is the name of a namespace, /// the value is a set of type names defined in the namespace and visible outside of the namespace. /// Dictionary VisibleTypesByNamespace { get; } HashSet AllowedAssemblyRules { get; } HashSet DisallowedAssemblyRules { get; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/IUpdateableConfigProvider.cs ================================================ namespace Codartis.NsDepCop.Config { /// /// Provides config info and config state. Some config data can also be updated. /// public interface IUpdateableConfigProvider: IConfigProvider { /// /// Updates the MaxIssueCount config parameter to the given value and persists the changes. /// /// The new value for MaxIssueCount. void UpdateMaxIssueCount(int newValue); } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/AnalyzerConfig.cs ================================================ using System; using System.Collections.Generic; namespace Codartis.NsDepCop.Config.Implementation { /// /// Describes the config for a dependency analyzer. Immutable. /// [Serializable] internal class AnalyzerConfig : IAnalyzerConfig { public bool IsEnabled { get; } public string[] SourcePathExclusionPatterns { get; } public bool CheckAssemblyDependencies { get; } public bool ChildCanDependOnParentImplicitly { get; } public bool ParentCanDependOnChildImplicitly { get; } public Dictionary AllowRules { get; } public HashSet DisallowRules { get; } public Dictionary VisibleTypesByNamespace { get; } public HashSet AllowedAssemblyRules { get; } public HashSet DisallowedAssemblyRules { get; } public int MaxIssueCount { get; } public bool AutoLowerMaxIssueCount { get; } public AnalyzerConfig( bool isEnabled, string[] sourcePathExclusionPatterns, bool checkAssemblyDependencies, bool childCanDependOnParentImplicitly, bool parentCanDependOnChildImplicitly, Dictionary allowRules, HashSet disallowRules, Dictionary visibleTypesByNamespace, HashSet allowedAssemblyRules, HashSet disallowedAssemblyRules, int maxIssueCount, bool autoLowerMaxIssueCount) { IsEnabled = isEnabled; SourcePathExclusionPatterns = sourcePathExclusionPatterns; CheckAssemblyDependencies = checkAssemblyDependencies; ChildCanDependOnParentImplicitly = childCanDependOnParentImplicitly; ParentCanDependOnChildImplicitly = parentCanDependOnChildImplicitly; AllowRules = allowRules; DisallowRules = disallowRules; VisibleTypesByNamespace = visibleTypesByNamespace; AllowedAssemblyRules = allowedAssemblyRules; DisallowedAssemblyRules = disallowedAssemblyRules; MaxIssueCount = maxIssueCount; AutoLowerMaxIssueCount = autoLowerMaxIssueCount; } public IEnumerable ToStrings() { yield return $"IsEnabled={IsEnabled}"; yield return $"SourcePathExclusionPatterns={string.Join(",", SourcePathExclusionPatterns)}"; yield return $"ChildCanDependOnParentImplicitly={ChildCanDependOnParentImplicitly}"; foreach (var s in AllowRules.ToStrings()) yield return s; foreach (var s in DisallowRules.ToStrings()) yield return s; foreach (var s in AllowedAssemblyRules.ToStrings()) yield return s; foreach (var s in DisallowedAssemblyRules.ToStrings()) yield return s; foreach (var s in VisibleTypesByNamespace.ToStrings()) yield return s; yield return $"MaxIssueCount={MaxIssueCount}"; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/AnalyzerConfigBuilder.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Implementation { /// /// Builds analyzer config objects. /// public class AnalyzerConfigBuilder { public int? InheritanceDepth { get; private set; } public bool? IsEnabled { get; private set; } public List SourcePathExclusionPatterns { get; private set; } public bool? CheckAssemblyDependencies { get; private set; } public bool? ChildCanDependOnParentImplicitly { get; private set; } public bool? ParentCanDependOnChildImplicitly { get; private set; } public Dictionary AllowRules { get; } public HashSet DisallowRules { get; } public Dictionary VisibleTypesByNamespace { get; } public HashSet AllowedAssemblyRules { get; } public HashSet DisallowedAssemblyRules { get; } public int? MaxIssueCount { get; private set; } public bool? AutoLowerMaxIssueCount { get; private set; } public AnalyzerConfigBuilder() { SourcePathExclusionPatterns = new List(); AllowRules = new Dictionary(); DisallowRules = new HashSet(); VisibleTypesByNamespace = new Dictionary(); AllowedAssemblyRules = new HashSet(); DisallowedAssemblyRules = new HashSet(); } public IAnalyzerConfig ToAnalyzerConfig() { return new AnalyzerConfig( IsEnabled ?? ConfigDefaults.IsEnabled, SourcePathExclusionPatterns.ToArray(), CheckAssemblyDependencies ?? ConfigDefaults.CheckAssemblyDependencies, ChildCanDependOnParentImplicitly ?? ConfigDefaults.ChildCanDependOnParentImplicitly, ParentCanDependOnChildImplicitly ?? ConfigDefaults.ParentCanDependOnChildImplicitly, AllowRules, DisallowRules, VisibleTypesByNamespace, AllowedAssemblyRules, DisallowedAssemblyRules, MaxIssueCount ?? ConfigDefaults.MaxIssueCount, AutoLowerMaxIssueCount ?? ConfigDefaults.AutoLowerMaxIssueCount ); } public AnalyzerConfigBuilder Combine(AnalyzerConfigBuilder analyzerConfigBuilder) { // Note that InheritanceDepth is not combined. SetIsEnabled(analyzerConfigBuilder.IsEnabled); AddSourcePathExclusionPatterns(analyzerConfigBuilder.SourcePathExclusionPatterns); SetCheckAssemblyDependencies(analyzerConfigBuilder.CheckAssemblyDependencies); SetChildCanDependOnParentImplicitly(analyzerConfigBuilder.ChildCanDependOnParentImplicitly); SetParentCanDependOnChildImplicitly(analyzerConfigBuilder.ParentCanDependOnChildImplicitly); AddAllowRules(analyzerConfigBuilder.AllowRules); AddDisallowRules(analyzerConfigBuilder.DisallowRules); AddVisibleTypesByNamespace(analyzerConfigBuilder.VisibleTypesByNamespace); AddAllowedAssemblyRules(analyzerConfigBuilder.AllowedAssemblyRules); AddDisallowedAssemblyRules(analyzerConfigBuilder.DisallowedAssemblyRules); SetMaxIssueCount(analyzerConfigBuilder.MaxIssueCount); SetAutoLowerMaxIssueCount(analyzerConfigBuilder.AutoLowerMaxIssueCount); return this; } public AnalyzerConfigBuilder SetInheritanceDepth(int? inheritanceDepth) { if (inheritanceDepth.HasValue) InheritanceDepth = inheritanceDepth; return this; } public AnalyzerConfigBuilder SetIsEnabled(bool? isEnabled) { if (isEnabled.HasValue) IsEnabled = isEnabled; return this; } public AnalyzerConfigBuilder AddSourcePathExclusionPatterns(IEnumerable sourcePathExclusionPatterns) { if (sourcePathExclusionPatterns != null) SourcePathExclusionPatterns.AddRange(sourcePathExclusionPatterns); return this; } public AnalyzerConfigBuilder SetCheckAssemblyDependencies(bool? checkAssemblyDependencies) { if (checkAssemblyDependencies.HasValue) CheckAssemblyDependencies = checkAssemblyDependencies; return this; } public AnalyzerConfigBuilder MakePathsRooted(string rootPath) { if (!Path.IsPathRooted(rootPath)) throw new ArgumentException($"Rooted path expected: {rootPath}"); SourcePathExclusionPatterns = SourcePathExclusionPatterns.Select(i => ToRootedPath(rootPath, i)).ToList(); return this; } public AnalyzerConfigBuilder SetChildCanDependOnParentImplicitly(bool? childCanDependOnParentImplicitly) { if (childCanDependOnParentImplicitly.HasValue) ChildCanDependOnParentImplicitly = childCanDependOnParentImplicitly; return this; } public AnalyzerConfigBuilder SetParentCanDependOnChildImplicitly(bool? parentCanDependOnChildImplicitly) { if (parentCanDependOnChildImplicitly.HasValue) ParentCanDependOnChildImplicitly = parentCanDependOnChildImplicitly; return this; } public AnalyzerConfigBuilder AddAllowRule(DependencyRule dependencyRule, TypeNameSet typeNameSet = null) { AllowRules.AddOrUnion(dependencyRule, typeNameSet); return this; } private AnalyzerConfigBuilder AddAllowRules(IEnumerable> allowRules) { foreach (var keyValuePair in allowRules) AddAllowRule(keyValuePair.Key, keyValuePair.Value); return this; } public AnalyzerConfigBuilder AddDisallowRule(DependencyRule dependencyRule) { DisallowRules.Add(dependencyRule); return this; } private AnalyzerConfigBuilder AddDisallowRules(IEnumerable disallowRules) { foreach (var dependencyRule in disallowRules) AddDisallowRule(dependencyRule); return this; } public AnalyzerConfigBuilder AddAllowedAssemblyRule(DependencyRule assemblyDependencyRule) { AllowedAssemblyRules.Add(assemblyDependencyRule); return this; } private AnalyzerConfigBuilder AddAllowedAssemblyRules(IEnumerable assemblyDependencyRules) { foreach (var assemblyDependencyRule in assemblyDependencyRules) AddAllowedAssemblyRule(assemblyDependencyRule); return this; } public AnalyzerConfigBuilder AddDisallowedAssemblyRule(DependencyRule assemblyDependencyRule) { DisallowedAssemblyRules.Add(assemblyDependencyRule); return this; } private AnalyzerConfigBuilder AddDisallowedAssemblyRules(IEnumerable assemblyDependencyRules) { foreach (var assemblyDependencyRule in assemblyDependencyRules) AddDisallowedAssemblyRule(assemblyDependencyRule); return this; } public AnalyzerConfigBuilder AddVisibleTypesByNamespace(Domain domain, TypeNameSet typeNameSet) { VisibleTypesByNamespace.AddOrUnion(domain, typeNameSet); return this; } private AnalyzerConfigBuilder AddVisibleTypesByNamespace(IEnumerable> visibleTypesByNamespace) { foreach (var keyValuePair in visibleTypesByNamespace) AddVisibleTypesByNamespace(keyValuePair.Key, keyValuePair.Value); return this; } public AnalyzerConfigBuilder SetMaxIssueCount(int? maxIssueCount) { if (maxIssueCount.HasValue) MaxIssueCount = maxIssueCount; return this; } public AnalyzerConfigBuilder SetAutoLowerMaxIssueCount(bool? autoLowerMaxIssueCount) { if (autoLowerMaxIssueCount.HasValue) AutoLowerMaxIssueCount = autoLowerMaxIssueCount; return this; } public IEnumerable ToStrings() { if (InheritanceDepth.HasValue) yield return $"InheritanceDepth={InheritanceDepth}"; if (IsEnabled.HasValue) yield return $"IsEnabled={IsEnabled}"; if (SourcePathExclusionPatterns != null) yield return $"SourcePathExclusionPatterns={string.Join(";", SourcePathExclusionPatterns)}"; if (CheckAssemblyDependencies.HasValue) yield return $"CheckAssemblyDependencies={CheckAssemblyDependencies}"; if (ChildCanDependOnParentImplicitly.HasValue) yield return $"ChildCanDependOnParentImplicitly={ChildCanDependOnParentImplicitly}"; if (ParentCanDependOnChildImplicitly.HasValue) yield return $"ParentCanDependOnChildImplicitly={ParentCanDependOnChildImplicitly}"; if (AllowRules.Any()) foreach (var s in AllowRules.ToStrings()) yield return s; if (DisallowRules.Any()) foreach (var s in DisallowRules.ToStrings()) yield return s; if (AllowedAssemblyRules.Any()) foreach (var s in AllowedAssemblyRules.ToStrings()) yield return s; if (DisallowedAssemblyRules.Any()) foreach (var s in DisallowedAssemblyRules.ToStrings()) yield return s; if (VisibleTypesByNamespace.Any()) foreach (var s in VisibleTypesByNamespace.ToStrings()) yield return s; if (MaxIssueCount.HasValue) yield return $"MaxIssueCount={MaxIssueCount}"; } private static string ToRootedPath(string rootPath, string path) { if (rootPath == null || Path.IsPathRooted(path)) return path; return Path.Combine(rootPath, path); } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/ConfigLoadResult.cs ================================================ using System; using System.Collections.Generic; namespace Codartis.NsDepCop.Config.Implementation { /// /// Describes the result of a config load operation. /// public readonly struct ConfigLoadResult { public AnalyzerConfigState ConfigState { get; } public AnalyzerConfigBuilder ConfigBuilder { get; } public IAnalyzerConfig Config { get; } public Exception ConfigException { get; } private ConfigLoadResult(AnalyzerConfigState configState, AnalyzerConfigBuilder configBuilder, IAnalyzerConfig config, Exception configException) { ConfigState = configState; ConfigBuilder = configBuilder; Config = config; ConfigException = configException; } public static ConfigLoadResult CreateWithError(Exception configException) { if (configException == null) throw new ArgumentNullException(nameof(configException)); return new ConfigLoadResult(AnalyzerConfigState.ConfigError, null, null, configException); } public static ConfigLoadResult CreateWithNoConfig() { return new ConfigLoadResult(AnalyzerConfigState.NoConfig, null, null, null); } public static ConfigLoadResult CreateWithConfig(AnalyzerConfigBuilder configBuilder) { if (configBuilder == null) throw new ArgumentNullException(nameof(configBuilder)); var config = configBuilder.ToAnalyzerConfig(); return config.IsEnabled ? new ConfigLoadResult(AnalyzerConfigState.Enabled, configBuilder, config, null) : new ConfigLoadResult(AnalyzerConfigState.Disabled, null, null, null); } public IEnumerable ToStrings() { yield return $"ConfigState={ConfigState}"; if (ConfigException != null) yield return $"ConfigException={ConfigException}"; if (Config != null) foreach (var s in Config.ToStrings()) yield return s; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/ConfigProviderBase.cs ================================================ using System; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Implementation { /// /// Abstract base class for config provider implementations. /// /// /// Uses locking to ensure that no property can be read while refreshing the config. /// public abstract class ConfigProviderBase : IUpdateableConfigProvider { private bool _isInitialized; private ConfigLoadResult _configLoadResult; /// /// This lock ensures that no property can be read while loading or saving the config. /// protected readonly object SaveLoadLockObject = new(); protected MessageHandler TraceMessageHandler { get; } protected ConfigProviderBase(MessageHandler traceMessageHandler) { TraceMessageHandler = traceMessageHandler; } public abstract string ConfigLocation { get; } public IAnalyzerConfig Config { get { lock (SaveLoadLockObject) { EnsureInitialized(); return _configLoadResult.Config; } } } public AnalyzerConfigBuilder ConfigBuilder { get { lock (SaveLoadLockObject) { EnsureInitialized(); return _configLoadResult.ConfigBuilder; } } } public AnalyzerConfigState ConfigState { get { lock (SaveLoadLockObject) { EnsureInitialized(); return _configLoadResult.ConfigState; } } } public Exception ConfigException { get { lock (SaveLoadLockObject) { EnsureInitialized(); return _configLoadResult.ConfigException; } } } public void RefreshConfig() { lock (SaveLoadLockObject) { EnsureInitialized(); _configLoadResult = RefreshConfigCore(); } } public void UpdateMaxIssueCount(int newValue) { lock (SaveLoadLockObject) { EnsureInitialized(); if (_configLoadResult.ConfigState != AnalyzerConfigState.Enabled) throw new InvalidOperationException($"Cannot {nameof(UpdateMaxIssueCount)} in {_configLoadResult.ConfigState} state."); _configLoadResult = UpdateMaxIssueCountCore(newValue); } } protected abstract ConfigLoadResult LoadConfigCore(); protected abstract ConfigLoadResult RefreshConfigCore(); protected abstract ConfigLoadResult UpdateMaxIssueCountCore(int newValue); protected void EnsureInitialized() { lock (SaveLoadLockObject) { if (_isInitialized) return; _isInitialized = true; _configLoadResult = LoadConfigCore(); } } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/FileConfigProviderBase.cs ================================================ using System; using System.IO; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Implementation { /// /// Abstract base class for file based config implementations. /// Reloads only if the file changed. /// /// /// Base class ensures that all operations are executed in an atomic way so no extra locking needed. /// public abstract class FileConfigProviderBase : ConfigProviderBase { private bool _configFileExists; private DateTime _configLastLoadUtc; private ConfigLoadResult _lastConfigLoadResult; protected string ConfigFilePath { get; } protected FileConfigProviderBase(string configFilePath, MessageHandler traceMessageHandler) : base(traceMessageHandler) { ConfigFilePath = configFilePath; } public override string ConfigLocation => ConfigFilePath; public int InheritanceDepth => ConfigBuilder?.InheritanceDepth ?? ConfigDefaults.InheritanceDepth; public bool HasConfigFileChanged() { return ConfigFileCreatedOrDeleted() || ConfigFileModifiedSinceLastLoad(); } protected override ConfigLoadResult LoadConfigCore() { _lastConfigLoadResult = LoadConfigFromFile(); return _lastConfigLoadResult; } protected override ConfigLoadResult RefreshConfigCore() { if (!HasConfigFileChanged()) return _lastConfigLoadResult; LogTraceMessage($"Refreshing config {this}."); return LoadConfigCore(); } private ConfigLoadResult LoadConfigFromFile() { try { _configFileExists = File.Exists(ConfigFilePath); if (!_configFileExists) return ConfigLoadResult.CreateWithNoConfig(); _configLastLoadUtc = DateTime.UtcNow; var configBuilder = CreateConfigBuilderFromFile(ConfigFilePath) .MakePathsRooted(Path.GetDirectoryName(ConfigFilePath)); return ConfigLoadResult.CreateWithConfig(configBuilder); } catch (Exception e) { LogTraceMessage($"BuildConfig exception: {e}"); return ConfigLoadResult.CreateWithError(e); } } protected abstract AnalyzerConfigBuilder CreateConfigBuilderFromFile(string configFilePath); private bool ConfigFileCreatedOrDeleted() { return _configFileExists != File.Exists(ConfigFilePath); } private bool ConfigFileModifiedSinceLastLoad() { return File.Exists(ConfigFilePath) && _configLastLoadUtc < File.GetLastWriteTimeUtc(ConfigFilePath); } private void LogTraceMessage(string message) => TraceMessageHandler?.Invoke(message); } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/MultiLevelXmlFileConfigProvider.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Implementation { /// /// Traverses the source tree and reads one or multiple config files to create a composite config. /// Starts from the specified folder and traverses the folder tree upwards till the root or the max inheritance level is reached. /// /// /// Base class ensures that all operations are executed in an atomic way so no extra locking needed. /// public sealed class MultiLevelXmlFileConfigProvider : ConfigProviderBase { private ConfigLoadResult _lastConfigLoadResult; private int _lastInheritanceDepth; /// /// The collection of all file config providers that must be composed to a single config. /// /// /// The list is sorted from the project folder's config to increasingly farther configs in the folder tree. /// The configs must be combined in reverse order (from the farthest to the closest). /// private List _fileConfigProviders; private string ProjectFolder { get; } public MultiLevelXmlFileConfigProvider(string projectFolder, MessageHandler traceMessageHandler) : base(traceMessageHandler) { ProjectFolder = projectFolder; } public override string ConfigLocation => ProjectFolder; public int InheritanceDepth { get { lock (SaveLoadLockObject) { EnsureInitialized(); return _fileConfigProviders.First().InheritanceDepth; } } } public override string ToString() => $"MultiLevelXmlConfig:'{ProjectFolder}'"; protected override ConfigLoadResult LoadConfigCore() { LogTraceMessage($"Loading config {this}"); var projectLevelConfigProvider = new XmlFileConfigProvider(GetConfigFilePath(ProjectFolder), TraceMessageHandler); _fileConfigProviders = CreateFileConfigProviderList(projectLevelConfigProvider, ProjectFolder); return CombineFileConfigProvidersAndSaveResult(); } protected override ConfigLoadResult RefreshConfigCore() { if (!AnyChildConfigChanged()) return _lastConfigLoadResult; LogTraceMessage($"Refreshing config {this}."); var projectLevelConfigProvider = _fileConfigProviders[0]; projectLevelConfigProvider.RefreshConfig(); if (InheritanceDepth != _lastInheritanceDepth) { _fileConfigProviders = CreateFileConfigProviderList(projectLevelConfigProvider, ProjectFolder); } else { foreach (var configProvider in _fileConfigProviders.Skip(1)) configProvider.RefreshConfig(); } return CombineFileConfigProvidersAndSaveResult(); } protected override ConfigLoadResult UpdateMaxIssueCountCore(int newValue) { _fileConfigProviders.First().UpdateMaxIssueCount(newValue); return CombineFileConfigProvidersAndSaveResult(); } private ConfigLoadResult CombineFileConfigProvidersAndSaveResult() { _lastInheritanceDepth = InheritanceDepth; _lastConfigLoadResult = CombineFileConfigProviders(); LogTraceMessage(IndentHelper.Indent("Effective config:", 1).Concat(IndentHelper.Indent(_lastConfigLoadResult.ToStrings(), 2))); return _lastConfigLoadResult; } private ConfigLoadResult CombineFileConfigProviders() { var configBuilder = CreateAnalyzerConfigBuilder(); var anyConfigFound = false; foreach (var childConfigProvider in Enumerable.Reverse(_fileConfigProviders)) { var childConfigState = childConfigProvider.ConfigState; LogTraceMessage(IndentHelper.Indent($"Combining {childConfigProvider}, state={childConfigState}", 1)); switch (childConfigState) { case AnalyzerConfigState.NoConfig: break; case AnalyzerConfigState.Disabled: anyConfigFound = true; configBuilder.SetIsEnabled(false); break; case AnalyzerConfigState.Enabled: anyConfigFound = true; var childConfigBuilder = childConfigProvider.ConfigBuilder; configBuilder.Combine(childConfigBuilder); LogTraceMessage(IndentHelper.Indent(childConfigBuilder.ToStrings(), 2)); break; case AnalyzerConfigState.ConfigError: return ConfigLoadResult.CreateWithError(childConfigProvider.ConfigException); default: throw new ArgumentOutOfRangeException(nameof(childConfigState), childConfigState, "Unexpected value."); } } return anyConfigFound ? ConfigLoadResult.CreateWithConfig(configBuilder) : ConfigLoadResult.CreateWithNoConfig(); } private static AnalyzerConfigBuilder CreateAnalyzerConfigBuilder() => new(); private bool AnyChildConfigChanged() => _fileConfigProviders.Any(i => i.HasConfigFileChanged()); private List CreateFileConfigProviderList(XmlFileConfigProvider firstConfigProvider, string startFolderPath) { var fileConfigProviders = new List { firstConfigProvider }; LogTraceMessage(IndentHelper.Indent($"InheritanceDepth={firstConfigProvider.InheritanceDepth}", 1)); var currentFolder = startFolderPath; for (var i = 0; i < firstConfigProvider.InheritanceDepth; i++) { currentFolder = Directory.GetParent(currentFolder)?.FullName; if (string.IsNullOrWhiteSpace(currentFolder)) break; var higherLevelConfigProvider = new XmlFileConfigProvider(GetConfigFilePath(currentFolder), TraceMessageHandler); fileConfigProviders.Add(higherLevelConfigProvider); } return fileConfigProviders; } private static string GetConfigFilePath(string folderPath) => Path.Combine(folderPath, ProductConstants.DefaultConfigFileName); private void LogTraceMessage(IEnumerable messages) { foreach (var message in messages) LogTraceMessage(message); } private void LogTraceMessage(string message) => TraceMessageHandler?.Invoke(message); } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/RuleConfigToStringsFormatter.cs ================================================ using System.Collections.Generic; namespace Codartis.NsDepCop.Config.Implementation { /// /// Static helper class for formatting rule config data into strings. /// public static class RuleConfigToStringsFormatter { public static IEnumerable ToStrings(this IDictionary allowRules) { yield return $"AllowRules={allowRules.Count}"; foreach (var allowRule in allowRules) yield return $" {allowRule.Key}, {allowRule.Value?.ToString() ?? "{}"}"; } public static IEnumerable ToStrings(this ISet disallowRules) { yield return $"DisallowRules={disallowRules.Count}"; foreach (var disallowRule in disallowRules) yield return $" {disallowRule}"; } public static IEnumerable ToStrings(this IDictionary visibleTypesByNamespaces) { yield return $"VisibleTypesByNamespace={visibleTypesByNamespaces.Count}"; foreach (var visibleTypesByNamespace in visibleTypesByNamespaces) yield return $" {visibleTypesByNamespace.Key}, {visibleTypesByNamespace.Value?.ToString() ?? "{}"}"; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/XmlConfigParser.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Xml; using System.Xml.Linq; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Implementation { /// /// Parses a config provided in XML format. /// public static class XmlConfigParser { private const string RootElementName = "NsDepCopConfig"; private const string InheritanceDepthAttributeName = "InheritanceDepth"; private const string IsEnabledAttributeName = "IsEnabled"; private const string MaxIssueCountAttributeName = "MaxIssueCount"; private const string AutoLowerMaxIssueCountAttributeName = "AutoLowerMaxIssueCount"; private const string ImplicitParentDependencyAttributeName = "ChildCanDependOnParentImplicitly"; private const string ImplicitChildDependencyAttributeName = "ParentCanDependOnChildImplicitly"; private const string SourcePathExclusionPatternsAttributeName = "ExcludedFiles"; private const string CheckAssemblyDependenciesAttributeName = "CheckAssemblyDependencies"; private const string AllowedElementName = "Allowed"; private const string DisallowedElementName = "Disallowed"; private const string AllowedAssemblyElementName = "AllowedAssembly"; private const string DisallowedAssemblyElementName = "DisallowedAssembly"; private const string VisibleMembersElementName = "VisibleMembers"; private const string TypeElementName = "Type"; private const string OfNamespaceAttributeName = "OfNamespace"; private const string FromAttributeName = "From"; private const string ToAttributeName = "To"; private const string TypeNameAttributeName = "Name"; public static AnalyzerConfigBuilder Parse(XDocument configXml) { var configBuilder = new AnalyzerConfigBuilder(); var rootElement = GetRootElement(configXml); ParseRootNodeAttributes(rootElement, configBuilder); ParseChildElements(rootElement, configBuilder); return configBuilder; } public static void UpdateMaxIssueCount(XDocument configXml, int newValue) { var rootElement = GetRootElement(configXml); AddOrUpdateAttribute(rootElement, MaxIssueCountAttributeName, newValue.ToString()); } private static XElement GetRootElement(XDocument configXml) { var rootElement = configXml.Element(RootElementName); if (rootElement == null) throw new Exception($"'{RootElementName}' root element not found."); return rootElement; } private static void ParseRootNodeAttributes(XElement rootElement, AnalyzerConfigBuilder configBuilder) { configBuilder.SetIsEnabled(ParseValueType(rootElement, IsEnabledAttributeName, bool.TryParse)); configBuilder.SetInheritanceDepth(ParseValueType(rootElement, InheritanceDepthAttributeName, int.TryParse)); configBuilder.AddSourcePathExclusionPatterns(ParseStringList(rootElement, SourcePathExclusionPatternsAttributeName, ',')); configBuilder.SetCheckAssemblyDependencies(ParseValueType(rootElement, CheckAssemblyDependenciesAttributeName, bool.TryParse)); configBuilder.SetChildCanDependOnParentImplicitly(ParseValueType(rootElement, ImplicitParentDependencyAttributeName, bool.TryParse)); configBuilder.SetParentCanDependOnChildImplicitly(ParseValueType(rootElement, ImplicitChildDependencyAttributeName, bool.TryParse)); configBuilder.SetMaxIssueCount(ParseValueType(rootElement, MaxIssueCountAttributeName, int.TryParse)); configBuilder.SetAutoLowerMaxIssueCount(ParseValueType(rootElement, AutoLowerMaxIssueCountAttributeName, bool.TryParse)); } private static IEnumerable ParseStringList(XElement element, string attributeName, char separatorChar) { var attribute = element.Attribute(attributeName); var parts = Split(attribute?.Value, separatorChar); return parts?.ToList(); } private static IEnumerable Split(string s, char separatorChar) { return s?.Split(new[] { separatorChar }, StringSplitOptions.RemoveEmptyEntries).Select(i => i.Trim()); } private static void ParseChildElements(XElement rootElement, AnalyzerConfigBuilder configBuilder) { foreach (var xElement in rootElement.Elements()) { switch (xElement.Name.ToString()) { case AllowedElementName: ParseAllowedElement(xElement, configBuilder); break; case DisallowedElementName: ParseDisallowedElement(xElement, configBuilder); break; case AllowedAssemblyElementName: ParseAllowedAssemblyElement(xElement, configBuilder); break; case DisallowedAssemblyElementName: ParseDisallowedAssemblyElement(xElement, configBuilder); break; case VisibleMembersElementName: ParseVisibleMembersElement(xElement, configBuilder); break; default: Trace.WriteLine($"Unexpected element '{xElement.Name}' ignored."); break; } } } private static void ParseAllowedElement(XElement element, AnalyzerConfigBuilder configBuilder) { var allowedDependencyRule = ParseDependencyRule(element); var visibleTypeNames = ParseVisibleMembersInsideAllowedRule(element, allowedDependencyRule); if (visibleTypeNames.IsNullOrEmpty()) visibleTypeNames = null; configBuilder.AddAllowRule(allowedDependencyRule, visibleTypeNames); } private static void ParseDisallowedElement(XElement element, AnalyzerConfigBuilder configBuilder) { var disallowedDependencyRule = ParseDependencyRule(element); configBuilder.AddDisallowRule(disallowedDependencyRule); } private static void ParseAllowedAssemblyElement(XElement element, AnalyzerConfigBuilder configBuilder) { var allowedAssemblyDependencyRule = ParseAssemblyDependencyRule(element); configBuilder.AddAllowedAssemblyRule(allowedAssemblyDependencyRule); } private static void ParseDisallowedAssemblyElement(XElement element, AnalyzerConfigBuilder configBuilder) { var disallowedAssemblyDependencyRule = ParseAssemblyDependencyRule(element); configBuilder.AddDisallowedAssemblyRule(disallowedAssemblyDependencyRule); } private static TypeNameSet ParseVisibleMembersInsideAllowedRule(XElement element, DependencyRule allowedRule) { var visibleMembersChild = element.Element(VisibleMembersElementName); if (visibleMembersChild == null) return null; if (allowedRule.To is not Domain) throw new Exception($"{GetLineInfo(element)}The target namespace '{allowedRule.To}' must be a single namespace."); if (visibleMembersChild.Attribute(OfNamespaceAttributeName) != null) throw new Exception( $"{GetLineInfo(element)}If {VisibleMembersElementName} is embedded in a dependency specification then '{OfNamespaceAttributeName}' attribute must not be defined."); return ParseTypeNameSet(visibleMembersChild, TypeElementName); } private static void ParseVisibleMembersElement(XElement element, AnalyzerConfigBuilder configBuilder) { var targetNamespaceName = GetAttributeValue(element, OfNamespaceAttributeName); if (targetNamespaceName == null) throw new Exception($"{GetLineInfo(element)}'{OfNamespaceAttributeName}' attribute missing."); var targetNamespace = TryAndReportError(element, () => new Domain(targetNamespaceName.Trim())); var visibleTypeNames = ParseTypeNameSet(element, TypeElementName); if (!visibleTypeNames.Any()) return; configBuilder.AddVisibleTypesByNamespace(targetNamespace, visibleTypeNames); } private static DependencyRule ParseDependencyRule(XElement element) { var fromValue = GetAttributeValue(element, FromAttributeName); if (fromValue == null) throw new Exception($"{GetLineInfo(element)}'{FromAttributeName}' attribute missing."); var toValue = GetAttributeValue(element, ToAttributeName); if (toValue == null) throw new Exception($"{GetLineInfo(element)}'{ToAttributeName}' attribute missing."); var from = TryAndReportError(element, () => DomainSpecificationParser.Parse(fromValue.Trim())); var to = TryAndReportError(element, () => DomainSpecificationParser.Parse(toValue.Trim())); return new DependencyRule(from, to); } private static DependencyRule ParseAssemblyDependencyRule(XElement element) { var fromValue = GetAttributeValue(element, FromAttributeName); if (fromValue == null) throw new Exception($"{GetLineInfo(element)}'{FromAttributeName}' attribute missing."); var toValue = GetAttributeValue(element, ToAttributeName); if (toValue == null) throw new Exception($"{GetLineInfo(element)}'{ToAttributeName}' attribute missing."); var from = TryAndReportError(element, () => DomainSpecificationParser.Parse(fromValue.Trim())); var to = TryAndReportError(element, () => DomainSpecificationParser.Parse(toValue.Trim())); return new DependencyRule(from, to); } private static T TryAndReportError(XObject xObject, Func parserDelegate) { try { return parserDelegate(); } catch (Exception e) { throw new Exception($"{GetLineInfo(xObject)}{e.Message}", e); } } private static TypeNameSet ParseTypeNameSet(XElement element, string childElementName) { var typeNameSet = new TypeNameSet(); foreach (var xElement in element.Elements(childElementName)) { var typeName = GetAttributeValue(xElement, TypeNameAttributeName); if (typeName == null) throw new Exception($"{GetLineInfo(xElement)}'{TypeNameAttributeName}' attribute missing."); if (!string.IsNullOrWhiteSpace(typeName)) typeNameSet.Add(typeName.Trim()); } return typeNameSet; } /// /// Returns an attribute's value, or null if the attribute was not found. /// /// The parent element of the attribute. /// The name of the attribute. /// The value of the attribute or null if the attribute was not found. private static string GetAttributeValue(XElement element, string attributeName) { return element.Attribute(attributeName)?.Value; } private static void AddOrUpdateAttribute(XElement element, string attributeName, string newValue) { var attribute = element.Attribute(attributeName); if (attribute == null) element.Add(new XAttribute(attributeName, newValue)); else attribute.Value = newValue; } /// /// Defines the signature of a TryParse-like method, that is used to parse a value of T from string. /// /// The type of the parse result. /// The string that must be parsed. /// The successfully parsed value. /// True if successfully parsed, false otherwise. private delegate bool TryParseMethod(string s, out T t); private static T? ParseValueType(XElement element, string attributeName, TryParseMethod tryParseMethod) where T : struct { var attribute = element.Attribute(attributeName); if (attribute == null) return null; if (tryParseMethod(attribute.Value, out var parseResult)) return parseResult; throw new FormatException($"{GetLineInfo(element)}Error parsing '{attribute.Name}' value '{attribute.Value}'."); } private static string GetLineInfo(XObject xObject) { var xmlLineInfo = xObject as IXmlLineInfo; return xmlLineInfo.HasLineInfo() ? $"[Line: {xmlLineInfo.LineNumber}, Pos: {xmlLineInfo.LinePosition}] " : string.Empty; } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/Implementation/XmlFileConfigProvider.cs ================================================ using System.Xml.Linq; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.Config.Implementation { /// /// Extracts config information from an xml config file. /// /// /// Base class handles config load errors so no need to catch exceptions here. /// public sealed class XmlFileConfigProvider : FileConfigProviderBase { private XDocument _configXDocument; public XmlFileConfigProvider(string configFilePath, MessageHandler traceMessageHandler) : base(configFilePath, traceMessageHandler) { } public override string ToString() => $"XmlConfig:'{ConfigFilePath}'"; protected override AnalyzerConfigBuilder CreateConfigBuilderFromFile(string configFilePath) { _configXDocument = XDocument.Load(configFilePath, LoadOptions.SetLineInfo); return XmlConfigParser.Parse(_configXDocument); } protected override ConfigLoadResult UpdateMaxIssueCountCore(int newValue) { XmlConfigParser.UpdateMaxIssueCount(_configXDocument, newValue); _configXDocument.Save(ConfigFilePath); return LoadConfigCore(); } } } ================================================ FILE: source/NsDepCop.Analyzer/Config/RegexCompilationMode.cs ================================================ namespace Codartis.NsDepCop.Config; public enum RegexCompilationMode { Compiled, Interpreted } ================================================ FILE: source/NsDepCop.Analyzer/Config/RegexDomain.cs ================================================ using System; using System.Text.RegularExpressions; namespace Codartis.NsDepCop.Config; /// /// Represents a domain specification with a regular expression pattern. /// The given pattern must follow the dotnet regex specification found at: /// https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference /// /// /// This class provides functionality to define a domain using a regular expression /// and supports validation and matching of other domains against the specified pattern. /// [Serializable] public sealed class RegexDomain : DomainSpecification { public const string Delimiter = "/"; private static readonly TimeSpan DefaultRegexTimeout = TimeSpan.FromMilliseconds(100); private readonly Regex _regex; private readonly RegexOptions _regexOptions; private readonly TimeSpan _regexTimeout; public RegexDomain( string value, bool validate = true, RegexUsageMode regexUsageMode = RegexUsageMode.Instance, RegexCompilationMode regexCompilationMode = RegexCompilationMode.Interpreted, TimeSpan? regexTimeout = null) : base(value, validate, IsValid) { _regexTimeout = regexTimeout ?? DefaultRegexTimeout; _regexOptions = RegexOptions.Singleline; if (regexCompilationMode == RegexCompilationMode.Compiled) _regexOptions |= RegexOptions.Compiled; if (regexUsageMode == RegexUsageMode.Instance) _regex = new Regex(Normalize(Value), _regexOptions, _regexTimeout); } public override int GetMatchRelevance(Domain domain) { try { if (_regex != null) return _regex.IsMatch(domain.ToString()) ? 1 : 0; return Regex.IsMatch(domain.ToString(), Normalize(Value), _regexOptions, _regexTimeout) ? 1 : 0; } catch (RegexMatchTimeoutException) { return 0; } } private static bool IsValid(string domainAsString) { if (!domainAsString.StartsWith(Delimiter, StringComparison.Ordinal) || !domainAsString.EndsWith(Delimiter, StringComparison.Ordinal)) { return false; } var normalizedDomainAsString = Normalize(domainAsString); if (string.IsNullOrWhiteSpace(normalizedDomainAsString)) return false; try { _ = new Regex(normalizedDomainAsString); return true; } catch (ArgumentException) { return false; } } private static string Normalize(string domainAsString) => domainAsString.Trim(Delimiter.ToCharArray()); } ================================================ FILE: source/NsDepCop.Analyzer/Config/RegexUsageMode.cs ================================================ namespace Codartis.NsDepCop.Config; public enum RegexUsageMode { Static, Instance } ================================================ FILE: source/NsDepCop.Analyzer/Config/TypeNameSet.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; namespace Codartis.NsDepCop.Config { /// /// A set of type names represented as strings (without namespace part). /// [Serializable] public class TypeNameSet : HashSet { public TypeNameSet() { } public TypeNameSet(IEnumerable collection) : base(collection) { } protected TypeNameSet(SerializationInfo info, StreamingContext context) : base(info, context) { } public override string ToString() => this.Any() ? $"{{{string.Join(",", this)}}}" : "{}"; } } ================================================ FILE: source/NsDepCop.Analyzer/Config/WildcardDomain.cs ================================================ using System; using System.Linq; namespace Codartis.NsDepCop.Config { /// /// Represents a domain specification with wildcards, eg. 'System.IO.*' or 'System.?.?.Generic'. /// Each '?' can be replaced with exactly one domain component when matching against a domain. /// Each '*' can be replaced with any number of domain components. ///
/// This class is immutable. ///
/// /// The 'any domain' (represented by a star '*') is also a domain that contains every domain. /// [Serializable] public sealed class WildcardDomain : DomainSpecification { private readonly string[] _domainComponents; public const string SingleDomainMarker = "?"; public const string AnyDomainMarker = "*"; /// /// Creates a new instance from a string representation. /// /// The string representation of a domain pattern containing wildcards. /// True means validate the input string. public WildcardDomain(string wildcardDomainAsString, bool validate = true) : base(wildcardDomainAsString, validate, IsValid) { _domainComponents = wildcardDomainAsString.Split(DomainPartSeparator); } /// public override int GetMatchRelevance(Domain domain) { var actualList = domain.ToString().Split(DomainPartSeparator); var distance = CalcDistance(actualList, _domainComponents, 0); return int.MaxValue - distance; } /// /// Returns a boolean value indication if the given is a valid . /// /// The domain string to check. /// True, if and only if the is valid. public static bool IsValid(string domainAsString) { var parts = domainAsString.Split(DomainPartSeparator); bool validChars = parts.All(s => !string.IsNullOrWhiteSpace(s) && (s is SingleDomainMarker or AnyDomainMarker || (!s.Contains(SingleDomainMarker) && !s.Contains(AnyDomainMarker))) ); bool anyWildcard = parts.Any(s => s is SingleDomainMarker or AnyDomainMarker); bool notAdjacentTrees = parts.Length > 0 && parts.Zip(parts.Skip(1), (a, b) => (a, b)).All(p => p.a != AnyDomainMarker || p.b != AnyDomainMarker); return validChars && anyWildcard && notAdjacentTrees; } /// /// Calculates the edit distance between and . ///
/// The edit distance is calculated as the sum of all edit operations which are needed to replace the wildcards with /// the domain names. The costs are as follows: ///
    ///
  • * Replacing a `?` has a cost of 1.
  • ///
  • * Replacing a `*` has a cost of 1 and additionally a cost of 1 per sub-domain that replaces the `*`.
  • ///
///
/// A span of nested domain names to match. /// A span of nested domain names or wildcards to match with. /// The edit cost of the parent domain. /// /// The sum of actualDistance and the edit distance between and . /// private static int CalcDistance(ReadOnlySpan remainingActual, ReadOnlySpan remainingPattern, int actualDistance) { if (remainingPattern.IsEmpty) { if (remainingActual.IsEmpty) { // completely matched return actualDistance; } // no match, as the pattern is exhausted return int.MaxValue; } if (remainingActual.IsEmpty) { if (remainingPattern.Length == 1 && remainingPattern[0] is AnyDomainMarker) { // removing the trailing star costs one point return actualDistance + 1; } // no match, as no more input can be matched against the pattern return int.MaxValue; } System.Diagnostics.Debug.Assert(!remainingActual.IsEmpty && !remainingPattern.IsEmpty); switch (remainingPattern[0]) { case SingleDomainMarker: { // replace single wildcard at cost 1 return CalcDistance(remainingActual.Slice(1), remainingPattern.Slice(1), actualDistance + 1); } case AnyDomainMarker: { // try both options: (This moves the algorithm to complexity class O(NM).) // remove wildcard int v1 = CalcDistance(remainingActual, remainingPattern.Slice(1), actualDistance + 1); // use wildcard to substitute one domain component int v2 = CalcDistance(remainingActual.Slice(1), remainingPattern, actualDistance + 1); return Math.Min(v1, v2); } default: { if (remainingPattern[0] == remainingActual[0]) { // full match return CalcDistance(remainingActual.Slice(1), remainingPattern.Slice(1), actualDistance); } // no match return int.MaxValue; } } } } } ================================================ FILE: source/NsDepCop.Analyzer/GlobalSettings.cs ================================================ using System; namespace Codartis.NsDepCop { public static class GlobalSettings { public static bool IsToolDisabled() { var environmentVariableValue = Environment.GetEnvironmentVariable(ProductConstants.DisableToolEnvironmentVariableName); return environmentVariableValue == "1" || string.Equals(environmentVariableValue, "true", StringComparison.OrdinalIgnoreCase); } } } ================================================ FILE: source/NsDepCop.Analyzer/NsDepCop.Analyzer.csproj ================================================  netstandard2.0 Codartis.NsDepCop false false true latest all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: source/NsDepCop.Analyzer/ParserAdapter/Roslyn/ISyntaxNodeAnalyzer.cs ================================================ using System.Collections.Generic; using Codartis.NsDepCop.Analysis; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.ParserAdapter.Roslyn { /// /// Analyzer logic for a single syntax node. /// public interface ISyntaxNodeAnalyzer { /// /// Returns type dependencies for a syntax node. /// /// A syntax node. /// The semantic model of the current document. /// A list of type dependencies. Can be empty. IEnumerable GetTypeDependencies(SyntaxNode node, SemanticModel semanticModel); } } ================================================ FILE: source/NsDepCop.Analyzer/ParserAdapter/Roslyn/SyntaxNodeAnalyzer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Analysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Codartis.NsDepCop.ParserAdapter.Roslyn { /// /// Enumerates type dependencies for a syntax node using Roslyn 1.x. /// public class SyntaxNodeAnalyzer : ISyntaxNodeAnalyzer { /// /// The list of those type kinds that can occur as a declaration. /// private static readonly List DeclarationTypeKinds = new() { TypeKind.Class, TypeKind.Delegate, TypeKind.Enum, TypeKind.Interface, TypeKind.Struct, }; /// /// Returns type dependencies for a syntax node. /// /// A syntax node. /// The semantic model of the current document. /// A list of type dependencies. Can be empty. public IEnumerable GetTypeDependencies(SyntaxNode node, SemanticModel semanticModel) { // Determine the type that contains the current syntax node. var enclosingType = DetermineEnclosingType(node, semanticModel); if (!IsAnalyzableDeclarationType(enclosingType)) yield break; // Determine the type referenced by the symbol represented by the current syntax node. var referencedType = DetermineReferencedType(node, semanticModel); foreach (var type in GetConstituentTypes(referencedType, node)) yield return CreateTypeDependency(enclosingType, type, node); // If this is an extension method invocation then determine the type declaring the extension method. var declaringType = DetermineExtensionOrStaticMethodDeclaringType(node, semanticModel); if (IsAnalyzableDeclarationType(declaringType)) yield return CreateTypeDependency(enclosingType, declaringType, node); } private static bool IsAnalyzableDeclarationType(ITypeSymbol typeSymbol) { return typeSymbol?.ContainingNamespace != null && !typeSymbol.IsAnonymousType && DeclarationTypeKinds.Contains(typeSymbol.TypeKind) && !string.IsNullOrWhiteSpace(typeSymbol.MetadataName); } protected virtual IEnumerable GetConstituentTypes(ITypeSymbol typeSymbol, SyntaxNode syntaxNode) { if (typeSymbol == null || typeSymbol.IsAnonymousType) yield break; var typeKind = typeSymbol.TypeKind; switch (typeKind) { case TypeKind.Array: var arrayTypeSymbol = (IArrayTypeSymbol) typeSymbol; foreach (var type in GetConstituentTypes(arrayTypeSymbol.ElementType, syntaxNode)) yield return type; break; case TypeKind.Pointer: var pointerTypeSymbol = (IPointerTypeSymbol) typeSymbol; foreach (var type in GetConstituentTypes(pointerTypeSymbol.PointedAtType, syntaxNode)) yield return type; break; case TypeKind.Class: case TypeKind.Delegate: case TypeKind.Enum: case TypeKind.Interface: case TypeKind.Struct: var namedTypeSymbol = typeSymbol as INamedTypeSymbol; if (namedTypeSymbol == null) yield break; if (namedTypeSymbol.IsGenericType) { yield return namedTypeSymbol.ConstructedFrom; // If the syntax node has any identifier descendants then those will trigger analysis for the type arguments // so there's no need to recurse into the type arguments here. if (syntaxNode.HasDescendant()) yield break; foreach (var typeArgument in namedTypeSymbol.TypeArguments) foreach (var type in GetConstituentTypes(typeArgument, syntaxNode)) yield return type; } else { yield return namedTypeSymbol; } break; case TypeKind.Dynamic: case TypeKind.Error: case TypeKind.Module: case TypeKind.Unknown: case TypeKind.Submission: case TypeKind.TypeParameter: yield break; default: throw new ArgumentOutOfRangeException(nameof(typeKind), typeKind, "Unexpected value."); } } /// /// Returns a type dependency object for the given types. /// /// The referring type. /// The referenced type. /// The syntax node currently analyzed. /// A type dependency object. private static TypeDependency CreateTypeDependency(ITypeSymbol fromType, ITypeSymbol toType, SyntaxNode node) { return new TypeDependency( fromType.ContainingNamespace.ToDisplayString(), fromType.MetadataName, toType.ContainingNamespace.ToDisplayString(), toType.MetadataName, GetSourceSegment(node)); } /// /// Determines the type declaring the given extension method syntax node or /// the declaring type of a static method when the type is statically imported. /// e.g. /// using static A.ClassA; /// StaticMethodOfClassA(); // in contrast to A.ClassA.StaticMethodOfClassA(); /// /// /// A syntax node representing a static or extension method. /// The semantic model of the project. /// The type declaring the given static or extension method syntax node, or null if not found. private static ITypeSymbol DetermineExtensionOrStaticMethodDeclaringType(SyntaxNode node, SemanticModel semanticModel) { var methodSymbol = semanticModel.GetSymbolInfo(node).Symbol as IMethodSymbol; bool isExtensionMethodOrCallOnStaticImport = methodSymbol != null && (methodSymbol.IsExtensionMethod || (methodSymbol.IsStatic && node.Parent is not MemberAccessExpressionSyntax)); return isExtensionMethodOrCallOnStaticImport ? methodSymbol.ContainingType : null; } /// /// Determines the type referenced by the given syntax node. /// /// A syntax node. /// The semantic model of the project. /// The type referenced by the given syntax node, or null if no type was referenced. protected virtual ITypeSymbol DetermineReferencedType(SyntaxNode node, SemanticModel semanticModel) { var typeSymbol = semanticModel.GetTypeInfo(node).Type; if (typeSymbol != null && typeSymbol.TypeKind != TypeKind.Error) return typeSymbol; // Special case: deconstructing declaration with var outside, e.g.: var (d, e) = Method2(); if (node.Parent is DeclarationExpressionSyntax && node.Parent.ChildNodes().Any(i => i is ParenthesizedVariableDesignationSyntax)) return DetermineReferencedType(node.Parent, semanticModel); // In same cases GetTypeInfo(node).Type does not return the desired type symbol but GetSymbolInfo(node).Symbol does. // E.g.: IdentifierNameSyntax inside an ObjectCreationExpression var symbolInfo = semanticModel.GetSymbolInfo(node); if (symbolInfo.Symbol is ITypeSymbol symbolInfoTypeSymbol) return symbolInfoTypeSymbol; // Special case: for method invocations we should check the return type if (symbolInfo.Symbol is IMethodSymbol methodSymbol) return methodSymbol.ReturnType; // Could not determine referenced type. return null; } /// /// Determines the type that contains the given syntax node. /// /// A syntax node. /// The semantic model of the project. /// The type that contains the given syntax node. Or null if can't determine. private static ITypeSymbol DetermineEnclosingType(SyntaxNode node, SemanticModel semanticModel) { // Find the type declaration that contains the current syntax node. var typeDeclarationSyntaxNode = node.Ancestors().FirstOrDefault(i => i.IsTypeDeclaration() || i is CompilationUnitSyntax); if (typeDeclarationSyntaxNode == null) return null; if (typeDeclarationSyntaxNode is CompilationUnitSyntax) { return semanticModel.GetDeclaredSymbol(typeDeclarationSyntaxNode)?.ContainingType; } // Determine the type of the type declaration that contains the current syntax node. return semanticModel.GetDeclaredSymbol(typeDeclarationSyntaxNode) as ITypeSymbol; } /// /// Gets the source segment of the given syntax node. /// /// A syntax node. /// The source segment of the given syntax node. private static SourceSegment GetSourceSegment(SyntaxNode syntaxNode) { var lineSpan = syntaxNode.GetLocation().GetLineSpan(); return new SourceSegment ( lineSpan.StartLinePosition.Line + 1, lineSpan.StartLinePosition.Character + 1, lineSpan.EndLinePosition.Line + 1, lineSpan.EndLinePosition.Character + 1, syntaxNode.ToString(), lineSpan.Path ); } } } ================================================ FILE: source/NsDepCop.Analyzer/ParserAdapter/Roslyn/SyntaxNodeExtensions.cs ================================================ using System; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Codartis.NsDepCop.ParserAdapter.Roslyn { public static class SyntaxNodeExtensions { public static bool IsTypeDeclaration(this SyntaxNode syntaxNode) { return syntaxNode is BaseTypeDeclarationSyntax || syntaxNode is DelegateDeclarationSyntax; } public static bool HasDescendant(this SyntaxNode syntaxNode) where T : SyntaxNode { if (syntaxNode == null) throw new ArgumentNullException(nameof(syntaxNode)); return syntaxNode.ChildNodes().Any(i => i.IsOrHasDescendant()); } public static bool IsOrHasDescendant(this SyntaxNode syntaxNode) where T : SyntaxNode { if (syntaxNode == null) return false; if (syntaxNode.GetType() == typeof(T)) return true; return syntaxNode.ChildNodes().Any(i => i.IsOrHasDescendant()); } public static bool HasParent(this SyntaxNode syntaxNode) where T : SyntaxNode { var parent = syntaxNode.Parent; if (parent == null) return false; if (parent.GetType() == typeof(T)) return true; return parent.HasParent(); } } } ================================================ FILE: source/NsDepCop.Analyzer/ParserAdapter/Roslyn/TypeDependencyEnumerator.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Util; using DotNet.Globbing; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace Codartis.NsDepCop.ParserAdapter.Roslyn { /// /// Implements a type dependency enumerators that use Roslyn as the parser. /// public class TypeDependencyEnumerator : ITypeDependencyEnumerator { private static readonly CSharpParseOptions ParseOptions = new(LanguageVersion.Latest); private readonly ISyntaxNodeAnalyzer _syntaxNodeAnalyzer; private readonly MessageHandler _traceMessageHandler; public TypeDependencyEnumerator(ISyntaxNodeAnalyzer syntaxNodeAnalyzer, MessageHandler traceMessageHandler) { _syntaxNodeAnalyzer = syntaxNodeAnalyzer ?? throw new ArgumentNullException(nameof(syntaxNodeAnalyzer)); _traceMessageHandler = traceMessageHandler; } private static TypeDependencyEnumeratorSyntaxVisitor CreateSyntaxVisitor( SemanticModel semanticModel, ISyntaxNodeAnalyzer syntaxNodeAnalyzer) { return new(semanticModel, syntaxNodeAnalyzer); } public IEnumerable GetTypeDependencies( IEnumerable sourceFilePaths, IEnumerable referencedAssemblyPaths, IEnumerable sourcePathExclusionGlobs) { var referencedAssemblies = referencedAssemblyPaths.Select(LoadMetadata).Where(i => i != null).ToList(); var syntaxTrees = sourceFilePaths.Select(ParseFile).Where(i => i != null).ToList(); var compilation = CSharpCompilation.Create("NsDepCopProject", syntaxTrees, referencedAssemblies, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); return syntaxTrees.Where(i => !IsExcludedFilePath(i.FilePath, sourcePathExclusionGlobs)) .SelectMany(i => GetTypeDependenciesForSyntaxTree(compilation, i, _syntaxNodeAnalyzer)); } private static bool IsExcludedFilePath(string filePath, IEnumerable sourcePathExclusionGlobs) { return sourcePathExclusionGlobs.Any(i => i.IsMatch(filePath)); } private static IEnumerable GetTypeDependenciesForSyntaxTree( Compilation compilation, SyntaxTree syntaxTree, ISyntaxNodeAnalyzer syntaxNodeAnalyzer) { var documentRootNode = syntaxTree.GetRoot(); var semanticModel = compilation.GetSemanticModel(syntaxTree); var syntaxVisitor = CreateSyntaxVisitor(semanticModel, syntaxNodeAnalyzer); syntaxVisitor.Visit(documentRootNode); return syntaxVisitor.TypeDependencies; } public IEnumerable GetTypeDependencies( SyntaxNode syntaxNode, SemanticModel semanticModel, IEnumerable sourcePathExclusionGlobs) { return IsExcludedFilePath(syntaxNode?.SyntaxTree.FilePath, sourcePathExclusionGlobs) ? Enumerable.Empty() : _syntaxNodeAnalyzer.GetTypeDependencies(syntaxNode, semanticModel); } private MetadataReference LoadMetadata(string fileName) { try { return MetadataReference.CreateFromFile(fileName); } catch (Exception e) { LogTraceMessage($"Error loading metadata file '{fileName}': {e}"); return null; } } private SyntaxTree ParseFile(string fileName) { try { using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var streamReader = new StreamReader(stream)) { var sourceText = streamReader.ReadToEnd(); return CSharpSyntaxTree.ParseText(sourceText, ParseOptions, fileName); } } catch (Exception e) { LogTraceMessage($"Error parsing source file '{fileName}': {e}"); return null; } } private void LogTraceMessage(string message) => _traceMessageHandler?.Invoke(message); } } ================================================ FILE: source/NsDepCop.Analyzer/ParserAdapter/Roslyn/TypeDependencyEnumeratorSyntaxVisitor.cs ================================================ using System; using System.Collections.Generic; using Codartis.NsDepCop.Analysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Codartis.NsDepCop.ParserAdapter.Roslyn { /// /// Implements a syntax visitor that traverses the syntax tree and finds all type dependencies. /// /// /// WARNING: when adding a new SyntaxNode type to the visitor, /// also add it to NsDepCopDiagnosticAnalyzerBase.GetSyntaxKindsToRegister /// public class TypeDependencyEnumeratorSyntaxVisitor : CSharpSyntaxVisitor { /// /// The semantic model of the current document. /// private readonly SemanticModel _semanticModel; /// /// Implements the analysis logic for a single syntax node. /// private readonly ISyntaxNodeAnalyzer _syntaxNodeAnalyzer; /// /// The collection of type dependencies that the syntax visitor found. /// private readonly List _typeDependencies; /// /// Initializes a new instance. /// /// The semantic model for the document. /// Syntax node analyzer logic. public TypeDependencyEnumeratorSyntaxVisitor(SemanticModel semanticModel, ISyntaxNodeAnalyzer syntaxNodeAnalyzer) { _semanticModel = semanticModel ?? throw new ArgumentNullException(nameof(semanticModel)); _syntaxNodeAnalyzer = syntaxNodeAnalyzer ?? throw new ArgumentNullException(nameof(syntaxNodeAnalyzer)); _typeDependencies = new List(); } public IEnumerable TypeDependencies => _typeDependencies; /// /// Visits all child nodes of a given node. /// /// A syntax node. public override void DefaultVisit(SyntaxNode node) { foreach (var childNode in node.ChildNodes()) Visit(childNode); } public override void VisitIdentifierName(IdentifierNameSyntax node) { AnalyzeNode(node); // No need to call DefaultVisit, because it cannot have such child nodes that need to be checked. } public override void VisitGenericName(GenericNameSyntax node) { AnalyzeNode(node); DefaultVisit(node); } public override void VisitLiteralExpression(LiteralExpressionSyntax node) { if (node.Kind() == SyntaxKind.DefaultLiteralExpression) AnalyzeNode(node); } /// /// Collects type dependencies for a given syntax node. /// /// A syntax node. private void AnalyzeNode(SyntaxNode node) { var typeDependencies = _syntaxNodeAnalyzer.GetTypeDependencies(node, _semanticModel); _typeDependencies.AddRange(typeDependencies); } } } ================================================ FILE: source/NsDepCop.Analyzer/ProductConstants.cs ================================================ using System.Reflection; namespace Codartis.NsDepCop { /// /// Product-wide constant values. /// public static class ProductConstants { public const string ToolName = "NsDepCop"; public const string DefaultConfigFileName = "config.nsdepcop"; public const string DisableToolEnvironmentVariableName = "DisableNsDepCop"; public static readonly string Version = $"{Assembly.GetExecutingAssembly().GetName().Version}"; } } ================================================ FILE: source/NsDepCop.Analyzer/Properties/AssemblyInfo.cs ================================================ using System.Reflection; [assembly: AssemblyTitle("NsDepCop.Analyzer")] [assembly: AssemblyDescription("Static code analysis tool for enforcing namespace-based type dependency rules in C# projects.")] ================================================ FILE: source/NsDepCop.Analyzer/RoslynAnalyzer/AnalyzerProvider.cs ================================================ using System; using System.Collections.Concurrent; using System.IO; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Util; namespace Codartis.NsDepCop.RoslynAnalyzer { /// /// Creates and stores dependency analyzers for C# projects. /// Ensures that the analyzers' configs are always refreshed. /// public sealed class AnalyzerProvider : IAnalyzerProvider { private readonly IDependencyAnalyzerFactory _dependencyAnalyzerFactory; private readonly IAssemblyDependencyAnalyzerFactory _assemblyDependencyAnalyzerFactory; private readonly IConfigProviderFactory _configProviderFactory; private readonly ITypeDependencyEnumerator _typeDependencyEnumerator; /// /// Maps project files to their corresponding dependency analyzer. The key is the project file name with full path. /// private readonly ConcurrentDictionary _projectFileToDependencyAnalyzerMap = new(); private readonly ConcurrentDictionary _projectFileToAssemblyDependencyAnalyzerMap = new(); public AnalyzerProvider( IDependencyAnalyzerFactory dependencyAnalyzerFactory, IAssemblyDependencyAnalyzerFactory assemblyDependencyAnalyzerFactory, IConfigProviderFactory configProviderFactory, ITypeDependencyEnumerator typeDependencyEnumerator) { _dependencyAnalyzerFactory = dependencyAnalyzerFactory ?? throw new ArgumentNullException(nameof(dependencyAnalyzerFactory)); _assemblyDependencyAnalyzerFactory = assemblyDependencyAnalyzerFactory ?? throw new ArgumentNullException(nameof(assemblyDependencyAnalyzerFactory)); _configProviderFactory = configProviderFactory ?? throw new ArgumentNullException(nameof(configProviderFactory)); _typeDependencyEnumerator = typeDependencyEnumerator ?? throw new ArgumentNullException(nameof(typeDependencyEnumerator)); } public IDependencyAnalyzer GetDependencyAnalyzer(string csprojFilePath) { if (string.IsNullOrWhiteSpace(csprojFilePath)) throw new ArgumentException("Filename must not be null or whitespace.", nameof(csprojFilePath)); var dependencyAnalyzer = _projectFileToDependencyAnalyzerMap.GetOrAdd(csprojFilePath, CreateDependencyAnalyzer, out var added); if (!added) dependencyAnalyzer.RefreshConfig(); return dependencyAnalyzer; } private IDependencyAnalyzer CreateDependencyAnalyzer(string projectFilePath) { var projectFileDirectory = Path.GetDirectoryName(projectFilePath); var configProvider = _configProviderFactory.CreateFromMultiLevelXmlConfigFile(projectFileDirectory); return _dependencyAnalyzerFactory.Create(configProvider, _typeDependencyEnumerator); } public IAssemblyDependencyAnalyzer GetAssemblyDependencyAnalyzer(string csprojFilePath) { if (string.IsNullOrWhiteSpace(csprojFilePath)) throw new ArgumentException("Filename must not be null or whitespace.", nameof(csprojFilePath)); var dependencyAnalyzer = _projectFileToAssemblyDependencyAnalyzerMap.GetOrAdd(csprojFilePath, CreateAssemblyDependencyAnalyzer, out var added); if (!added) dependencyAnalyzer.RefreshConfig(); return dependencyAnalyzer; } private IAssemblyDependencyAnalyzer CreateAssemblyDependencyAnalyzer(string projectFilePath) { var projectFileDirectory = Path.GetDirectoryName(projectFilePath); var configProvider = _configProviderFactory.CreateFromMultiLevelXmlConfigFile(projectFileDirectory); return _assemblyDependencyAnalyzerFactory.Create(configProvider); } } } ================================================ FILE: source/NsDepCop.Analyzer/RoslynAnalyzer/DiagnosticDefinitions.cs ================================================ using System.Globalization; using Microsoft.CodeAnalysis; namespace Codartis.NsDepCop.RoslynAnalyzer { /// /// Defines the diagnostics that the tool can report. /// public static class DiagnosticDefinitions { /// /// Format string to create a help link for diagnostics. The parameter is the ID of the diagnostic in lowercase. /// private const string HelpLinkFormat = @"https://github.com/realvizu/NsDepCop/blob/master/doc/Diagnostics.md#{0}"; public static readonly DiagnosticDescriptor IllegalDependency = CreateDiagnosticDescriptor( "NSDEPCOP01", "Illegal namespace reference.", "Illegal namespace reference: {0}->{1} (Type: {2}->{3}){4}", DiagnosticSeverity.Warning, "This type cannot reference the other type because their namespaces cannot depend on each other according to the current rules." + " Change the dependency rules in the 'config.nsdepcop' file or change your design to avoid this namespace dependency." ); public static readonly DiagnosticDescriptor TooManyDependencyIssues = CreateDiagnosticDescriptor( "NSDEPCOP02", "Too many dependency issues, analysis was stopped.", "Maximum dependency issue count ({0}) reached, analysis in this compilation was stopped.", DiagnosticSeverity.Warning, "The number of dependency issues in this compilation has exceeded the configured maximum value." + " Correct the reported issues and run the build again or set the MaxIssueCount attribute in your 'config.nsdepcop' file to a higher number." ); public static readonly DiagnosticDescriptor NoConfigFile = CreateDiagnosticDescriptor( "NSDEPCOP03", "No config file found, analysis skipped.", "No config file found, analysis skipped.", DiagnosticSeverity.Info, "This analyzer requires that you add a file called 'config.nsdepcop' to your project with build action 'C# analyzer additional file'." + " If there's no such file, the analyzer skips this project." ); public static readonly DiagnosticDescriptor ConfigDisabled = CreateDiagnosticDescriptor( "NSDEPCOP04", "Analysis is disabled in the config file.", "Analysis is disabled in the file 'config.nsdepcop'.", DiagnosticSeverity.Info, "The IsEnabled attribute was set to false in this project's 'config.nsdepcop' file, so the analyzer skips this project." ); public static readonly DiagnosticDescriptor ConfigException = CreateDiagnosticDescriptor( "NSDEPCOP05", "Error loading config.", "Error when loading the file 'config.nsdepcop': {0}", DiagnosticSeverity.Error, "There was an error while loading the 'config.nsdepcop' file, see the message for details." + " Some common reasons: malformed content, file permission or file locking problem." ); public static readonly DiagnosticDescriptor ToolDisabled = CreateDiagnosticDescriptor( "NSDEPCOP06", "Analysis is disabled with environment variable.", $"NsDepCop is disabled with environment variable: '{ProductConstants.DisableToolEnvironmentVariableName}'.", DiagnosticSeverity.Info, $"If the '{ProductConstants.DisableToolEnvironmentVariableName}' environment variable is set to 'True' or '1' then all analysis is skipped." ); public static readonly DiagnosticDescriptor IllegalAssemblyDependency = CreateDiagnosticDescriptor( "NSDEPCOP07", "Illegal assembly reference.", "Illegal assembly reference: {0}->{1}", DiagnosticSeverity.Warning, "The assembly cannot reference the other assembly because their dependency is prohibited according to the current rules." + " Change the dependency rules in the 'config.nsdepcop' file or change your design to avoid this assembly dependency." ); private static DiagnosticDescriptor CreateDiagnosticDescriptor( string id, string title, string messageFormat, DiagnosticSeverity defaultSeverity, string description) { return new( id, title, messageFormat, category: ProductConstants.ToolName, defaultSeverity, isEnabledByDefault: true, description, helpLinkUri: string.Format(HelpLinkFormat, id.ToLower(CultureInfo.InvariantCulture)) ); } } } ================================================ FILE: source/NsDepCop.Analyzer/RoslynAnalyzer/IAnalyzerProvider.cs ================================================ using Codartis.NsDepCop.Analysis; namespace Codartis.NsDepCop.RoslynAnalyzer { /// /// Provides configured dependency analyzer instances. /// public interface IAnalyzerProvider { /// /// Retrieves an up-to-date analyzer for a C# project file (csproj). /// /// The full path of a C# project file. /// A dependency analyzer, or null if cannot be retrieved. IDependencyAnalyzer GetDependencyAnalyzer(string csprojFilePath); IAssemblyDependencyAnalyzer GetAssemblyDependencyAnalyzer(string csprojFilePath); } } ================================================ FILE: source/NsDepCop.Analyzer/RoslynAnalyzer/NsDepCopAnalyzer.cs ================================================ using System; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Analysis.Factory; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Config.Factory; using Codartis.NsDepCop.ParserAdapter.Roslyn; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Codartis.NsDepCop.RoslynAnalyzer { /// /// Wraps the dependency analyzer in a Roslyn . /// [DiagnosticAnalyzer(LanguageNames.CSharp)] // ReSharper disable once UnusedType.Global public sealed class NsDepCopAnalyzer : DiagnosticAnalyzer { private static readonly ImmutableArray DiagnosticDescriptors = ImmutableArray.Create( DiagnosticDefinitions.IllegalDependency, DiagnosticDefinitions.TooManyDependencyIssues, DiagnosticDefinitions.NoConfigFile, DiagnosticDefinitions.ConfigDisabled, DiagnosticDefinitions.ConfigException, DiagnosticDefinitions.ToolDisabled, DiagnosticDefinitions.IllegalAssemblyDependency ); private static readonly ImmutableArray SyntaxKindsToRegister = ImmutableArray.Create( SyntaxKind.IdentifierName, SyntaxKind.GenericName, SyntaxKind.DefaultLiteralExpression ); private readonly IAnalyzerProvider _analyzerProvider; public NsDepCopAnalyzer() { _analyzerProvider = new AnalyzerProvider( new DependencyAnalyzerFactory(LogTraceMessage), new AssemblyDependencyAnalyzerFactory(), new ConfigProviderFactory(LogTraceMessage), new TypeDependencyEnumerator(new SyntaxNodeAnalyzer(), LogTraceMessage) ); } public override ImmutableArray SupportedDiagnostics => DiagnosticDescriptors; public override void Initialize(AnalysisContext analysisContext) { analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); analysisContext.EnableConcurrentExecution(); analysisContext.RegisterCompilationStartAction(OnCompilationStart); analysisContext.RegisterCompilationAction(OnCompilation); } private void OnCompilationStart(CompilationStartAnalysisContext compilationStartContext) { if (GlobalSettings.IsToolDisabled()) { compilationStartContext.RegisterSyntaxTreeAction(i => ReportForSyntaxTree(i, DiagnosticDefinitions.ToolDisabled)); return; } var configFilePath = GetConfigFilePath(compilationStartContext.Options.AdditionalFiles); if (configFilePath == null || !File.Exists(configFilePath)) { compilationStartContext.RegisterSyntaxTreeAction(i => ReportForSyntaxTree(i, DiagnosticDefinitions.NoConfigFile)); return; } var dependencyAnalyzer = _analyzerProvider.GetDependencyAnalyzer(configFilePath); if (dependencyAnalyzer.ConfigState == AnalyzerConfigState.ConfigError) { var exceptionMessage = dependencyAnalyzer.ConfigException.Message; compilationStartContext.RegisterSyntaxTreeAction(i => ReportForSyntaxTree(i, DiagnosticDefinitions.ConfigException, exceptionMessage)); return; } if (dependencyAnalyzer.ConfigState == AnalyzerConfigState.Disabled) { compilationStartContext.RegisterSyntaxTreeAction(i => ReportForSyntaxTree(i, DiagnosticDefinitions.ConfigDisabled)); return; } // Per-compilation dependency issue counter. var issueCount = 0; compilationStartContext.RegisterSyntaxNodeAction( i => AnalyzeSyntaxNodeAndReportDiagnostics(i, dependencyAnalyzer, ref issueCount), SyntaxKindsToRegister); } private static void AnalyzeSyntaxNodeAndReportDiagnostics( SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, IDependencyAnalyzer dependencyAnalyzer, ref int issueCount) { var maxIssueCount = dependencyAnalyzer.Config.MaxIssueCount; // Not sure whether this method will be called concurrently so to be on the safe side let's access issueCount with interlocked. if (GetInterlocked(ref issueCount) > maxIssueCount) return; var analyzerMessages = dependencyAnalyzer.AnalyzeSyntaxNode(syntaxNodeAnalysisContext.Node, syntaxNodeAnalysisContext.SemanticModel); foreach (var analyzerMessage in analyzerMessages.OfType()) { var currentIssueCount = Interlocked.Increment(ref issueCount); if (currentIssueCount > maxIssueCount) { ReportForSyntaxNode(syntaxNodeAnalysisContext, DiagnosticDefinitions.TooManyDependencyIssues, maxIssueCount); break; } string allowedMemberNames = string.Empty; if (analyzerMessage.AllowedMemberNames.Length > 0) { allowedMemberNames = $" Allowed types are: {string.Join(", ", analyzerMessage.AllowedMemberNames)}"; } ReportForSyntaxNode( syntaxNodeAnalysisContext, DiagnosticDefinitions.IllegalDependency, analyzerMessage.IllegalDependency.FromNamespaceName, analyzerMessage.IllegalDependency.ToNamespaceName, analyzerMessage.IllegalDependency.FromTypeName, analyzerMessage.IllegalDependency.ToTypeName, allowedMemberNames ); } } /// /// Reading an int that's updated by Interlocked on other threads: https://stackoverflow.com/a/24893231/38186 /// private static int GetInterlocked(ref int issueCount) => Interlocked.CompareExchange(ref issueCount, 0, 0); private void OnCompilation(CompilationAnalysisContext compilationAnalysisContext) { if (GlobalSettings.IsToolDisabled()) { ReportForCompilationAnalysisContext(compilationAnalysisContext, DiagnosticDefinitions.ToolDisabled); return; } var configFilePath = GetConfigFilePath(compilationAnalysisContext.Options.AdditionalFiles); if (configFilePath == null || !File.Exists(configFilePath)) { ReportForCompilationAnalysisContext(compilationAnalysisContext, DiagnosticDefinitions.NoConfigFile); return; } var assemblyDependencyAnalyzer = _analyzerProvider.GetAssemblyDependencyAnalyzer(configFilePath); if (assemblyDependencyAnalyzer.ConfigState == AnalyzerConfigState.ConfigError) { var exceptionMessage = assemblyDependencyAnalyzer.ConfigException.Message; ReportForCompilationAnalysisContext(compilationAnalysisContext, DiagnosticDefinitions.ConfigException, exceptionMessage); return; } if (assemblyDependencyAnalyzer.ConfigState == AnalyzerConfigState.Disabled) { ReportForCompilationAnalysisContext(compilationAnalysisContext, DiagnosticDefinitions.ConfigDisabled); return; } AssemblyIdentity sourceAssembly = compilationAnalysisContext.Compilation.Assembly.Identity; ImmutableArray referencedAssemblies = compilationAnalysisContext.Compilation.ReferencedAssemblyNames.ToImmutableArray(); var analyzerMessages = assemblyDependencyAnalyzer.AnalyzeProject(sourceAssembly, referencedAssemblies); foreach (var illegalAssemblyDependencyMessage in analyzerMessages.OfType()) { ReportForCompilationAnalysisContext( compilationAnalysisContext, DiagnosticDefinitions.IllegalAssemblyDependency, illegalAssemblyDependencyMessage.IllegalAssemblyDependency.FromAssembly.Name, illegalAssemblyDependencyMessage.IllegalAssemblyDependency.ToAssembly.Name ); } } private static void ReportForSyntaxTree( SyntaxTreeAnalysisContext syntaxTreeAnalysisContext, DiagnosticDescriptor diagnosticDescriptor, params object[] messageArgs) { var location = Location.Create(syntaxTreeAnalysisContext.Tree, TextSpan.FromBounds(0, 0)); var diagnostic = CreateDiagnostic(diagnosticDescriptor, location, messageArgs); syntaxTreeAnalysisContext.ReportDiagnostic(diagnostic); } private static void ReportForSyntaxNode( SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, DiagnosticDescriptor diagnosticDescriptor, params object[] messageArgs) { var node = syntaxNodeAnalysisContext.Node; var location = Location.Create(node.SyntaxTree, node.Span); var diagnostic = CreateDiagnostic(diagnosticDescriptor, location, messageArgs); syntaxNodeAnalysisContext.ReportDiagnostic(diagnostic); } private static void ReportForCompilationAnalysisContext( CompilationAnalysisContext compilationAnalysisContext, DiagnosticDescriptor diagnosticDescriptor, params object[] messageArguments) { var diagnostic = CreateDiagnostic(diagnosticDescriptor, null, messageArguments); compilationAnalysisContext.ReportDiagnostic(diagnostic); } private static Diagnostic CreateDiagnostic(DiagnosticDescriptor diagnosticDescriptor, Location location, params object[] messageArgs) { return Diagnostic.Create(diagnosticDescriptor, location, messageArgs); } private static string GetConfigFilePath(ImmutableArray additionalFiles) { return additionalFiles.FirstOrDefault(IsConfigFile)?.Path; } private static bool IsConfigFile(AdditionalText additionalText) { return string.Equals(Path.GetFileName(additionalText.Path), ProductConstants.DefaultConfigFileName, StringComparison.OrdinalIgnoreCase); } private static void LogTraceMessage(string message) => Debug.WriteLine($"[{ProductConstants.ToolName}] {message}"); } } ================================================ FILE: source/NsDepCop.Analyzer/Util/ConcurrentDictionaryExtensions.cs ================================================ using System; using System.Collections.Concurrent; namespace Codartis.NsDepCop.Util { /// /// Extension methods for ConcurrentDictionary. /// public static class ConcurrentDictionaryExtensions { /// /// Same as the built-in ConcurrentDictionary.GetOrAdd method /// but also tells whether a new value was created or a stored value was returned. /// /// The type of the dictionary's key. /// The type of the dictionary's value. /// A concurrent dictionary object. /// A key. /// A delegate that generates a value for a key. /// True if a new value was generated and added, false otherwise. /// The value corresponding for the given key. public static TValue GetOrAdd(this ConcurrentDictionary dict, TKey key, Func generator, out bool added) { while (true) { if (dict.TryGetValue(key, out TValue value)) { added = false; return value; } value = generator(key); if (dict.TryAdd(key, value)) { added = true; return value; } } } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/DictionaryExtensions.cs ================================================ using System.Collections.Generic; using System.Linq; namespace Codartis.NsDepCop.Util { /// /// Static helper class that implements extension methods for a dictionary whose value is a collection type. /// public static class DictionaryExtensions { /// /// If the dictionary does not contain the given key then simply adds it with the given values. /// If it already contains the key the union's it value with the given values. /// /// The type of the dictionary's key. /// The type of the dictionary's value. It must be a collection. /// The type of the collection's elements. /// A dictionary object. /// The key to be added or updated. /// The collection of newValues to be added or unioned into the dictionary, public static void AddOrUnion(this Dictionary dictionary, TKey key, TCollection newValues) where TCollection : class, ICollection, new() { if (!dictionary.ContainsKey(key)) { dictionary.Add(key, newValues); return; } var oldValues = dictionary[key]; if (oldValues == null && newValues != null) { dictionary[key] = newValues; return; } if (oldValues != null && newValues != null) { var newCollection = new TCollection(); foreach (var value in oldValues.Union(newValues)) newCollection.Add(value); dictionary[key] = newCollection; } } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/EnumerableExtensions.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Text; namespace Codartis.NsDepCop.Util { /// /// Static helper class that implements extension methods for IEnumerable[T] /// public static class EnumerableExtensions { /// /// Returns an empty collection for a null value. /// /// Any type. /// A collection. /// The collection itself if it was not null or an empty collection if it was null. public static IEnumerable EmptyIfNull(this IEnumerable collection) { return collection ?? Enumerable.Empty(); } /// /// Returns a value indicating that the collection is null or empty. /// /// Any type. /// A collection. /// True if the collection is null or empty. False if it is not empty. public static bool IsNullOrEmpty(this IEnumerable collection) { return collection == null || !collection.Any(); } /// /// Converts a collection into a single string by concatenating the string representation of the collection elements /// using optional separator and wrapper strings. /// /// Any type. /// A collection. /// A string that will be put between every to item. Null means no separator. /// A string that will precede every item. Null means no left wrapper. /// A string that will follow every item. Null means no right wrapper. /// The string representation of the collection. public static string ToSingleString(this IEnumerable collection, string separator, string leftWrapper, string rightWrapper) { var stringBuilder = new StringBuilder(); foreach (var item in collection) { // Apply separator if necessary. if (stringBuilder.Length > 0) { if (!string.IsNullOrEmpty(separator)) stringBuilder.Append(separator); } // Apply left wrapper string if necessary. if (!string.IsNullOrEmpty(leftWrapper)) stringBuilder.Append(leftWrapper); // Append the item's string representation. stringBuilder.Append(item); // Apply right wrapper string if necessary. if (!string.IsNullOrEmpty(rightWrapper)) stringBuilder.Append(rightWrapper); } return stringBuilder.ToString(); } public static IEnumerable ToEnumerable(this T item) { if (item != null) yield return item; } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/ICacheStatistics.cs ================================================ namespace Codartis.NsDepCop.Util { /// /// Provides cache efficiency statistics. /// public interface ICacheStatisticsProvider { int HitCount { get; } int MissCount { get; } double EfficiencyPercent { get; } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/IDateTimeProvider.cs ================================================ using System; namespace Codartis.NsDepCop.Util { /// /// Provides date and time information. /// /// /// Introduced for testability. /// public interface IDateTimeProvider { /// /// Returns the current UTC date and time. /// DateTime UtcNow { get; } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/IDiagnosticSupport.cs ================================================ using System.Collections.Generic; namespace Codartis.NsDepCop.Util { /// /// Provides diagnostic support operations. /// public interface IDiagnosticSupport { /// /// Returns the state of the object as a string collection /// IEnumerable ToStrings(); } } ================================================ FILE: source/NsDepCop.Analyzer/Util/IndentHelper.cs ================================================ using System.Collections.Generic; using System.Linq; namespace Codartis.NsDepCop.Util { /// /// Static helper class that formats strings with indentation. /// public static class IndentHelper { public const int IndentSize = 2; public static IEnumerable Indent(string message, int indent = 0) => Indent(new [] {message}, indent); public static IEnumerable Indent(IEnumerable messages, int indent = 0) => messages.Select(i => Format(i, indent)); private static string Format(string message, int indent = 0) => $"{new string(' ', indent * IndentSize)}{message}"; } } ================================================ FILE: source/NsDepCop.Analyzer/Util/LinqExtensions.cs ================================================ using System; using System.Collections.Generic; namespace Codartis.NsDepCop.Util { public static class LinqExtensions { public static TSource MinByOrDefault(this IEnumerable source, Func selector) { return source.MinByOrDefault(selector, Comparer.Default); } public static TSource MinByOrDefault(this IEnumerable source, Func selector, IComparer comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); if (comparer == null) throw new ArgumentNullException(nameof(comparer)); using (var sourceIterator = source.GetEnumerator()) { if (!sourceIterator.MoveNext()) { return default(TSource); } var min = sourceIterator.Current; var minKey = selector(min); while (sourceIterator.MoveNext()) { var candidate = sourceIterator.Current; var candidateProjected = selector(candidate); if (comparer.Compare(candidateProjected, minKey) < 0) { min = candidate; minKey = candidateProjected; } } return min; } } public static TSource MaxByOrDefault(this IEnumerable source, Func selector) { return source.MaxByOrDefault(selector, Comparer.Default); } public static TSource MaxByOrDefault(this IEnumerable source, Func selector, IComparer comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); if (comparer == null) throw new ArgumentNullException(nameof(comparer)); using (var sourceIterator = source.GetEnumerator()) { if (!sourceIterator.MoveNext()) { return default(TSource); } var max = sourceIterator.Current; var maxKey = selector(max); while (sourceIterator.MoveNext()) { var candidate = sourceIterator.Current; var candidateProjected = selector(candidate); if (comparer.Compare(candidateProjected, maxKey) > 0) { max = candidate; maxKey = candidateProjected; } } return max; } } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/MathHelper.cs ================================================ namespace Codartis.NsDepCop.Util { /// /// Static helper class for simple math operations. /// public static class MathHelper { public static double CalculatePercent(double part, double total) { // ReSharper disable once CompareOfFloatsByEqualityOperator return total == 0 ? 0 : part / total; } } } ================================================ FILE: source/NsDepCop.Analyzer/Util/MessageHandler.cs ================================================ namespace Codartis.NsDepCop.Util { /// /// A delegate that receives a string message. /// /// A message. public delegate void MessageHandler(string message); } ================================================ FILE: source/NsDepCop.Analyzer/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Benchmarks/NsDepCop.Benchmarks.RuleTypesBenchmarks-report-github.md ================================================ ``` BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5608/22H2/2022Update) Intel Core i7-10850H CPU 2.70GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK 9.0.201 [Host] : .NET 8.0.14 (8.0.1425.11118), X64 RyuJIT AVX2 DefaultJob : .NET 8.0.14 (8.0.1425.11118), X64 RyuJIT AVX2 ``` | Method | Iterations | regexCompilationMode | useStaticRegexCache | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |------------------- |----------- |--------------------- |-------------------- |---------------:|-------------:|------------:|----------:|----------:|----------:| | SimpleRule | 1000 | ? | ? | 274.7 μs | 2.82 μs | 2.50 μs | 187.5000 | 0.4883 | 1.12 MB | | WildcardRule | 1000 | ? | ? | 493.7 μs | 3.13 μs | 2.93 μs | 259.7656 | 0.9766 | 1.56 MB | | RegexRule_Instance | 1000 | Interpreted | ? | 507.8 μs | 4.79 μs | 4.24 μs | 187.5000 | - | 1.13 MB | | RegexRule_Static | 1000 | Compiled | True | 536.1 μs | 10.63 μs | 9.94 μs | 221.6797 | - | 1.33 MB | | RegexRule_Static | 1000 | Interpreted | True | 642.8 μs | 6.56 μs | 6.13 μs | 221.6797 | - | 1.33 MB | | RegexRule_Instance | 1000 | Compiled | ? | 1,065.3 μs | 3.66 μs | 3.24 μs | 189.4531 | 19.5313 | 1.13 MB | | RegexRule_Static | 1000 | Interpreted | False | 2,859.5 μs | 28.90 μs | 24.13 μs | 1312.5000 | - | 7.94 MB | | RegexRule_Static | 1000 | Compiled | False | 1,948,017.4 μs | 10,476.50 μs | 8,748.35 μs | 4000.0000 | 3000.0000 | 29.51 MB | ================================================ FILE: source/NsDepCop.Benchmarks/NsDepCop.Benchmarks.csproj ================================================  Exe net8.0 false true latest ================================================ FILE: source/NsDepCop.Benchmarks/Program.cs ================================================ using BenchmarkDotNet.Running; namespace NsDepCop.Benchmarks; public class Program { public static void Main(string[] args) { BenchmarkRunner.Run(); } } ================================================ FILE: source/NsDepCop.Benchmarks/RuleTypesBenchmarks.cs ================================================ using System.Text.RegularExpressions; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Order; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Test.Implementation.Analysis; using FluentAssertions; namespace NsDepCop.Benchmarks; [MemoryDiagnoser] [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class RuleTypesBenchmarks { [Params(1000)] public int Iterations { get; set; } [Benchmark] public void SimpleRule() { var config = new DependencyRulesBuilder().AddAllowed(new Domain("A.B.C"), new Domain("D.E.F")); BenchmarkCore(config, Iterations); } [Benchmark] public void WildcardRule() { var config = new DependencyRulesBuilder().AddAllowed(new WildcardDomain("A.?.?"), new WildcardDomain("D.*")); BenchmarkCore(config, Iterations); } /// /// Benchmark using a Regex object instance, with or without RegexOptions.Compiled. /// [Benchmark] [Arguments(RegexCompilationMode.Compiled)] [Arguments(RegexCompilationMode.Interpreted)] public void RegexRule_Instance(RegexCompilationMode regexCompilationMode) { var regexDomain = new RegexDomain("/[A-Z.]*/", validate: true, RegexUsageMode.Instance, regexCompilationMode); var config = new DependencyRulesBuilder().AddAllowed(regexDomain, regexDomain); BenchmarkCore(config, Iterations); } /// /// Benchmark using the static Regex.IsMatch method, with or without RegexOptions.Compiled, /// and with or without using the built-in cache for static Regex.IsMatch calls. /// The reason behind measuring performance also with the static Regex cache turned off, /// is that in real-world usage there probably will be more than 15 different patterns, /// so the cache won't be effective anyway. /// /// /// See: https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-regex /// By default, the last 15 most recently used static regular expression patterns are cached. /// For applications that require a larger number of cached static regular expressions, /// the size of the cache can be adjusted by setting the Regex.CacheSize property. /// [Benchmark] [Arguments(RegexCompilationMode.Compiled, true)] [Arguments(RegexCompilationMode.Compiled, false)] [Arguments(RegexCompilationMode.Interpreted, true)] [Arguments(RegexCompilationMode.Interpreted, false)] public void RegexRule_Static(RegexCompilationMode regexCompilationMode, bool useStaticRegexCache) { Regex.CacheSize = useStaticRegexCache ? 15 : 0; var regexDomain = new RegexDomain("/[A-Z.]*/", validate: true, RegexUsageMode.Static, regexCompilationMode); var config = new DependencyRulesBuilder().AddAllowed(regexDomain, regexDomain); BenchmarkCore(config, Iterations); } private static void BenchmarkCore(IDependencyRules config, int iterations) { var typeDependencyValidator = new TypeDependencyValidator(config); for (var i = 0; i < iterations; i++) { var dependencyStatus = typeDependencyValidator.IsAllowedDependency(new TypeDependency("A.B.C", "T", "D.E.F", "T", default)); dependencyStatus.IsAllowed.Should().BeTrue(); } } } ================================================ FILE: source/NsDepCop.Benchmarks/readme.txt ================================================ How to run the benchmarks? 1. Build the solution with Release config. 2. Run the NsDepCop.Benchmarks project. 3. Compare the new results (in the NsDepCop.Benchmarks\bin\Release\net8.0\BenchmarkDotNet.Artifacts\results folder) with the saved result (in the NsDepCop.Benchmarks folder). ================================================ FILE: source/NsDepCop.ConfigSchema/NsDepCopCatalog.xml ================================================ ================================================ FILE: source/NsDepCop.ConfigSchema/NsDepCopConfig.xsd ================================================  The root tag for NsDepCop config files. Add any number of Allowed, Disallowed and VisibleMembers children in any order. Defines an allowed namespace dependency. Defines a disallowed namespace dependency. Defines the types of a namespace that are visible for all other namespaces. Empty type list means no restriction. Defines an allowed assembly dependency. Defines a disallowed assembly dependency. Set the number of folder levels to traverse upwards to find and combine config files. Default is 0 (no inheritance). Set to true to perform analysis on the current project, false to skip analysis. Default is true. Sets the severity of the dependency violations: Info, Warning (default), Error. Sets the maximum number of issues reported from a project. After reaching this number analysis stops. Default is 100. Sets the severity of the event when MaxIssueCount is reached: Info, Warning (default), Error. If set to true then after analysis automatically sets MaxIssueCount to the current issue count if it is lower than the current MaxIssueCount. Default is false. Set to true to enable types to depend on any other types defined in parent namespaces. Default is false (for backward compatibility). Set to true to enable all parent namespaces to depend on any of their children namespaces. Default is false. Sets the importance level of NsDepCop information messages: Low, Normal (default), High. This setting combined with MsBuild verbosity will determine whether information messages show up in the output or not. Sets the interval lengths between subsequent retries when calling the analyzer service. Must be a list of integers (milliseconds) separated with comma. E.g.: 100,500,1000,5000 File patterns that define which source files should be excluded from the analysis. Comma separated list of https://github.com/dazinator/DotNet.Glob patterns. Paths are relative to the config file's folder. E.g.: **/*.g.cs,TestFiles/*.cs We adopt the 'disallowed-by-default' approach for assembly dependencies check, similar to how we handle namespace dependencies (where everything is disallowed unless explicitly permitted). To ensure the backward compatibility, this configuration attribute has been introduced to explicitly enable the assembly dependency checking. By default this attribute is false. The fully qualified name of the namespace that depends on the other namespace. The fully qualified name of the namespace that the other namespace depends on. Defines the types of the target namespace that are visible to the source namespace(s). Omitting this element or empty type list means no restriction. Specifies a type. The fully qualified name of the namespace whose types are listed. A type name without namespace qualification. Generic type names must be in metadata format (eg: List`1). ================================================ FILE: source/NsDepCop.NuGet/NsDepCop.NuGet.csproj ================================================  netstandard2.0 false true true true true true NsDepCop 2.7.0-local NsDepCop - Namespace and Assembly Dependency Checker Tool for C# NsDepCop is a static code analysis tool that enforces namespace and assembly dependency rules in C# projects. static code analysis analyzer tool C# csharp namespace type assembly dependency dependencies Copyright 2013-2025 Ferenc Vizkeleti Ferenc Vizkeleti GPL-2.0-only false https://github.com/realvizu/NsDepCop https://github.com/realvizu/NsDepCop.git https://raw.githubusercontent.com/realvizu/NsDepCop/master/images/icons/NsDepCop_128.png true - Perf: Avoids unnecessary rebuilds when only a solution-level config.nsdepcop file is used (no project-level files). $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput ================================================ FILE: source/NsDepCop.NuGet/tools/install.ps1 ================================================ param($installPath, $toolsPath, $package, $project) if($project.Object.SupportsPackageDependencyResolution) { if($project.Object.SupportsPackageDependencyResolution()) { # Do not install analyzers via install.ps1, instead let the project system handle it. return } } $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve foreach($analyzersPath in $analyzersPaths) { if (Test-Path $analyzersPath) { # Install the language agnostic analyzers. foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } } # $project.Type gives the language name like (C# or VB.NET) $languageFolder = "" if($project.Type -eq "C#") { $languageFolder = "cs" } if($project.Type -eq "VB.NET") { $languageFolder = "vb" } if($languageFolder -eq "") { return } foreach($analyzersPath in $analyzersPaths) { # Install language specific analyzers. $languageAnalyzersPath = join-path $analyzersPath $languageFolder if (Test-Path $languageAnalyzersPath) { foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } } # SIG # Begin signature block # MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s # FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDYEwggX/MIID56ADAgECAhMzAAABh3IX # chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB # znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH # sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d # weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ # itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV # Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy # S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K # NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV # BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr # qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx # zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe # yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g # yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf # AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI # 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 # GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea # jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgRjg7DcI6 # uhYfXWwAQ6hK0mPW7iyr2tzHR0DHSDJkscIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQCMjRK3YvDB0Sy30hadcXmGkSLrq7U7R+bkBh+FsWz8 # CLSdV9Sh1z6mmVlEk6jHf7h3fiMEZwXGSvBEH4dc+equuU9KhYkhfTTfd3SSCER6 # swWv/vqzQgz7WZOzuDuOrMc4lqCc3qUeLCeFnZEjfDKgymUi7UaTDofM0HNZtYA1 # f2kLORo2CzvvZLHK+xQYvefFFrCsHSiNFvH5zM9142c1aMpGVgyuB6cBxL3johS/ # 7i4myLHr9LB7GcSmEYqDH5q4mxgNNtunYqEyK0V0b/UI0b9q1p50KJPag9zh/HB0 # Q+fY1t8guPwGoxkO+hqDV6k1R3tJBHG5c8kDSCVR/CbVoYIS8TCCEu0GCisGAQQB # gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIHQWatv8aXHB68pc0SETnr9LF64Haou9lbLES5Og # 6ES0AgZgDz5A4W0YEzIwMjEwMTI4MTM1MzExLjc1N1owBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA # AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD # LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/ # Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB # h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT # 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z # QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D # 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2 # I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH # LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX # ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX # ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R # 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW # aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2 # MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOO8XzYwIhgPMjAyMTAxMjgwMTUyNTRaGA8yMDIxMDEyOTAxNTI1NFowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA47xfNgIBADAKAgEAAgIbLwIB/zAHAgEAAgITKTAK # AgUA472wtgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAC77bUsJUEVTSYlY # wKFpNKL8dbjlzb8EutPbxa952QSEIxgMUycq0Db9t3QSPfWd919JnobZ+0PCoE2A # Pu4Os73CDKVzF+bwFgLRnttBccwAsgy7d1pWtQcfyIh9O+l/HislSXsFH5zawEs5 # 9uvH8UtGX1jExKlXKnmQriZYTlTCMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQgfLtPMEgO0BfdMeIvsY3gSnjcAKLOUnkiV89CXQ7fmqkwgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07 # CQl1DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # Jt+6SyK5goIHAAAAAAEmMCIEILgdbWcIe050A6mMuy1I+6mT4dgtx0U15aTsUnn0 # tfP4MA0GCSqGSIb3DQEBCwUABIIBAI2O4PAlewLHuXky0KYOlR03qPHYvVDSIZWv # ydszjrgdE02PX1qWo6ZyiuPuyM+ssKiHImpy/wCt2aQk/PPAOIgJVhyhsyc2N0h/ # Hb1cBDGdpwfDndZquf5pwrqfNm2KM9tk7nDkKa0O0C7VpfOgSdamThZiaOZDJVhp # A3pTsR1LhA17Wih8bgDRsZRCiEPhrfeWBMb3nMjyox3zg+XL0yFmvfcyOWkn/I3o # 4jj8KKqJ2SwJTJqRWqNg036ilkaayzpFA2XX55s2cdAmPpVh8VuTRLrKVx00/TXS # hGHM1fgii3urBBNUn8TSXTcUZFpnb9dWJFbxi0aHcHq8iq0J90g= # SIG # End signature block ================================================ FILE: source/NsDepCop.NuGet/tools/uninstall.ps1 ================================================ param($installPath, $toolsPath, $package, $project) if($project.Object.SupportsPackageDependencyResolution) { if($project.Object.SupportsPackageDependencyResolution()) { # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. return } } $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve foreach($analyzersPath in $analyzersPaths) { # Uninstall the language agnostic analyzers. if (Test-Path $analyzersPath) { foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) } } } } # $project.Type gives the language name like (C# or VB.NET) $languageFolder = "" if($project.Type -eq "C#") { $languageFolder = "cs" } if($project.Type -eq "VB.NET") { $languageFolder = "vb" } if($languageFolder -eq "") { return } foreach($analyzersPath in $analyzersPaths) { # Uninstall language specific analyzers. $languageAnalyzersPath = join-path $analyzersPath $languageFolder if (Test-Path $languageAnalyzersPath) { foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) { if($project.Object.AnalyzerReferences) { try { $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) } catch { } } } } } # SIG # Begin signature block # MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL # yXrxJhYfmibzcOh8caqC0uZprfczDaCCDYEwggX/MIID56ADAgECAhMzAAABh3IX # chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB # znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH # sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d # weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ # itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV # Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy # S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K # NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV # BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr # qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx # zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe # yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g # yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf # AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI # 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 # GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea # jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgF1ypFyzl # AvvWGVCeXczrfpXmJNm9vpyjcwd4y4ivfqowQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQAlvp3kobQ3njGrv7T9mKm8viQ7IsK06yFq43ZwDIob # rkxRRMZKKJfaJ2pkkTtMun/wzMjQ7hqbULUO22lywwNy3t7uNINETfPReIYNIG9p # KZ3t8zI1T+7B9Vpm5t9LC9hQp1yrj88LcA1QAaFcvjn1sxUOGlLFK2jsa2AUcrwr # QpiVDX+OOdExw+EJTUBil/kvAtXHdCT1qkH3JzzSBYiCLGq2pW4AuVvlL0iiRKeT # /AW6TY9SuOUoG0aOZZzLAuCp6qA7ovKqRAGoFpcAaGXQokWRLU4se3/2meBksksu # ZlP1paBMHbT1ZLJP095SzhwYfsw7IDkC0Zt1OLq7IdwwoYIS8TCCEu0GCisGAQQB # gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIMdwQTCmst1vtKyhKIg+hpE9VQEH7XzRp1eSg9au # tOz7AgZgEAenLh4YEzIwMjEwMTI4MTM1MzExLjM0N1owBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjo0NjJGLUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJMvNAqEXcFyaAAAA # AAEkMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTE5MTIxOTAxMTQ1N1oXDTIxMDMxNzAxMTQ1N1owgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0NjJG # LUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCbKjgNnhjvMlnRNAtx # 7X3N5ZZkSfUFULyWsVJ1pnyMsSITimg1q3OQ1Ikf0/3gg8UG5TIRm7wH8sjBtoB3 # nuzFz11CegIFcanYnt050JvnrUTKeAPUR5pLpTeP3QEgL+CWOc4lTg/XxjnQv01F # D7TTn9DEuO3kp0GQ87Mjd5ssxK0K1q4IWNFAyRpx5n8Vm3Vm1iiVL5FMDUKsl5G/ # SqQdiEDn8cqYbqWMVzWH94PdKdw1mIHToBRCNsR9BHHWzNkSS+R0WRipBSSorKT7 # cuLlEBYhDo8AY3uMGlv0kLRLHASZ+sz2nfkpW2CVt+bHhVmM6/5qiu2f7eYoTYJu # cFECAwEAAaOCARswggEXMB0GA1UdDgQWBBS7HdFyrGKIhDxvypLA1lD/wGRSsDAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBo3QzNuNRgzdflwA4U7f3e2CcGlwdg # 6ii498cBiSrTpWKO3qqz5pvgHAk4hh6y/FLY80R59inLwcVuyD24S3JEdSie4y1t # C5JptweR1qlxRJCRM4vG7nPtIC4eAMKcXgovu0mTFv7xpFAVpRuvuepR91gIde32 # 8lv1HTTJCV/LBBk83Xi7nCGPF59FxeIrcE32xt4YJgEpEAikeMqvWCTMyPqlmvx9 # J92fxU3cQcw2j2EWwqOD5T3Nz2HWfPV80sihD1A6Y5HhjpS9taDPs7CI58I211F3 # ysegNyOesG3MTrSJHyPMLKYFDxcG1neV0liktv+TW927sUOVczcSUhQLMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0 # NjJGLUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUAlwPlNCq+Un54UfxLe/wKS1Xc4nqggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOO9KO8wIhgPMjAyMTAxMjgxNjEzMzVaGA8yMDIxMDEyOTE2MTMzNVowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA470o7wIBADAKAgEAAgIdzgIB/zAHAgEAAgITSTAK # AgUA4756bwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEiUh4QVb7vaceUo # /TtRAhp98XfrRN5mZ4KyZDuNDYKfBaGx8hnyD2BgIjkQ59KzIVNfkv8PBXhLaQK7 # u7k4S23kUWQ/zsRYm5EutcKXB3zrW4Ym0TpwiMTJ8arMFj3BIYjZCMFqUxdAxiH/ # CD+FIK0vvPSTRZ00KPqi5idqCj3TMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAEky80CoRdwXJoAAAAAASQwDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQgrLzvm34azoh15/K+sBdmMol8NxD/FT4rQfDgBXN/atwwgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCBiOOHoohqL+X7Xa/25jp1wTrQxYlYGLszis/nA # TirjIDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # JMvNAqEXcFyaAAAAAAEkMCIEILvfBT5GpMSMrQo5TU1VuEC6URcoooi2maNoHgLo # yvsvMA0GCSqGSIb3DQEBCwUABIIBAIxWH4gHkyfHcF3w9aW9L1OaBOjv/Zmq7V1R # pj4tkkyuQKPqIi9gdq4wMxToI1m4IM/9I8FwLI1LH/6AWBLTfdkFr4mOMFyflO5v # gya84Pij48E/37XOOBG5S454c4Sgj6OQcZb1Ljb+QNf+6JovAhUdLL4ZLmdvuK5X # amv+cjYVV3jSJYMSx8d0w22rPmJVB25h1NZlJvPKZdKGc28ub2IxnvALF6SYaR5u # AQVzIzltI+lm1Owqt9JAuuif6YYhh/P4Z4PfXWtY/2xsIqYHbQwizlu/3Hs9UK60 # VE+jyR89LdWrWqCW0orWrNwUFBiYIuupntcRH/1LMk6Tr/CJa64= # SIG # End signature block ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeatureTests.cs ================================================ using Xunit; namespace Codartis.NsDepCop.SourceTest { /// /// Tests analyzer features on source files. /// /// /// The name of the source file and its containing folder is the same as the name of the test. /// public class AnalyzerFeatureTests { [Fact] public void AnalyzerFeature_AllowedDependency() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_DisallowedDependency() { SourceTestSpecification.Create() .ExpectInvalidSegment(5, 19, 25) .ExpectInvalidSegment(6, 17, 29) .ExpectInvalidSegment(7, 24, 28) .Execute(); } [Fact] public void AnalyzerFeature_ExcludedFiles() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_ExcludedFiles_WithWildcard() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_SameNamespaceAlwaysAllowed() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_SameNamespaceAllowedEvenWhenVisibleMembersDefined() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_ChildCanDependOnParentImplicitly() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_ParentCanDependOnChildImplicitly() { SourceTestSpecification.Create().Execute(); } [Fact] public void AnalyzerFeature_VisibleMembersOfNamespace() { string[] members = new[] { "VisibleType", "OnlyGenericIsVisibleType`1" }; SourceTestSpecification.Create() // A -> C .ExpectInvalidSegment(6, 19, 32, members) .ExpectInvalidSegment(8, 19, 43, members) .ExpectInvalidSegment(9, 19, 47, members) // B -> C .ExpectInvalidSegment(20, 19, 32, members) .ExpectInvalidSegment(22, 19, 43, members) .ExpectInvalidSegment(23, 19, 47, members) .Execute(); } [Fact] public void AnalyzerFeature_VisibleMembersOfAllowedRule() { string[] members = new[] { "VisibleType", "OnlyGenericIsVisibleType`1" }; SourceTestSpecification.Create() // A -> C .ExpectInvalidSegment(6, 19, 32, members) .ExpectInvalidSegment(8, 19, 43, members) .ExpectInvalidSegment(9, 19, 47, members) .Execute(); } [Fact] public void AnalyzerFeature_WithTopLevelStatement() { SourceTestSpecification.Create() .ExpectInvalidSegment(1, 8, 12) .Execute(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication); } } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_AllowedDependency/AnalyzerFeature_AllowedDependency.cs ================================================ namespace A { public class NoIssue { private B.MyEnum field1; private MyGlobalEnum field2; private System.Type field3; } } namespace B { public enum MyEnum { } } public enum MyGlobalEnum { } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_AllowedDependency/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ChildCanDependOnParentImplicitly/AnalyzerFeature_ChildCanDependOnParentImplicitly.cs ================================================ namespace A.B { public class NoIssue { private A.MyEnum field1; private MyGlobalEnum field2; } } namespace A { public enum MyEnum { } } public enum MyGlobalEnum { } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ChildCanDependOnParentImplicitly/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_DisallowedDependency/AnalyzerFeature_DisallowedDependency.cs ================================================ namespace A { public class NoIssue { private B.MyEnum field1; private MyGlobalEnum field2; private System.Type field3; } } namespace B { public enum MyEnum { } } public enum MyGlobalEnum { } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_DisallowedDependency/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ExcludedFiles/AnalyzerFeature_ExcludedFiles.cs ================================================ namespace A { public class MyClass { private B.MyEnum field1; } } namespace B { public enum MyEnum { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ExcludedFiles/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ExcludedFiles_WithWildcard/AnalyzerFeature_ExcludedFiles_WithWildcard.cs ================================================ namespace A { public class MyClass { private B.MyEnum field1; } } namespace B { public enum MyEnum { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ExcludedFiles_WithWildcard/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ParentCanDependOnChildImplicitly/AnalyzerFeature_ParentCanDependOnChildImplicitly.cs ================================================ public class NoIssue { private A.MyEnum field1; private A.B.MyGlobalEnum field2; } namespace A { public enum MyEnum { } } namespace A.B { public enum MyGlobalEnum { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_ParentCanDependOnChildImplicitly/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_SameNamespaceAllowedEvenWhenVisibleMembersDefined/AnalyzerFeature_SameNamespaceAllowedEvenWhenVisibleMembersDefined.cs ================================================ namespace A { public class NoIssue { private A.MyEnum field; } public enum MyEnum { } public class OnlyTypeVisibleOutside { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_SameNamespaceAllowedEvenWhenVisibleMembersDefined/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_SameNamespaceAlwaysAllowed/AnalyzerFeature_SameNamespaceAlwaysAllowed.cs ================================================ namespace A { public class NoIssue { private A.MyEnum field; } public enum MyEnum { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_SameNamespaceAlwaysAllowed/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_VisibleMembersOfAllowedRule/AnalyzerFeature_VisibleMembersOfAllowedRule.cs ================================================ namespace A { public class MyClass { private C.VisibleType field1; private C.InvisibleType field2; private C.OnlyGenericIsVisibleType field3; private C.OnlyGenericIsVisibleType field4; private C.InvisibleGenericType field5; } public enum MyEnum { } } namespace B { public class MyClass { private C.VisibleType field1; private C.InvisibleType field2; private C.OnlyGenericIsVisibleType field3; private C.OnlyGenericIsVisibleType field4; private C.InvisibleGenericType field5; } public enum MyEnum { } } namespace C { public enum VisibleType { } public enum InvisibleType { } public class OnlyGenericIsVisibleType { } public class OnlyGenericIsVisibleType { } public class InvisibleGenericType { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_VisibleMembersOfAllowedRule/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_VisibleMembersOfNamespace/AnalyzerFeature_VisibleMembersOfNamespace.cs ================================================ namespace A { public class MyClass { private C.VisibleType field1; private C.InvisibleType field2; private C.OnlyGenericIsVisibleType field3; private C.OnlyGenericIsVisibleType field4; private C.InvisibleGenericType field5; } public enum MyEnum { } } namespace B { public class MyClass { private C.VisibleType field1; private C.InvisibleType field2; private C.OnlyGenericIsVisibleType field3; private C.OnlyGenericIsVisibleType field4; private C.InvisibleGenericType field5; } public enum MyEnum { } } namespace C { public enum VisibleType { } public enum InvisibleType { } public class OnlyGenericIsVisibleType { } public class OnlyGenericIsVisibleType { } public class InvisibleGenericType { } } ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_VisibleMembersOfNamespace/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_WithTopLevelStatement/AnalyzerFeature_WithTopLevelStatement.cs ================================================ System.Type field3; ================================================ FILE: source/NsDepCop.SourceTest/AnalyzerFeature_WithTopLevelStatement/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6Tests.cs ================================================ using Xunit; namespace Codartis.NsDepCop.SourceTest { /// /// Tests that the analyzer handles various C# 6.0 constructs correctly. /// /// /// The name of the source file and its containing folder is the same as the name of the test. /// public class Cs6Tests { [Fact] public void Cs6_AliasQualifiedName() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 25, 31) .Execute(); } [Fact] public void Cs6_ArrayType() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 19, 25) .ExpectInvalidSegment(11, 20, 27) .ExpectInvalidSegment(12, 20, 27) .Execute(); } [Fact] public void Cs6_QualifiedName() { SourceTestSpecification.Create() .ExpectInvalidSegment(5, 19, 25) .Execute(); } [Fact] public void Cs6_InvocationExpression() { SourceTestSpecification.Create() // Class3 .ExpectInvalidSegment(9, 13, 20) // Class3 .ExpectInvalidSegment(10, 26, 33) // Class3 .ExpectInvalidSegment(11, 20, 27) // Class4 .ExpectInvalidSegment(12, 20, 27) .ExpectInvalidSegment(12, 20, 27) // Class3 .ExpectInvalidSegment(15, 11, 17) .Execute(); } [Fact] public void Cs6_InvocationWithTypeArg() { SourceTestSpecification.Create() // Class3 .ExpectInvalidSegment(10, 28, 34) // Class4 .ExpectInvalidSegment(11, 28, 42) .ExpectInvalidSegment(11, 35, 41) .Execute(); } [Fact] public void Cs6_MemberAccessExpression() { SourceTestSpecification.Create() .ExpectInvalidSegment(9, 37, 47) .Execute(); } [Fact] public void Cs6_GenericName() { SourceTestSpecification.Create() // MyGenericClass .ExpectInvalidSegment(8, 17, 41) // MyGenericClass2 .ExpectInvalidSegment(11, 17, 62) // MyClass3 .ExpectInvalidSegment(11, 33, 41) // MyClass3 .ExpectInvalidSegment(11, 53, 61) // MyGenericClass> .ExpectInvalidSegment(14, 17, 57) // MyGenericClass .ExpectInvalidSegment(14, 32, 56) // MyClass3 .ExpectInvalidSegment(14, 47, 55) .Execute(); } [Fact] public void Cs6_GenericTypeArgument() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 32, 40) .ExpectInvalidSegment(8, 36, 44) .Execute(); } [Fact] public void Cs6_NestedType() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 17, 25) .ExpectInvalidSegment(7, 26, 36) .ExpectInvalidSegment(8, 19, 27) .ExpectInvalidSegment(8, 28, 38) .Execute(); } [Fact] public void Cs6_PointerType() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 19, 25) .ExpectInvalidSegment(11, 20, 27) .ExpectInvalidSegment(12, 20, 27) .Execute(); } [Fact] public void Cs6_VeryComplexType() { SourceTestSpecification.Create() // Class4> .ExpectInvalidSegment(10, 13, 60) .ExpectInvalidSegment(10, 20, 26) .ExpectInvalidSegment(10, 30, 59) .ExpectInvalidSegment(10, 37, 43) .ExpectInvalidSegment(10, 48, 54) // Method2 return value .ExpectInvalidSegment(10, 72, 79) .ExpectInvalidSegment(10, 72, 79) .ExpectInvalidSegment(10, 72, 79) .ExpectInvalidSegment(10, 72, 79) .ExpectInvalidSegment(10, 72, 79) .Execute(); } [Fact] public void Cs6_NullableType() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 17, 25) .Execute(); } [Fact] public void Cs6_EveryUserDefinedTypeKind() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 17, 24) .ExpectInvalidSegment(8, 17, 29) .ExpectInvalidSegment(9, 17, 25) .ExpectInvalidSegment(10, 17, 23) .ExpectInvalidSegment(11, 17, 27) .Execute(); } [Fact] public void Cs6_ExtensionMethodInvocation() { SourceTestSpecification.Create() .ExpectInvalidSegment(9, 27, 44) .ExpectInvalidSegment(10, 27, 51) .Execute(); } [Fact] public void Cs6_ObjectCreationExpression() { SourceTestSpecification.Create() .ExpectInvalidSegment(9, 17, 29) .Execute(); } [Fact] public void Cs6_Var() { SourceTestSpecification.Create() .ExpectInvalidSegment(7, 13, 16) .ExpectInvalidSegment(7, 23, 29) .ExpectInvalidSegment(7, 30, 40) .Execute(); } [Fact] public void Cs6_VarWithConstructedGenericType() { SourceTestSpecification.Create() // var: ClassB`2, EnumB, EnumB .ExpectInvalidSegment(8, 13, 16) .ExpectInvalidSegment(8, 13, 16) .ExpectInvalidSegment(8, 13, 16) // ClassB .ExpectInvalidSegment(8, 23, 54) // EnumB .ExpectInvalidSegment(8, 32, 37) // EnumB .ExpectInvalidSegment(8, 48, 53) // Instance: ClassB`2, EnumB, EnumB .ExpectInvalidSegment(8, 55, 63) .ExpectInvalidSegment(8, 55, 63) .ExpectInvalidSegment(8, 55, 63) .Execute(); } [Fact] public void Cs6_Attributes() { SourceTestSpecification.Create() // assembly and module attributes are not analyzed because there's no enclosing type //// [assembly: A.AllowedAttributeWithTypeArg(typeof(B.ForbiddenType))] //CreateLogEntryParameters(sourceFileName, 3, 14, 3, 41), //CreateLogEntryParameters(sourceFileName, 3, 51, 3, 64), //// [module: A.AllowedAttributeWithTypeArg(typeof(B.ForbiddenType))] //CreateLogEntryParameters(sourceFileName, 4, 12, 4, 39), //CreateLogEntryParameters(sourceFileName, 4, 49, 4, 62), //// [assembly: B.ForbiddenAttribute("foo")] //CreateLogEntryParameters(sourceFileName, 5, 14, 5, 32), //// [module: B.Forbidden("foo")] //CreateLogEntryParameters(sourceFileName, 6, 12, 6, 21), // [Forbidden on class .ExpectInvalidSegment(20, 6, 15) // class attribute type parameter .ExpectInvalidSegment(26, 41, 54) // field attribute type parameter .ExpectInvalidSegment(34, 45, 58) // enum value attribute type parameter .ExpectInvalidSegment(43, 45, 58) .Execute(); } [Fact] public void Cs6_Delegates() { SourceTestSpecification.Create() // delegate Class1 Delegate1(Class2 c); .ExpectInvalidSegment(5, 14, 28) .ExpectInvalidSegment(5, 21, 27) .ExpectInvalidSegment(5, 39, 45) .Execute(); } [Fact] public void Cs6_ElementAccess() { SourceTestSpecification.Create() // var a = new Class2[10]; .ExpectInvalidSegment(9, 13, 16) .ExpectInvalidSegment(9, 13, 16) .ExpectInvalidSegment(9, 25, 39) .ExpectInvalidSegment(9, 32, 38) // a[1] = a[2]; .ExpectInvalidSegment(10, 13, 14) .ExpectInvalidSegment(10, 13, 14) .ExpectInvalidSegment(10, 20, 21) .ExpectInvalidSegment(10, 20, 21) .Execute(); } [Fact] public void Cs6_StaticImport() { SourceTestSpecification.Create() .ExpectInvalidSegment(9, 13, 32) .ExpectInvalidSegment(10, 13, 34) // because of the return value .ExpectInvalidSegment(10, 13, 34) // because of the declaring type .Execute(); } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_AliasQualifiedName/Cs6_AliasQualifiedName.cs ================================================ enum MyEnum {} namespace A { class MyClass { private global::MyEnum e; } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_AliasQualifiedName/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ArrayType/Cs6_ArrayType.cs ================================================ namespace A { using B; class Class1 { private C.Class3[] e; private void Method1() { Class2.Method2(); Class2.Method3(); } } } namespace B { using C; class Class2 { public static Class3[] Method2() { return null; } public static Class3[][] Method3() { return null; } } } namespace C { class Class3 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ArrayType/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_Attributes/Cs6_Attributes.cs ================================================ using System; // not analyzed: [assembly: A.AllowedAttributeWithTypeArg(typeof(B.ForbiddenType))] // not analyzed: [module: A.AllowedAttributeWithTypeArg(typeof(B.ForbiddenType))] // not analyzed: [assembly: B.ForbiddenAttribute("foo")] // not analyzed: [module: B.Forbidden("foo")] namespace A { using B; [AttributeUsage(AttributeTargets.All)] class AllowedAttributeWithTypeArg : Attribute { public AllowedAttributeWithTypeArg(Type t) { } } [Forbidden("Attributes defined in the foreign namespace detected ok")] class Foo2 { } // Reference to forbidden type in arg to attribute on class. Detected OK. [AllowedAttributeWithTypeArg(typeof(ForbiddenType))] class Foo3 { } class Foo4 { // Reference to forbidden type in arg to attribute on class field. Detected OK. [AllowedAttributeWithTypeArg(typeof(ForbiddenType))] public int x; } enum Foo5 { Value1, // Reference to forbidden type in arg to attribute on enum value. Not Detected! [AllowedAttributeWithTypeArg(typeof(ForbiddenType))] Value2 } } namespace B { class ForbiddenAttribute : Attribute { public ForbiddenAttribute(string s) { } } class ForbiddenType { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_Attributes/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_Delegates/Cs6_Delegates.cs ================================================ namespace A { using B; delegate Class1 Delegate1(Class2 c); } namespace B { class Class1 { } class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_Delegates/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ElementAccess/Cs6_ElementAccess.cs ================================================ namespace A { using B; class Class1 { public void Method1() { var a = new Class2[10]; a[1] = a[2]; } } } namespace B { class Class2 { } class Class3 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ElementAccess/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_EveryUserDefinedTypeKind/Cs6_EveryUserDefinedTypeKind.cs ================================================ namespace A { using B; class Foo { private MyClass x1; private IMyInterface x2; private MyStruct x3; private MyEnum x4; private MyDelegate x5; } } namespace B { class MyClass { } interface IMyInterface { } struct MyStruct { } enum MyEnum { } delegate void MyDelegate(); } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_EveryUserDefinedTypeKind/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ExtensionMethodInvocation/Cs6_ExtensionMethodInvocation.cs ================================================ namespace A { using B; class MyClass { void MyMethod() { new MyClass().MyExtensionMethod(); new MyClass().MyGenericExtensionMethod(); } } } namespace B { using A; static class MyClassExtensions { public static void MyExtensionMethod(this MyClass myClass) { } public static void MyGenericExtensionMethod(this T t) { } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ExtensionMethodInvocation/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_GenericName/Cs6_GenericName.cs ================================================ namespace A { using B; class MyClass { // Generic type is not allowed private MyGenericClass e1; // Generic type and some type arguments are not allowed private MyGenericClass2 e2; // Generic type, nested generic type and type argument are not allowed private MyGenericClass> e3; } class MyClass2 { } } namespace B { class MyGenericClass { } class MyGenericClass2 { } class MyClass3 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_GenericName/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_GenericTypeArgument/Cs6_GenericTypeArgument.cs ================================================ namespace A { using B; class MyClass { private MyGenericClass e1; private A.MyGenericClass e2; } class MyGenericClass { } } namespace B { class MyClass2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_GenericTypeArgument/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_InvocationExpression/Cs6_InvocationExpression.cs ================================================ namespace A { using B; class Class1 { void Method1() { Method2(); new Class2().Method3(); Class2.Method4(); Class2.Method5(); } C.Class3 Method2() { return null; } } } namespace B { using C; class Class2 { public Class3 Method3() { return null; } public static Class3 Method4() { return null; } public static Class4 Method5() { return null; } } } namespace C { class Class3 { } class Class4 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_InvocationExpression/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_InvocationWithTypeArg/Cs6_InvocationWithTypeArg.cs ================================================ namespace A { using B; using C; class Class1 { void Method1() { Class2.Method2(); Class2.Method2>(); } } } namespace B { static class Class2 { public static void Method2() { } } } namespace C { class Class3 { } class Class4 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_InvocationWithTypeArg/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_MemberAccessExpression/Cs6_MemberAccessExpression.cs ================================================ namespace A { using B; class MyClass { void MyMethod() { object o = MyOtherClass.MyProperty; } } } namespace B { static class MyOtherClass { public static C.MyEnum MyProperty { get; set; } } } namespace C { enum MyEnum { Value1 } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_MemberAccessExpression/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_NestedType/Cs6_NestedType.cs ================================================ namespace A { using B; class MyClass { private MyClass2.MySubclass e1; private B.MyClass2.MySubclass e2; } } namespace B { class MyClass2 { public class MySubclass { } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_NestedType/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_NullableType/Cs6_NullableType.cs ================================================ namespace A { using B; class MyClass { private MyStruct? e; } } namespace B { struct MyStruct { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_NullableType/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ObjectCreationExpression/Cs6_ObjectCreationExpression.cs ================================================ namespace A { using B; class MyClass { void MyMethod() { new MyOtherClass(); } } } namespace B { class MyOtherClass { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_ObjectCreationExpression/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_PointerType/Cs6_PointerType.cs ================================================ namespace A { using B; unsafe class Class1 { private C.Class3* e; private void Method1() { Class2.Method2(); Class2.Method3(); } } } namespace B { using C; unsafe class Class2 { public static Class3* Method2() { return null; } public static Class3** Method3() { return null; } } } namespace C { struct Class3 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_PointerType/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_QualifiedName/Cs6_QualifiedName.cs ================================================ namespace A { class MyClass { private B.MyEnum e; } } namespace B { enum MyEnum { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_QualifiedName/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_StaticImport/Cs6_StaticImport.cs ================================================ namespace B { using static A.ClassA; public class ClassB { public void CallA() { IsCalledReturnsVoid(); // #60 call dependency itself is ignored IsCalledReturnsClassA(); // #60 produces warning based on return type (but not on the call itself) } } } namespace A { public class ClassA { public static void IsCalledReturnsVoid() { _ = new ClassA();} public static ClassA IsCalledReturnsClassA() { return new ClassA(); } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_StaticImport/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_Var/Cs6_Var.cs ================================================ namespace A { public class Class1 { public void Method() { var a = B.MyEnum.EnumValue1; } } } namespace B { public enum MyEnum { EnumValue1 } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_Var/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_VarWithConstructedGenericType/Cs6_VarWithConstructedGenericType.cs ================================================ namespace A { public class ClassA { public void Method() { // var and Instance should have 3 dependency violations each (generic type ClassB and type arguments EnumB) var b = B.ClassB.Instance; } } public enum EnumA { } } namespace B { public enum EnumB { } public class ClassB { public static ClassB Instance; } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_VarWithConstructedGenericType/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs6_VeryComplexType/Cs6_VeryComplexType.cs ================================================ namespace A { using B; using C; unsafe class Class1 { private void Method1() { Class4> a = Class2.Method2(); } } } namespace B { using C; unsafe class Class2 { public static Class4> Method2() { return null; } } } namespace C { struct Class3 { } class Class4 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs6_VeryComplexType/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7Tests.cs ================================================ using Xunit; namespace Codartis.NsDepCop.SourceTest { /// /// Tests that the analyzer handles various C# 7.0 constructs correctly. /// /// /// The name of the source file and its containing folder is the same as the name of the test. /// public class Cs7Tests { [Fact] public void Cs7_Out() { SourceTestSpecification.Create() // out MyEnum x -- this is a variable declaration so only the type name is checked and not the variable. .ExpectInvalidSegment(11, 45, 51) .Execute(); } [Fact] public void Cs7_Tuples() { SourceTestSpecification.Create() // public (Class1, Class2) Method1() .ExpectInvalidSegment(10, 25, 31) // return (new Class1(), new Class2()); .ExpectInvalidSegment(13, 39, 45) // public (Class1 class1, Class2 class2) Method2() .ExpectInvalidSegment(17, 32, 38) // return (class1: new Class1(), class2: new Class2()); .ExpectInvalidSegment(20, 55, 61) // var a = Method2() | var -> Class2 | Method2 -> Class2 .ExpectInvalidSegment(26, 13, 16) .ExpectInvalidSegment(26, 21, 28) // a.Item2 = null; | a -> Class2 | Item2 -> Class2 .ExpectInvalidSegment(28, 13, 14) .ExpectInvalidSegment(28, 15, 20) // a.class4 = null; | a -> Class2 | class4 -> Class2 .ExpectInvalidSegment(30, 13, 14) .ExpectInvalidSegment(30, 15, 21) .Execute(); } [Fact] public void Cs7_Deconstruction() { SourceTestSpecification.Create() // (var a, var b) = Method2(); .ExpectInvalidSegment(13, 21, 24) // (_, var c) = Method2(); .ExpectInvalidSegment(16, 17, 20) // var (d, e) = Method2(); .ExpectInvalidSegment(19, 13, 16) // Class2 g; .ExpectInvalidSegment(23, 13, 19) // (f, g) = Method2(); .ExpectInvalidSegment(24, 17, 18) .Execute(); } [Fact] public void Cs7_IsExpressionWithPattern() { SourceTestSpecification.Create() // Class2 o .ExpectInvalidSegment(7, 28, 34) // o is Class3 class3 (type pattern) .ExpectInvalidSegment(9, 17, 18) .ExpectInvalidSegment(9, 22, 28) .Execute(); } [Fact] public void Cs7_SwitchWithPattern() { SourceTestSpecification.Create() // case Class2 class2 when class2 != null: .ExpectInvalidSegment(11, 22, 28) .ExpectInvalidSegment(11, 41, 47) .Execute(); } [Fact] public void Cs7_LocalFunction() { SourceTestSpecification.Create() // Class2 LocalFunction(Class2 class2) .ExpectInvalidSegment(9, 13, 19) .ExpectInvalidSegment(9, 34, 40) .Execute(); } [Fact] public void Cs7_ThrowExpression() { SourceTestSpecification.Create() // throw new MyException(); .ExpectInvalidSegment(9, 43, 54) .Execute(); } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_1_DefaultLiteral/Cs7_1_DefaultLiteral.cs ================================================ using System; namespace A { using B; public class Class1 { // https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md public Class2 M1(Class2 p1 = default) { Class2 x = default; var a = new[] {default, x}; return default; } } } namespace B { public class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_1_DefaultLiteral/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_1_InferredTupleNames/Cs7_1_InferredTupleNames.cs ================================================ using System; namespace A { using B; public class Class1 { // https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md public void M1(Class2 p1) { var tuple = (p1.F1, p1.F2); var f1 = tuple.F1; var f2 = tuple.F2; } } } namespace B { public class Class2 { public int F1; public int F2; } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_1_InferredTupleNames/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_1_Tests.cs ================================================ using Xunit; namespace Codartis.NsDepCop.SourceTest { /// /// Tests that the analyzer handles various C# 7.1 constructs correctly. /// /// /// The name of the source file and its containing folder is the same as the name of the test. /// // ReSharper disable once InconsistentNaming public class Cs7_1_Tests { [Fact] public void Cs7_1_DefaultLiteral() { SourceTestSpecification.Create() // public Class2 M1(Class2 p1 = default) .ExpectInvalidSegment(11, 16, 22) .ExpectInvalidSegment(11, 26, 32) .ExpectInvalidSegment(11, 38, 45) // Class2 x = default; .ExpectInvalidSegment(13, 13, 19) .ExpectInvalidSegment(13, 24, 31) // var a = new[] { default, x }; .ExpectInvalidSegment(14, 13, 16) .ExpectInvalidSegment(14, 28, 35) .ExpectInvalidSegment(14, 37, 38) // return default; .ExpectInvalidSegment(15, 20, 27) .Execute(); } [Fact] public void Cs7_1_InferredTupleNames() { SourceTestSpecification.Create() // public void M1(Class2 p1) .ExpectInvalidSegment(11, 24, 30) // var tuple = (p1.F1, p1.F2); .ExpectInvalidSegment(13, 26, 28) .ExpectInvalidSegment(13, 33, 35) .Execute(); } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_2_NonTrailingNamedArguments/Cs7_2_NonTrailingNamedArguments.cs ================================================ using System; namespace A { using B; public class Class1 { // https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/non-trailing-named-arguments.md public void M1(Class2 p1, Class2 p2) { M1(p1: p1, p2); } } } namespace B { public class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_2_NonTrailingNamedArguments/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_2_Tests.cs ================================================ using Xunit; namespace Codartis.NsDepCop.SourceTest { /// /// Tests that the analyzer handles various C# 7.2 constructs correctly. /// /// /// The name of the source file and its containing folder is the same as the name of the test. /// // ReSharper disable once InconsistentNaming public class Cs7_2_Tests { [Fact] public void Cs7_2_NonTrailingNamedArguments() { SourceTestSpecification.Create() // public void M1(Class2 p1, Class2 p2) .ExpectInvalidSegment(11, 24, 30) .ExpectInvalidSegment(11, 35, 41) // M1(p1: p1, p2); .ExpectInvalidSegment(13, 20, 22) .ExpectInvalidSegment(13, 24, 26) .Execute(); } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_3_AttributeOnPropertyBackingField/Cs7_3_AttributeOnPropertyBackingField.cs ================================================ using System; namespace A { [Serializable] public class Class1 { [field: NonSerialized] public Class1 P1 { get; set; } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_3_AttributeOnPropertyBackingField/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_3_Tests.cs ================================================ using Xunit; namespace Codartis.NsDepCop.SourceTest { /// /// Tests that the analyzer handles various C# 7.3 constructs correctly. /// /// /// The name of the source file and its containing folder is the same as the name of the test. /// // ReSharper disable once InconsistentNaming public class Cs7_3_Tests { [Fact] public void Cs7_3_AttributeOnPropertyBackingField() { SourceTestSpecification.Create() // [Serializable] .ExpectInvalidSegment(5, 6, 18) // [field: NonSerialized] .ExpectInvalidSegment(8, 17, 30) .Execute(); } } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_Deconstruction/Cs7_Deconstruction.cs ================================================ using System; using A; using B; using C; namespace A { public class Class1 { public void Method() { // deconstructing declaration (var a, var b) = Method2(); // deconstructing declaration with discard (_, var c) = Method2(); // deconstructing declaration with var outside var (d, e) = Method2(); // deconstructing assignment Class1 f; Class2 g; (f, g) = Method2(); } public Deconstructable Method2() => throw new NotImplementedException(); } } namespace B { public class Deconstructable { public Class1 Class1 { get; set; } public Class2 Class2 { get; set; } public void Deconstruct(out Class1 class1, out Class2 class2) { class1 = Class1; class2 = Class2; } } } namespace C { public class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_Deconstruction/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_IsExpressionWithPattern/Cs7_IsExpressionWithPattern.cs ================================================ namespace A { using B; public class Class1 { public void Method(Class2 o) { if (o is Class3 class3) return; } } } namespace B { public class Class2 { } public class Class3 : Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_IsExpressionWithPattern/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_LocalFunction/Cs7_LocalFunction.cs ================================================ namespace A { using B; public class Class1 { public void Method() { Class2 LocalFunction(Class2 class2) => null; } } } namespace B { public class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_LocalFunction/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_Out/Cs7_Out.cs ================================================ using System; namespace A { using B; public class Class1 { public void Method() { Enum.TryParse("EnumValue1", out MyEnum x); } } } namespace B { public enum MyEnum { EnumValue1 } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_Out/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_SwitchWithPattern/Cs7_SwitchWithPattern.cs ================================================ namespace A { using B; public class Class1 { public void Method(object o) { switch (o) { case Class2 class2 when class2 != null: break; } } } } namespace B { public class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_SwitchWithPattern/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_ThrowExpression/Cs7_ThrowExpression.cs ================================================ using System; namespace A { using B; public class Class1 { public void Method() => throw new MyException(); } } namespace B { public class MyException : Exception { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_ThrowExpression/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/Cs7_Tuples/Cs7_Tuples.cs ================================================ using System; namespace A { using B; public class Class1 { // Tuple return type public (Class1, Class2) Method1() { // Tuple literal return (new Class1(), new Class2()); } // Named tuple elements public (Class1 class1, Class2 class2) Method2() { // Named tuple elements in tuple literal return (class1: new Class1(), class2: new Class2()); } public void Method3() { // Get tuple value var a = Method2(); // Access tuple element by defalt name a.Item2 = null; // Access tuple element by name a.class2 = null; } } } namespace B { public class Class2 { } } ================================================ FILE: source/NsDepCop.SourceTest/Cs7_Tuples/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.SourceTest/FileBasedTestsBase.cs ================================================ using System; using System.IO; using System.Reflection; using System.Threading; namespace Codartis.NsDepCop.SourceTest { /// /// Abstract base class for test classes that manipulate test files. /// public abstract class FileBasedTestsBase { protected static string GetExecutingAssemblyDirectory() => GetAssemblyDirectory(Assembly.GetExecutingAssembly()); protected static string GetAssemblyPath(Assembly assembly) { return assembly.Location; } protected static string GetAssemblyDirectory(Assembly assembly) { var assemblyPath = GetAssemblyPath(assembly); return Path.GetDirectoryName(assemblyPath); } protected static string GetBinFilePath(string filename) { return Path.Combine(GetExecutingAssemblyDirectory(), filename); } protected string LoadFile(string fullPath) { using (var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var streamReader = new StreamReader(stream)) { return streamReader.ReadToEnd(); } } protected static void Rename(string fromFilename, string toFilename) { if (File.Exists(fromFilename)) { if (File.Exists(toFilename)) throw new InvalidOperationException($"Cannot rename '{fromFilename}' to '{toFilename}' because it already exists."); File.Move(fromFilename, toFilename); } } protected static void Delete(string filename) { if (File.Exists(filename)) File.Delete(filename); while (File.Exists(filename)) Thread.Sleep(100); } } } ================================================ FILE: source/NsDepCop.SourceTest/NsDepCop.SourceTest.csproj ================================================  net8.0 Codartis.NsDepCop.SourceTest false false true latest all runtime; build; native; contentfiles; analyzers; buildtransitive PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest ================================================ FILE: source/NsDepCop.SourceTest/Properties/AssemblyInfo.cs ================================================ using System.Reflection; [assembly: AssemblyTitle("NsDepCop.Analyzer.SourceTest")] [assembly: AssemblyDescription("Tests the dependency analyzer with source files.")] ================================================ FILE: source/NsDepCop.SourceTest/SourceLineSegment.cs ================================================ using Codartis.NsDepCop.Analysis; namespace Codartis.NsDepCop.SourceTest { public readonly struct SourceLineSegment { public int Line { get; } public int StartColumn { get; } public int EndColumn { get; } public SourceLineSegment(int line, int startColumn, int endColumn) { Line = line; StartColumn = startColumn; EndColumn = endColumn; } public bool Equals(SourceSegment sourceSegment) { return Line == sourceSegment.StartLine && Line == sourceSegment.EndLine && StartColumn == sourceSegment.StartColumn && EndColumn == sourceSegment.EndColumn; } public override string ToString() => $"({Line},{StartColumn}-{EndColumn})"; } } ================================================ FILE: source/NsDepCop.SourceTest/SourceTestSpecification.cs ================================================ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Analysis.Factory; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config.Factory; using Codartis.NsDepCop.ParserAdapter.Roslyn; using FluentAssertions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace Codartis.NsDepCop.SourceTest { internal sealed class SourceTestSpecification : FileBasedTestsBase { private static readonly CSharpParseOptions CSharpParseOptions = new(LanguageVersion.Latest); private readonly string _name; private readonly ITypeDependencyEnumerator _typeDependencyEnumerator; private readonly List _invalidLineSegments = new(); private readonly List _allowedMemberNames = new(); private SourceTestSpecification(string name, ITypeDependencyEnumerator typeDependencyEnumerator) { _name = name; _typeDependencyEnumerator = typeDependencyEnumerator; } public static SourceTestSpecification Create([CallerMemberName] string name = null) => new(name, new TypeDependencyEnumerator(new SyntaxNodeAnalyzer(), DebugMessageHandler)); public SourceTestSpecification ExpectInvalidSegment(int line, int startColumn, int endColumn) { _invalidLineSegments.Add(new SourceLineSegment(line, startColumn, endColumn)); return this; } public SourceTestSpecification ExpectInvalidSegment(int line, int startColumn, int endColumn, string[] expectedAllowedMemberNames) { _invalidLineSegments.Add(new SourceLineSegment(line, startColumn, endColumn)); _allowedMemberNames.Add(expectedAllowedMemberNames); return this; } public void Execute(OutputKind? outputKind = null) { var sourceFilePaths = new[] {_name}.Select(GetTestFileFullPath).ToList(); var referencedAssemblyPaths = GetReferencedAssemblyPaths().ToList(); ValidateCompilation(sourceFilePaths, referencedAssemblyPaths, outputKind ?? OutputKind.DynamicallyLinkedLibrary); AssertIllegalDependencies(sourceFilePaths, referencedAssemblyPaths); } private static void DebugMessageHandler(string message) => Debug.WriteLine(message); private void ValidateCompilation(IEnumerable sourceFiles, IEnumerable referencedAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { var compilation = CSharpCompilation.Create( "NsDepCopProject", sourceFiles.Select(i => CSharpSyntaxTree.ParseText(LoadFile(i), CSharpParseOptions)), referencedAssemblies.Select(i => MetadataReference.CreateFromFile(i)), new CSharpCompilationOptions(outputKind, allowUnsafe: true)); var errors = compilation.GetDiagnostics().Where(i => i.Severity == DiagnosticSeverity.Error).ToList(); errors.Should().HaveCount(0); } private void AssertIllegalDependencies(IEnumerable sourceFiles, IEnumerable referencedAssemblies) { var baseFolder = GetBinFilePath(_name); var illegalDependencies = GetIllegalDependencies(baseFolder, sourceFiles, referencedAssemblies).ToList(); illegalDependencies.Select(i => i.IllegalDependency.SourceSegment) .Should().Equal(_invalidLineSegments, (typeDependency, sourceLineSegment) => sourceLineSegment.Equals(typeDependency)); List membersFromGenerator = illegalDependencies .Select(i => i.AllowedMemberNames) .Where(amn => amn.Any()) .ToList(); membersFromGenerator.Count.Should().Be(_allowedMemberNames.Count); for (int i = 0; i < membersFromGenerator.Count; i++) { membersFromGenerator[i].Should().BeEquivalentTo(_allowedMemberNames[i]); } } private IEnumerable GetIllegalDependencies(string baseFolder, IEnumerable sourceFiles, IEnumerable referencedAssemblies) { var dependencyAnalyzerFactory = new DependencyAnalyzerFactory(DebugMessageHandler); var configProvider = new ConfigProviderFactory(DebugMessageHandler).CreateFromMultiLevelXmlConfigFile(baseFolder); var dependencyAnalyzer = dependencyAnalyzerFactory.Create(configProvider, _typeDependencyEnumerator); return dependencyAnalyzer.AnalyzeProject(sourceFiles, referencedAssemblies).OfType(); } private static string GetTestFileFullPath(string testName) { var relativeTestFilePath = Path.Combine(testName, testName + ".cs"); return Path.Combine(GetBinFilePath(relativeTestFilePath)); } private static IEnumerable GetReferencedAssemblyPaths() { return new[] { // mscorlib GetAssemblyPath(typeof(object).Assembly), }; } } } ================================================ FILE: source/NsDepCop.Test/FileBasedTestsBase.cs ================================================ using System; using System.IO; using System.Reflection; using System.Threading; namespace Codartis.NsDepCop.Test { /// /// Abstract base class for test classes that manipulate test files. /// public abstract class FileBasedTestsBase { protected static string GetExecutingAssemblyDirectory() => GetAssemblyDirectory(Assembly.GetExecutingAssembly()); protected static string GetAssemblyPath(Assembly assembly) { return assembly.Location; } protected static string GetAssemblyDirectory(Assembly assembly) { var assemblyPath = GetAssemblyPath(assembly); return Path.GetDirectoryName(assemblyPath); } protected static string GetBinFilePath(string filename) { return Path.Combine(GetExecutingAssemblyDirectory(), filename); } protected string GetFilePathInTestClassFolder(string filename) { var namespacePrefix = $"Codartis.{this.GetType().Assembly.GetName().Name}"; var namespacePostfix = GetType().FullName.Remove(0, namespacePrefix.Length + 1).Replace('.', '/'); return GetBinFilePath(Path.Combine(namespacePostfix, filename)); } protected string LoadFile(string fullPath) { using (var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var streamReader = new StreamReader(stream)) { return streamReader.ReadToEnd(); } } protected static void Rename(string fromFilename, string toFilename) { if (File.Exists(fromFilename)) { if (File.Exists(toFilename)) throw new InvalidOperationException($"Cannot rename '{fromFilename}' to '{toFilename}' because it already exists."); File.Move(fromFilename, toFilename); } } protected static void Delete(string filename) { if (File.Exists(filename)) File.Delete(filename); while (File.Exists(filename)) Thread.Sleep(100); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Analysis/AssemblyDependencyAnalyzerTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using FluentAssertions; using Microsoft.CodeAnalysis; using Moq; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Analysis { public class AssemblyDependencyAnalyzerTests { private static AssemblyIdentity SourceAssembly = AssemblyIdentity.FromAssemblyDefinition(typeof(AssemblyDependencyAnalyzerTests).Assembly); private static AssemblyIdentity ReferencedAssemblyOne = AssemblyIdentity.FromAssemblyDefinition(typeof(string).Assembly); private static AssemblyIdentity ReferencedAssemblyTwo = AssemblyIdentity.FromAssemblyDefinition(typeof(Assert).Assembly); private readonly Mock _configProviderMock = new(); private readonly Mock _configMock = new(); [Fact] public void NoConfig_ReturnsNoConfigMessage() { _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.NoConfig); AnalyzeProject().OfType().Should().HaveCount(1); } [Fact] public void ConfigDisabled_ReturnsConfigDisabledMessage() { _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.Disabled); AnalyzeProject().OfType().Should().HaveCount(1); } [Fact] public void ConfigError_ReturnsConfigErrorMessage() { var theException = new Exception(); _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.ConfigError); _configProviderMock.Setup(i => i.ConfigException).Returns(theException); var configErrorMessages = AnalyzeProject().OfType().ToList(); configErrorMessages.Should().HaveCount(1); configErrorMessages.First().Exception.Should().Be(theException); } [Fact] public void AnalyzeProject_TwoIllegalAssemblyDependencyMessagesReturned() { // Arrange SetUpEnabledConfig(); // Act var actual = AnalyzeProject().OfType(); // Assert actual.Should().HaveCount(2); } [Fact] public void AnalyzeProject_NoIllegalAssemblyDependencyMessageReturned() { // Arrange SetUpEnabledConfig(checkAssemblyDependencies: false); // Act var actual = AnalyzeProject().OfType(); // Assert actual.Should().HaveCount(1); } [Fact] public void AnalyzeProject_NoIllegalAssemblyDependencyMessageReturnedWhenAllowedAssemblyRulesDefined() { // Arrange SetUpEnabledConfig(); _configMock.Setup(i => i.AllowedAssemblyRules).Returns([ new DependencyRule( from: DomainSpecificationParser.Parse(SourceAssembly.Name), to: DomainSpecificationParser.Parse(ReferencedAssemblyOne.Name) ), new DependencyRule( from: DomainSpecificationParser.Parse(SourceAssembly.Name), to: DomainSpecificationParser.Parse(ReferencedAssemblyTwo.Name) ) ]); // Act var actual = AnalyzeProject().OfType(); // Assert actual.Should().BeEmpty(); } [Fact] public void AnalyzeProject_OneIllegalAssemblyDependencyMessageReturnedWhenDisallowedAssemblyRulesDefined() { // Arrange SetUpEnabledConfig(); _configMock.Setup(i => i.AllowedAssemblyRules).Returns([ new DependencyRule( from: DomainSpecificationParser.Parse("*"), to: DomainSpecificationParser.Parse("*") ) ]); _configMock.Setup(i => i.DisallowedAssemblyRules).Returns([ new DependencyRule( from: DomainSpecificationParser.Parse(SourceAssembly.Name), to: DomainSpecificationParser.Parse(ReferencedAssemblyTwo.Name) ) ]); // Act var actual = AnalyzeProject().OfType(); // Assert actual.Should().HaveCount(1); } private void SetUpEnabledConfig(bool checkAssemblyDependencies = true) { _configMock.Setup(i => i.AllowedAssemblyRules).Returns(new HashSet()); _configMock.Setup(i => i.DisallowedAssemblyRules).Returns(new HashSet()); _configMock.Setup(i => i.CheckAssemblyDependencies).Returns(checkAssemblyDependencies); _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.Enabled); _configProviderMock.Setup(i => i.ConfigException).Returns(null); _configProviderMock.Setup(i => i.Config).Returns(_configMock.Object); } private IEnumerable AnalyzeProject() { return CreateDependencyAnalyzer().AnalyzeProject( SourceAssembly, [ReferencedAssemblyOne, ReferencedAssemblyTwo] ); } private IAssemblyDependencyAnalyzer CreateDependencyAnalyzer() { return new AssemblyDependencyAnalyzer(_configProviderMock.Object); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Analysis/CachingTypeDependencyValidatorTests.cs ================================================ using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Analysis { public class CachingTypeDependencyValidatorTests { [Fact] public void NoRule_SameNamespaceIsAlwaysAllowed() { var ruleConfig = new DependencyRulesBuilder(); var cachingTypeDependencyValidator = CreateCachingTypeDependencyValidator(ruleConfig); cachingTypeDependencyValidator.IsAllowedDependency("N", "C1", "N", "C2").Should().BeTrue(); } [Fact] public void ValidatingATypeDependencyTwice_FirstCacheMissThenCacheHit() { var ruleConfig = new DependencyRulesBuilder(); var cachingTypeDependencyValidator = CreateCachingTypeDependencyValidator(ruleConfig); cachingTypeDependencyValidator.IsAllowedDependency("N1", "C1", "N2", "C2").Should().BeFalse(); cachingTypeDependencyValidator.MissCount.Should().Be(1); cachingTypeDependencyValidator.HitCount.Should().Be(0); cachingTypeDependencyValidator.IsAllowedDependency("N1", "C1", "N2", "C2").Should().BeFalse(); cachingTypeDependencyValidator.MissCount.Should().Be(1); cachingTypeDependencyValidator.HitCount.Should().Be(1); } private static CachingTypeDependencyValidator CreateCachingTypeDependencyValidator(IDependencyRules ruleConfig) => new CachingTypeDependencyValidator(ruleConfig, traceMessageHandler: null); } } ================================================ FILE: source/NsDepCop.Test/Implementation/Analysis/DependencyAnalyzerTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Analysis.Messages; using Codartis.NsDepCop.Config; using DotNet.Globbing; using FluentAssertions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Moq; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Analysis { public class DependencyAnalyzerTests { private static readonly SourceSegment DummySourceSegment = new(1, 1, 1, 1, null, null); private readonly Mock _configProviderMock = new(); private readonly Mock _configMock = new(); private readonly Mock _typeDependencyEnumeratorMock = new(); [Fact] public void NoConfig_ReturnsNoConfigMessage() { _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.NoConfig); AnalyzeProject().OfType().Should().HaveCount(1); AnalyzeSyntaxNode().OfType().Should().HaveCount(1); } [Fact] public void ConfigDisabled_ReturnsConfigDisabledMessage() { _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.Disabled); AnalyzeProject().OfType().Should().HaveCount(1); AnalyzeSyntaxNode().OfType().Should().HaveCount(1); } [Fact] public void ConfigError_ReturnsConfigErrorMessage() { var theException = new Exception(); _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.ConfigError); _configProviderMock.Setup(i => i.ConfigException).Returns(theException); { var configErrorMessages = AnalyzeProject().OfType().ToList(); configErrorMessages.Should().HaveCount(1); configErrorMessages.First().Exception.Should().Be(theException); } { var configErrorMessages = AnalyzeSyntaxNode().OfType().ToList(); configErrorMessages.Should().HaveCount(1); configErrorMessages.First().Exception.Should().Be(theException); } } [Fact] public void AnalyzeProject_TypeDependenciesReturned() { SetUpEnabledConfig(); _typeDependencyEnumeratorMock .Setup(i => i.GetTypeDependencies(It.IsAny>(), It.IsAny>(), It.IsAny>())) .Returns(Enumerable.Repeat(new TypeDependency("N1", "T1", "N2", "T2", DummySourceSegment), 2)); AnalyzeProject().OfType().Should().HaveCount(2); } [Fact] public void AnalyzeSyntaxNode_TypeDependenciesReturned() { SetUpEnabledConfig(); _typeDependencyEnumeratorMock .Setup(i => i.GetTypeDependencies(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(Enumerable.Repeat(new TypeDependency("N1", "T1", "N2", "T2", DummySourceSegment), 2)); AnalyzeSyntaxNode().OfType().Should().HaveCount(2); } [Fact(Skip = "MaxIssueCount temporarily not working for AnalyzeProject.")] public void RefreshConfig_Works() { SetUpEnabledConfig(maxIssueCount: 2); _typeDependencyEnumeratorMock .Setup(i => i.GetTypeDependencies(It.IsAny>(), It.IsAny>(), It.IsAny>())) .Returns(Enumerable.Repeat(new TypeDependency("N1", "T1", "N2", "T2", DummySourceSegment), 10)); var dependencyAnalyzer = CreateDependencyAnalyzer(); dependencyAnalyzer.AnalyzeProject(new List(), new List()).OfType().Should().HaveCount(2); SetUpEnabledConfig(maxIssueCount: 4); dependencyAnalyzer.RefreshConfig(); dependencyAnalyzer.AnalyzeProject(new List(), new List()).OfType().Should().HaveCount(4); } [Theory(Skip = "This feature is temporarily commented out.")] [InlineData(3, 2, true)] [InlineData(3, 3, false)] [InlineData(3, 4, false)] public void AutoLowerMaxIssueCount_Works(int maxIssueCount, int actualIssueCount, bool isUpdateCalled) { SetUpEnabledConfig(maxIssueCount: maxIssueCount, autoLowerMaxIssueCount: true); _typeDependencyEnumeratorMock .Setup(i => i.GetTypeDependencies(It.IsAny>(), It.IsAny>(), It.IsAny>())) .Returns(Enumerable.Repeat(new TypeDependency("N1", "T1", "N2", "T2", DummySourceSegment), actualIssueCount)); var expectedIssueCount = Math.Min(actualIssueCount, maxIssueCount); AnalyzeProject().OfType().Should().HaveCount(expectedIssueCount); if (isUpdateCalled) _configProviderMock.Verify(i => i.UpdateMaxIssueCount(expectedIssueCount)); else _configProviderMock.Verify(i => i.UpdateMaxIssueCount(It.IsAny()), Times.Never); } private void SetUpEnabledConfig(int maxIssueCount = 100, bool autoLowerMaxIssueCount = false) { _configMock.Setup(i => i.AllowRules).Returns(new Dictionary()); _configMock.Setup(i => i.DisallowRules).Returns(new HashSet()); _configMock.Setup(i => i.VisibleTypesByNamespace).Returns(new Dictionary()); _configMock.Setup(i => i.MaxIssueCount).Returns(maxIssueCount); _configMock.Setup(i => i.AutoLowerMaxIssueCount).Returns(autoLowerMaxIssueCount); _configProviderMock.Setup(i => i.ConfigState).Returns(AnalyzerConfigState.Enabled); _configProviderMock.Setup(i => i.ConfigException).Returns(null); _configProviderMock.Setup(i => i.Config).Returns(_configMock.Object); } private IEnumerable AnalyzeProject() { return CreateDependencyAnalyzer().AnalyzeProject(new List(), new List()); } private IEnumerable AnalyzeSyntaxNode() { var syntaxTree = SyntaxFactory.CompilationUnit().SyntaxTree; var dummyCompilation = CSharpCompilation.Create(assemblyName: "MyAssembly", syntaxTrees: new[] {syntaxTree}); var semanticModel = dummyCompilation.GetSemanticModel(syntaxTree); return CreateDependencyAnalyzer().AnalyzeSyntaxNode(SyntaxFactory.IdentifierName("dummy"), semanticModel); } private IDependencyAnalyzer CreateDependencyAnalyzer() { return new DependencyAnalyzer(_configProviderMock.Object, _typeDependencyEnumeratorMock.Object, traceMessageHandler: null); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Analysis/DependencyRulesBuilder.cs ================================================ using System.Collections.Generic; using Codartis.NsDepCop.Config; namespace Codartis.NsDepCop.Test.Implementation.Analysis { /// /// Helper class for unit testing. Enables rule config building. /// public class DependencyRulesBuilder : IDependencyRules { private readonly Dictionary _allowedDependencies; private readonly HashSet _disallowedDependencies; private readonly Dictionary _visibleTypesByTargetNamespace; private readonly HashSet _allowedAssemblyDependencies; private readonly HashSet _disallowedAssemblyDependencies; public DependencyRulesBuilder() { ChildCanDependOnParentImplicitly = ConfigDefaults.ChildCanDependOnParentImplicitly; ParentCanDependOnChildImplicitly = ConfigDefaults.ParentCanDependOnChildImplicitly; _allowedDependencies = new Dictionary(); _disallowedDependencies = new HashSet(); _visibleTypesByTargetNamespace = new Dictionary(); _allowedAssemblyDependencies = new HashSet(); _disallowedAssemblyDependencies = new HashSet(); } public bool ChildCanDependOnParentImplicitly { get; private set; } public bool ParentCanDependOnChildImplicitly { get; } public Dictionary AllowRules => _allowedDependencies; public HashSet DisallowRules => _disallowedDependencies; public Dictionary VisibleTypesByNamespace => _visibleTypesByTargetNamespace; public HashSet AllowedAssemblyRules => _allowedAssemblyDependencies; public HashSet DisallowedAssemblyRules => _disallowedAssemblyDependencies; public DependencyRulesBuilder SetChildCanDependOnParentImplicitly(bool value) { ChildCanDependOnParentImplicitly = value; return this; } public DependencyRulesBuilder AddAllowed(DomainSpecification from, DomainSpecification to, params string[] typeNames) { _allowedDependencies.Add(new DependencyRule(from, to), new TypeNameSet(typeNames)); return this; } public DependencyRulesBuilder AddAllowed(string from, string to, params string[] typeNames) { _allowedDependencies.Add(new DependencyRule(from, to), new TypeNameSet(typeNames)); return this; } public DependencyRulesBuilder AddDisallowed(string from, string to) { _disallowedDependencies.Add(new DependencyRule(from, to)); return this; } public DependencyRulesBuilder AddAllowedAssemblyDependency(string from, string to) { _allowedAssemblyDependencies.Add(new DependencyRule(from, to)); return this; } public DependencyRulesBuilder AddDisallowedAssemblyDependency(string from, string to) { _disallowedAssemblyDependencies.Add(new DependencyRule(from, to)); return this; } public DependencyRulesBuilder AddVisibleMembers(string targetNamespace, params string[] typeNames) { _visibleTypesByTargetNamespace.Add(new Domain(targetNamespace), new TypeNameSet(typeNames)); return this; } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Analysis/TypeDependencyValidatorExtensions.cs ================================================ using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Analysis.Implementation; namespace Codartis.NsDepCop.Test.Implementation.Analysis { internal static class TypeDependencyValidatorExtensions { private static readonly SourceSegment DummySourceSegment = new SourceSegment(0, 0, 0, 0, null, null); public static bool IsAllowedDependency(this TypeDependencyValidator typeDependencyValidator, string fromNamespace, string fromType, string toNamespace, string toType) { var typeDependency = new TypeDependency(fromNamespace, fromType, toNamespace, toType, DummySourceSegment); return typeDependencyValidator.IsAllowedDependency(typeDependency).IsAllowed; } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Analysis/TypeDependencyValidatorTests.cs ================================================ using Codartis.NsDepCop.Analysis.Implementation; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Analysis { public class TypeDependencyValidatorTests { [Fact] public void NoRule_SameNamespaceIsAlwaysAllowed() { var ruleConfig = new DependencyRulesBuilder(); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("N", "C1", "N", "C2").Should().BeTrue(); } [Fact] public void NoRule_EverythingIsDisallowed_ExceptSameNamespace() { var ruleConfig = new DependencyRulesBuilder(); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("N1", "C1", "N2", "C2").Should().BeFalse(); } [Fact] public void AllowRule() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("S", "T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S", "C1", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C1", "T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S", "C1", "T1", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WithSubnamespace() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("S.*", "T.*"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S", "C1", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S.S1", "C1", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S", "C1", "T.T1", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C1", "T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S", "C1", "T1", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WithPrefixWildcard() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("*.S", "*.T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "X.Y.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S", "C1", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S.S1", "C1", "T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S", "C1", "T.T1", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S1", "C1", "T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S", "C1", "T1", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WithSingleNamespacePrefix() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("?.S", "?.T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.S", "C1", "Y.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "Y.T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("B.S", "C1", "X.Y.T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S", "C1", "T", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WithWildcard() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("A.*.S", "U.*.T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.V.W.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.S", "C1", "U.V.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.X.S", "C1", "U.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.C.S", "C1", "U.V.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.S.C", "C1", "U.V.T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.V.T.W", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("A1.A.B.S", "C1", "U.V.T", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WithMultipleWildcards() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("A.*.?.S", "U.*.T.?"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.T.T1", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.C.S", "C1", "U.V.T.T1", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.T.T1", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.S", "C1", "U.T.T1", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.T", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WithAnyNamespace() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("*", "*"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S", "C1", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C1", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S", "C1", "T1", "C2").Should().BeTrue(); } [Fact] public void AllowRule_MoreSpecificRuleWithWildcardsIsStronger() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("A.*.S", "U.V.T", "C3") .AddAllowed("A.?.?.S", "U.V.T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.B.C.S", "C1", "U.V.T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.C.D.S", "C1", "U.V.T", "C2").Should().BeFalse(); } [Fact] public void AllowRule_WildcardRuleIsStrongerThanRegexRule() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("A.*.S", "U.V.T", "C3") .AddAllowed("/A...S/", "U.V.T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.V.T", "C3").Should().BeTrue(); dependencyValidator.IsAllowedDependency("A.B.S", "C1", "U.V.T", "C2").Should().BeFalse(); } [Fact] public void DisallowRule_LessSpecificRuleWithWildcardsIsStronger() { var ruleConfig = new DependencyRulesBuilder() .AddDisallowed("A.*.S", "U.V.T") .AddAllowed("A.?.?.S", "U.V.T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("A.B.C.S", "C1", "U.V.T", "C2").Should().BeFalse(); dependencyValidator.IsAllowedDependency("A.B.C.D.S", "C1", "U.V.T", "C2").Should().BeFalse(); } [Fact] public void AllowRuleWithVisibleMembers_AffectsOnlyAllowRuleSource() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("S1", "T", "C1", "C2") .AddAllowed("S2", "T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S1", "C", "T", "C1").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C", "T", "C3").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S2", "C", "T", "C1").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S2", "C", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S2", "C", "T", "C3").Should().BeTrue(); } [Fact] public void AllowRule_GlobalVisibleMembers_AffectsAllRuleSources() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("S1", "T") .AddAllowed("S2", "T") .AddVisibleMembers("T", "C1", "C2"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S1", "C", "T", "C1").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S1", "C", "T", "C3").Should().BeFalse(); dependencyValidator.IsAllowedDependency("S2", "C", "T", "C1").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S2", "C", "T", "C2").Should().BeTrue(); dependencyValidator.IsAllowedDependency("S2", "C", "T", "C3").Should().BeFalse(); } [Fact] public void DisallowRule() { var ruleConfig = new DependencyRulesBuilder() .AddDisallowed("S", "T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S", "C", "T", "C1").Should().BeFalse(); } [Fact] public void DisallowRule_StrongerThanAllowRule() { var ruleConfig = new DependencyRulesBuilder() .AddAllowed("S", "T") .AddDisallowed("S", "T"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("S", "C", "T", "C1").Should().BeFalse(); } [Fact] public void DisallowRule_ButSameNamespaceShouldBeAllowed() { var ruleConfig = new DependencyRulesBuilder() .AddDisallowed("a.*", "a.b.*"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("a.b.c", "C1", "a.b.c", "C2").Should().BeTrue(); } [Fact] public void DisallowRule_IsStrongerThanChildCanDependOnParentImplicitly() { var ruleConfig = new DependencyRulesBuilder() .AddDisallowed("a.*", "a.b.*") .SetChildCanDependOnParentImplicitly(true); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("a.b.c", "C1", "a.b", "C2").Should().BeFalse(); } [Fact] public void ChildCanDependOnParentImplicitly() { var ruleConfig = new DependencyRulesBuilder() .SetChildCanDependOnParentImplicitly(true); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("N1.N2", "C", "N1", "C1").Should().BeTrue(); dependencyValidator.IsAllowedDependency("N1", "C", "N1.N2", "C1").Should().BeFalse(); } [Fact] public void ChildCanDependOnParentImplicitly_ButDisallowWins() { var ruleConfig = new DependencyRulesBuilder() .SetChildCanDependOnParentImplicitly(true) .AddDisallowed("N1.N2", "N1"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("N1.N2", "C", "N1", "C1").Should().BeFalse(); dependencyValidator.IsAllowedDependency("N1", "C", "N1.N2", "C1").Should().BeFalse(); } [Fact] public void ChildCanDependOnParentImplicitly_ButDisallowWins_WithWildcard() { var ruleConfig = new DependencyRulesBuilder() .SetChildCanDependOnParentImplicitly(true) .AddDisallowed("N1.*", "N1"); var dependencyValidator = CreateTypeDependencyValidator(ruleConfig); dependencyValidator.IsAllowedDependency("N1.N2", "C", "N1", "C1").Should().BeFalse(); dependencyValidator.IsAllowedDependency("N1", "C", "N1.N2", "C1").Should().BeFalse(); } private static TypeDependencyValidator CreateTypeDependencyValidator(IDependencyRules ruleConfig) => new TypeDependencyValidator(ruleConfig); } } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/AnalyzerConfigBuilderTests.cs ================================================ using System.Collections.Generic; using System.IO; using System.Linq; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Config.Implementation; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Config { public class AnalyzerConfigBuilderTests { [Fact] public void ToAnalyzerConfig_AppliesDefaults() { var config = new AnalyzerConfigBuilder().ToAnalyzerConfig(); config.IsEnabled.Should().Be(ConfigDefaults.IsEnabled); config.ChildCanDependOnParentImplicitly.Should().Be(ConfigDefaults.ChildCanDependOnParentImplicitly); config.MaxIssueCount.Should().Be(ConfigDefaults.MaxIssueCount); } [Theory] [InlineData("/root", "*.cs", "/root/*.cs")] [InlineData(@"C:\folder with space", "*.cs", "C:/folder with space/*.cs")] [InlineData(@"C:\folder with space", @"**\*.cs", "C:/folder with space/**/*.cs")] [InlineData(@"C:\folder with space", @"D:\*.cs", "D:/*.cs")] [InlineData(@"C:\folder with space", "//a folder/b.cs", "//a folder/b.cs")] [InlineData(@"C:\folder with space", "/*.cs", "/*.cs")] public void ToAnalyzerConfig_ConvertsPathsToRooted(string pathRoot, string pathExclusionPattern, string expectedFullPath) { var configBuilder = new AnalyzerConfigBuilder() .AddSourcePathExclusionPatterns([pathExclusionPattern]) .MakePathsRooted(pathRoot); var config = configBuilder.ToAnalyzerConfig(); // Note that Path.GetFullPath normalizes the path according to the current OS so we can assert equivalence with the expected path. config.SourcePathExclusionPatterns.Select(Path.GetFullPath) .Should().BeEquivalentTo(Path.GetFullPath(expectedFullPath)); } [Fact] public void SetMethods_WithNonNullValues_OverwriteProperties() { var configBuilder = new AnalyzerConfigBuilder() .SetInheritanceDepth(9) .SetIsEnabled(true) .SetChildCanDependOnParentImplicitly(true) .SetCheckAssemblyDependencies(true) .SetMaxIssueCount(42); configBuilder.InheritanceDepth.Should().Be(9); configBuilder.IsEnabled.Should().Be(true); configBuilder.ChildCanDependOnParentImplicitly.Should().Be(true); configBuilder.CheckAssemblyDependencies.Should().Be(true); configBuilder.MaxIssueCount.Should().Be(42); } [Fact] public void SetMethods_WithNullValues_DoNotOverwriteProperties() { var configBuilder = new AnalyzerConfigBuilder() .SetInheritanceDepth(9) .SetIsEnabled(true) .SetChildCanDependOnParentImplicitly(true) .SetCheckAssemblyDependencies(true) .SetMaxIssueCount(42); configBuilder .SetInheritanceDepth(null) .SetIsEnabled(null) .SetChildCanDependOnParentImplicitly(null) .SetCheckAssemblyDependencies(null) .SetMaxIssueCount(null); configBuilder.InheritanceDepth.Should().Be(9); configBuilder.IsEnabled.Should().Be(true); configBuilder.ChildCanDependOnParentImplicitly.Should().Be(true); configBuilder.CheckAssemblyDependencies.Should().Be(true); configBuilder.MaxIssueCount.Should().Be(42); } [Fact] public void AddAllowRule_Works() { var configBuilder = new AnalyzerConfigBuilder() .AddAllowRule(new DependencyRule("N1", "N2")) .AddAllowRule(new DependencyRule("N3", "N4"), new TypeNameSet { "T1", "T2" }); configBuilder .AddAllowRule(new DependencyRule("N3", "N4"), new TypeNameSet { "T2", "T3" }) .AddAllowRule(new DependencyRule("N5", "N6"), new TypeNameSet { "T4" }); configBuilder.AllowRules.Should().BeEquivalentTo( new Dictionary { {new DependencyRule("N1", "N2"), null}, {new DependencyRule("N3", "N4"), new TypeNameSet {"T1", "T2", "T3"}}, {new DependencyRule("N5", "N6"), new TypeNameSet {"T4"}}, }); } [Fact] public void AddDisallowRule_Works() { var configBuilder = new AnalyzerConfigBuilder() .AddDisallowRule(new DependencyRule("N1", "N2")) .AddDisallowRule(new DependencyRule("N3", "N4")); configBuilder .AddDisallowRule(new DependencyRule("N3", "N4")) .AddDisallowRule(new DependencyRule("N5", "N6")); configBuilder.DisallowRules.Should().BeEquivalentTo( new HashSet { new DependencyRule("N1", "N2"), new DependencyRule("N3", "N4"), new DependencyRule("N5", "N6"), }); } [Fact] public void AddVisibleTypesByNamespace_Works() { var configBuilder = new AnalyzerConfigBuilder() .AddVisibleTypesByNamespace(new Domain("N1"), null) .AddVisibleTypesByNamespace(new Domain("N2"), new TypeNameSet { "T1", "T2" }); configBuilder .AddVisibleTypesByNamespace(new Domain("N2"), new TypeNameSet { "T2", "T3" }) .AddVisibleTypesByNamespace(new Domain("N3"), new TypeNameSet { "T4" }); configBuilder.VisibleTypesByNamespace.Should().BeEquivalentTo( new Dictionary { {new Domain("N1"), null}, {new Domain("N2"), new TypeNameSet {"T1", "T2", "T3"}}, {new Domain("N3"), new TypeNameSet {"T4"}}, }); } [Fact] public void Combine_EmptyWithEmpty() { var configBuilder1 = new AnalyzerConfigBuilder(); var configBuilder2 = new AnalyzerConfigBuilder(); configBuilder1.Combine(configBuilder2); configBuilder1.IsEnabled.Should().BeNull(); configBuilder1.ChildCanDependOnParentImplicitly.Should().BeNull(); configBuilder1.AllowRules.Should().HaveCount(0); configBuilder1.DisallowRules.Should().HaveCount(0); configBuilder1.VisibleTypesByNamespace.Should().HaveCount(0); configBuilder1.AllowedAssemblyRules.Should().HaveCount(0); configBuilder1.DisallowedAssemblyRules.Should().HaveCount(0); configBuilder1.MaxIssueCount.Should().BeNull(); } [Fact] public void AddAllowedAssemblyRule_Works() { var configBuilder = new AnalyzerConfigBuilder() .AddAllowedAssemblyRule(new DependencyRule("N1", "N2")) .AddAllowedAssemblyRule(new DependencyRule("N3", "N4")); configBuilder .AddAllowedAssemblyRule(new DependencyRule("N3", "N4")) .AddAllowedAssemblyRule(new DependencyRule("N5", "N6")); configBuilder.AllowedAssemblyRules.Should().BeEquivalentTo( new HashSet() { new DependencyRule("N1", "N2"), new DependencyRule("N3", "N4"), new DependencyRule("N5", "N6"), } ); } [Fact] public void AddDisallowedAssemblyRule_Works() { var configBuilder = new AnalyzerConfigBuilder() .AddDisallowedAssemblyRule(new DependencyRule("A1", "A2")) .AddDisallowedAssemblyRule(new DependencyRule("A3", "A4")); configBuilder .AddDisallowedAssemblyRule(new DependencyRule("A3", "A4")) .AddDisallowedAssemblyRule(new DependencyRule("A5", "A6")); configBuilder.DisallowedAssemblyRules.Should().BeEquivalentTo( new HashSet() { new DependencyRule("A1", "A2"), new DependencyRule("A3", "A4"), new DependencyRule("A5", "A6"), } ); } [Fact] public void Combine_NonEmptyWithEmpty() { var configBuilder1 = new AnalyzerConfigBuilder() .SetIsEnabled(true) .SetChildCanDependOnParentImplicitly(true) .AddAllowRule(new DependencyRule("N1", "N2"), new TypeNameSet { "T1" }) .AddDisallowRule(new DependencyRule("N3", "N4")) .AddVisibleTypesByNamespace(new Domain("N5"), new TypeNameSet { "T2" }) .AddAllowedAssemblyRule(new DependencyRule("A1", "A2")) .AddAllowedAssemblyRule(new DependencyRule("A3", "A4")) .AddDisallowedAssemblyRule(new DependencyRule("A5", "A6")) .AddDisallowedAssemblyRule(new DependencyRule("A7", "A8")) .SetMaxIssueCount(42); var configBuilder2 = new AnalyzerConfigBuilder(); configBuilder1.Combine(configBuilder2); configBuilder1.IsEnabled.Should().Be(true); configBuilder1.ChildCanDependOnParentImplicitly.Should().Be(true); configBuilder1.AllowRules.Should().HaveCount(1); configBuilder1.DisallowRules.Should().HaveCount(1); configBuilder1.VisibleTypesByNamespace.Should().HaveCount(1); configBuilder1.AllowedAssemblyRules.Should().HaveCount(2); configBuilder1.DisallowedAssemblyRules.Should().HaveCount(2); configBuilder1.MaxIssueCount.Should().Be(42); } [Fact] public void Combine_EmptyWithNonEmpty() { var configBuilder1 = new AnalyzerConfigBuilder(); var configBuilder2 = new AnalyzerConfigBuilder() .SetIsEnabled(true) .SetChildCanDependOnParentImplicitly(true) .AddAllowRule(new DependencyRule("N1", "N2"), new TypeNameSet { "T1" }) .AddDisallowRule(new DependencyRule("N3", "N4")) .AddVisibleTypesByNamespace(new Domain("N5"), new TypeNameSet { "T2" }) .AddAllowedAssemblyRule(new DependencyRule("A1", "A2")) .AddAllowedAssemblyRule(new DependencyRule("A3", "A4")) .AddDisallowedAssemblyRule(new DependencyRule("A5", "A6")) .AddDisallowedAssemblyRule(new DependencyRule("A7", "A8")) .SetMaxIssueCount(42); configBuilder1.Combine(configBuilder2); configBuilder1.IsEnabled.Should().Be(true); configBuilder1.ChildCanDependOnParentImplicitly.Should().Be(true); configBuilder1.AllowRules.Should().HaveCount(1); configBuilder1.DisallowRules.Should().HaveCount(1); configBuilder1.VisibleTypesByNamespace.Should().HaveCount(1); configBuilder1.AllowedAssemblyRules.Should().HaveCount(2); configBuilder1.DisallowedAssemblyRules.Should().HaveCount(2); configBuilder1.MaxIssueCount.Should().Be(42); } [Fact] public void Combine_NonEmptyWithNonEmpty() { var configBuilder1 = new AnalyzerConfigBuilder() .SetIsEnabled(false) .SetChildCanDependOnParentImplicitly(false) .AddAllowRule(new DependencyRule("N1", "N2"), new TypeNameSet { "T1" }) .AddDisallowRule(new DependencyRule("N3", "N4")) .AddVisibleTypesByNamespace(new Domain("N5"), new TypeNameSet { "T2" }) .AddAllowedAssemblyRule(new DependencyRule("A1", "A2")) .AddDisallowedAssemblyRule(new DependencyRule("A3", "A4")) .SetMaxIssueCount(43); var configBuilder2 = new AnalyzerConfigBuilder() .SetIsEnabled(true) .SetChildCanDependOnParentImplicitly(true) .AddAllowRule(new DependencyRule("N6", "N7"), new TypeNameSet { "T3" }) .AddDisallowRule(new DependencyRule("N8", "N9")) .AddVisibleTypesByNamespace(new Domain("N10"), new TypeNameSet { "T4" }) .AddAllowedAssemblyRule(new DependencyRule("A6", "A7")) .AddAllowedAssemblyRule(new DependencyRule("A8", "A9")) .AddDisallowedAssemblyRule(new DependencyRule("A9", "A9")) .AddDisallowedAssemblyRule(new DependencyRule("A10", "A10")) .SetMaxIssueCount(42); configBuilder1.Combine(configBuilder2); configBuilder1.IsEnabled.Should().Be(true); configBuilder1.ChildCanDependOnParentImplicitly.Should().Be(true); configBuilder1.AllowRules.Should().HaveCount(2); configBuilder1.DisallowRules.Should().HaveCount(2); configBuilder1.VisibleTypesByNamespace.Should().HaveCount(2); configBuilder1.AllowedAssemblyRules.Should().HaveCount(3); configBuilder1.DisallowedAssemblyRules.Should().HaveCount(3); configBuilder1.MaxIssueCount.Should().Be(42); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/Attributes_LowerLevelWins/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/Attributes_LowerLevelWins/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/Attributes_MissingDoesNotOverwrite/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/Attributes_MissingDoesNotOverwrite/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigDisabledAtHigherLevelAndUndefinedAtProjectLevel/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigDisabledAtHigherLevelAndUndefinedAtProjectLevel/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigDisabledAtHigherLevelButEnabledAtProjectLevel/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigDisabledAtHigherLevelButEnabledAtProjectLevel/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigDisabledAtProjectLevel/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigDisabledAtProjectLevel/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigEnabled/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigEnabled/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigError/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigError/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ConfigError/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ExcludedFiles_AllCorrectlyRooted/Level2/Excluded File 4.cs ================================================ class ExcludedFile4 { } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ExcludedFiles_AllCorrectlyRooted/Level2/ExcludedFile3.cs ================================================ class ExcludedFile3 { } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ExcludedFiles_AllCorrectlyRooted/Level2/Level1/Excluded File 2.cs ================================================ class ExcludedFile2a { } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ExcludedFiles_AllCorrectlyRooted/Level2/Level1/ExcludedFile1.cs ================================================ class ExcludedFile1a { } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ExcludedFiles_AllCorrectlyRooted/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/ExcludedFiles_AllCorrectlyRooted/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/NoConfig/Level2/Level1/placeholder.txt ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToConfigError/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToConfigError/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToDisabled/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToDisabled/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToEnabledButChanged/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToEnabledButChanged/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_EnabledToNoConfig/Level2/Level1/placeholder.txt ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_InheritanceDepthChanged/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_InheritanceDepthChanged/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/RefreshConfig_NoConfigToEnabled/Level2/Level1/placeholder.txt ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/Rules_Merged/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/Rules_Merged/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/UpdateMaxIssueCount_Level1ContainsMaxIssueCount/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/UpdateMaxIssueCount_Level1ContainsMaxIssueCount/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/UpdateMaxIssueCount_Level1ContainsNoMaxIssueCount/Level2/Level1/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests/UpdateMaxIssueCount_Level1ContainsNoMaxIssueCount/Level2/config.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/MultiLevelXmlFileConfigProviderTests.cs ================================================ using System.IO; using System.Linq; using System.Threading; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Config.Implementation; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Config { public class MultiLevelXmlFileConfigProviderTests : XmlFileConfigTestBase { [Fact] public void Rules_Merged() { var folder = GetFilePathInTestClassFolder(@"Rules_Merged/Level2/Level1"); var configProvider = CreateConfigProvider(folder); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); configProvider.ConfigException.Should().BeNull(); var allowedRules = configProvider.Config.AllowRules; allowedRules.Should().HaveCount(2); allowedRules.Keys.Should().Contain(new DependencyRule("N1", "N2")); allowedRules.Keys.Should().Contain(new DependencyRule("N3", "N4")); } [Fact] public void Attributes_LowerLevelWins() { var folder = GetFilePathInTestClassFolder("Attributes_LowerLevelWins/Level2/Level1"); var configProvider = CreateConfigProvider(folder); configProvider.Config.MaxIssueCount.Should().Be(2); } [Fact] public void Attributes_MissingDoesNotOverwrite() { var folder = GetFilePathInTestClassFolder("Attributes_MissingDoesNotOverwrite/Level2/Level1"); var configProvider = CreateConfigProvider(folder); configProvider.Config.MaxIssueCount.Should().Be(1); } [Fact] public void ExcludedFiles_AllCorrectlyRooted() { var folder = GetFilePathInTestClassFolder("ExcludedFiles_AllCorrectlyRooted/Level2/Level1"); var expectedExcludedFiles = new[] { GetFilePathInTestClassFolder("ExcludedFiles_AllCorrectlyRooted/Level2/Level1/ExcludedFile1.cs"), GetFilePathInTestClassFolder("ExcludedFiles_AllCorrectlyRooted/Level2/Level1/Excluded File 2.cs"), GetFilePathInTestClassFolder("ExcludedFiles_AllCorrectlyRooted/Level2/ExcludedFile3.cs"), GetFilePathInTestClassFolder("ExcludedFiles_AllCorrectlyRooted/Level2/Excluded File 4.cs"), }; var configProvider = CreateConfigProvider(folder); configProvider.Config.SourcePathExclusionPatterns.Select(Path.GetFullPath).Should().BeEquivalentTo(expectedExcludedFiles.Select(Path.GetFullPath)); configProvider.Config.SourcePathExclusionPatterns.All(File.Exists).Should().BeTrue(); } [Fact] public void Properties_ConfigNotFound() { var path = GetFilePathInTestClassFolder("NoConfig/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.NoConfig); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void Properties_ConfigError() { var path = GetFilePathInTestClassFolder("ConfigError/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.ConfigError); configProvider.ConfigException.Should().NotBeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void Properties_ConfigEnabled() { var path = GetFilePathInTestClassFolder("ConfigEnabled/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().NotBeNull(); } [Fact] public void Properties_ConfigDisabledAtProjectLevel_EffectiveDisabled() { var path = GetFilePathInTestClassFolder("ConfigDisabledAtProjectLevel/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Disabled); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void Properties_ConfigDisabledAtHigherLevelAndUndefinedAtProjectLevel_EffectiveDisabled() { var path = GetFilePathInTestClassFolder("ConfigDisabledAtHigherLevelAndUndefinedAtProjectLevel/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Disabled); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void Properties_ConfigDisabledAtHigherLevelButEnabledAtProjectLevel_DisabledConfigNotCombinedToEffective() { var path = GetFilePathInTestClassFolder("ConfigDisabledAtHigherLevelButEnabledAtProjectLevel/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); configProvider.ConfigException.Should().BeNull(); configProvider.Config.MaxIssueCount.Should().Be(ConfigDefaults.MaxIssueCount); } [Fact] public void RefreshConfig_Unchanged() { var path = GetFilePathInTestClassFolder("ConfigEnabled/Level2/Level1"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); var savedConfig = configProvider.Config; configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); configProvider.Config.Should().Be(savedConfig); } [Fact] public void RefreshConfig_EnabledToEnabledButChanged() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToEnabledButChanged/Level2/Level1"); var path2 = GetFilePathInTestClassFolder("RefreshConfig_EnabledToEnabledButChanged"); SetAttribute(GetConfigFilePath(path), "AutoLowerMaxIssueCount", "true"); SetAttribute(GetConfigFilePath(path2), "MaxIssueCount", "1"); var configProvider = CreateConfigProvider(path); configProvider.Config.AutoLowerMaxIssueCount.Should().Be(true); configProvider.Config.MaxIssueCount.Should().Be(1); Thread.Sleep(10); SetAttribute(GetConfigFilePath(path), "AutoLowerMaxIssueCount", "false"); SetAttribute(GetConfigFilePath(path2), "MaxIssueCount", "2"); configProvider.RefreshConfig(); configProvider.Config.AutoLowerMaxIssueCount.Should().Be(false); configProvider.Config.MaxIssueCount.Should().Be(2); } [Fact] public void RefreshConfig_EnabledToDisabled() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToDisabled/Level2/Level1"); SetAttribute(GetConfigFilePath(path), "IsEnabled", "true"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); Thread.Sleep(10); SetAttribute(GetConfigFilePath(path), "IsEnabled", "false"); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Disabled); } [Fact] public void RefreshConfig_EnabledToConfigError() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToConfigError/Level2/Level1"); SetAttribute(GetConfigFilePath(path), "IsEnabled", "true"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); Thread.Sleep(10); SetAttribute(GetConfigFilePath(path), "IsEnabled", "maybe"); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.ConfigError); } [Fact] public void RefreshConfig_NoConfigToEnabled() { var path = GetFilePathInTestClassFolder("RefreshConfig_NoConfigToEnabled/Level2/Level1"); Delete(GetConfigFilePath(path)); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.NoConfig); CreateConfigFile(GetConfigFilePath(path), "true", 2); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); } [Fact] public void RefreshConfig_EnabledToNoConfig() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToNoConfig/Level2/Level1"); Delete(path); CreateConfigFile(GetConfigFilePath(path), "true", 2); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); Delete(GetConfigFilePath(path)); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.NoConfig); } [Fact] public void RefreshConfig_InheritanceDepthChangedFrom0To2() { var path = GetFilePathInTestClassFolder("RefreshConfig_InheritanceDepthChanged/Level2/Level1"); SetAttribute(GetConfigFilePath(path), "InheritanceDepth", "0"); var configProvider = CreateConfigProvider(path); configProvider.InheritanceDepth.Should().Be(0); configProvider.Config.MaxIssueCount.Should().Be(ConfigDefaults.MaxIssueCount); Thread.Sleep(10); SetAttribute(GetConfigFilePath(path), "InheritanceDepth", "2"); configProvider.RefreshConfig(); configProvider.InheritanceDepth.Should().Be(2); configProvider.Config.MaxIssueCount.Should().Be(42); } [Fact] public void RefreshConfig_InheritanceDepthChangedFrom2To0() { var path = GetFilePathInTestClassFolder("RefreshConfig_InheritanceDepthChanged/Level2/Level1"); SetAttribute(GetConfigFilePath(path), "InheritanceDepth", "2"); var configProvider = CreateConfigProvider(path); configProvider.InheritanceDepth.Should().Be(2); configProvider.Config.MaxIssueCount.Should().Be(42); Thread.Sleep(10); SetAttribute(GetConfigFilePath(path), "InheritanceDepth", "0"); configProvider.RefreshConfig(); configProvider.InheritanceDepth.Should().Be(0); configProvider.Config.MaxIssueCount.Should().Be(ConfigDefaults.MaxIssueCount); } [Fact] public void UpdateMaxIssueCount_Level1ContainsNoMaxIssueCount_SetToNewValue() { var path = GetFilePathInTestClassFolder("UpdateMaxIssueCount_Level1ContainsNoMaxIssueCount/Level2/Level1"); RemoveAttribute(GetConfigFilePath(path), "MaxIssueCount"); var configProvider = CreateConfigProvider(path); configProvider.Config.MaxIssueCount.Should().Be(42); Thread.Sleep(10); configProvider.UpdateMaxIssueCount(142); configProvider.Config.MaxIssueCount.Should().Be(142); GetAttribute(GetConfigFilePath(path.Replace("/Level1", "")), "MaxIssueCount").Should().Be(42.ToString()); GetAttribute(GetConfigFilePath(path), "MaxIssueCount").Should().Be(142.ToString()); } [Fact] public void UpdateMaxIssueCount_Level1ContainsMaxIssueCount_SetToNewValue() { var path = GetFilePathInTestClassFolder("UpdateMaxIssueCount_Level1ContainsMaxIssueCount/Level2/Level1"); SetAttribute(GetConfigFilePath(path), "MaxIssueCount", 42.ToString()); var configProvider = CreateConfigProvider(path); configProvider.Config.MaxIssueCount.Should().Be(42); Thread.Sleep(10); configProvider.UpdateMaxIssueCount(142); configProvider.Config.MaxIssueCount.Should().Be(142); GetAttribute(GetConfigFilePath(path.Replace("/Level1", "")), "MaxIssueCount").Should().BeNull(); GetAttribute(GetConfigFilePath(path), "MaxIssueCount").Should().Be(142.ToString()); } private static string GetConfigFilePath(string path) { return Path.Combine(path, ProductConstants.DefaultConfigFileName); } private static MultiLevelXmlFileConfigProvider CreateConfigProvider(string folder) { return new MultiLevelXmlFileConfigProvider(folder, traceMessageHandler: null); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/AllowedAssemblyRules.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/AllowedRuleForNamespaceWithVisibleMembersWithOfNamespaceAttribute.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/AllowedRuleForWildcardNamespaceWithVisibleMembers.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/AllowedRuleFromAttributeMissing.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/AllowedRuleToAttributeMissing.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/AllowedRules.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/DisallowedAssemblyRules.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/DisallowedRules.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/InvalidAttributeValue.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/InvalidDuplicatedWildcardNamespaceString.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/InvalidNamespaceString.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/NoRootAttributes.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/NsDepCopConfigElementNotFound.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/RootAttributes.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/VisibleMembers.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/VisibleMembersOfNamespaceMissing.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests/VisibleMembersTypeNameAttributeMissing.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlConfigParserTests.cs ================================================ using System; using System.Xml.Linq; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Config.Implementation; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Config { public class XmlConfigParserTests : FileBasedTestsBase { [Fact] public void Parse_NoRootAttributes() { var xDocument = LoadXml("NoRootAttributes.nsdepcop"); var configBuilder = XmlConfigParser.Parse(xDocument); configBuilder.IsEnabled.Should().BeNull(); configBuilder.InheritanceDepth.Should().BeNull(); configBuilder.MaxIssueCount.Should().BeNull(); configBuilder.ChildCanDependOnParentImplicitly.Should().BeNull(); configBuilder.AutoLowerMaxIssueCount.Should().BeNull(); configBuilder.SourcePathExclusionPatterns.Should().BeEmpty(); configBuilder.CheckAssemblyDependencies.Should().BeNull(); } [Fact] public void Parse_RootAttributes() { var xDocument = LoadXml("RootAttributes.nsdepcop"); var configBuilder = XmlConfigParser.Parse(xDocument); configBuilder.IsEnabled.Should().BeTrue(); configBuilder.InheritanceDepth.Should().Be(9); configBuilder.MaxIssueCount.Should().Be(42); configBuilder.ChildCanDependOnParentImplicitly.Should().BeTrue(); configBuilder.AutoLowerMaxIssueCount.Should().BeTrue(); configBuilder.SourcePathExclusionPatterns.Should().BeEquivalentTo("**/*.g.cs", @"TestData\**\*.cs"); configBuilder.CheckAssemblyDependencies.Should().BeTrue(); } [Fact] public void Parse_AllowedRules() { var xDocument = LoadXml("AllowedRules.nsdepcop"); var config = XmlConfigParser.Parse(xDocument); var allowedRules = config.AllowRules; allowedRules.Should().HaveCount(3); { var types = allowedRules[new DependencyRule("N1", "N2")]; types.Should().BeNull(); } { var types = allowedRules[new DependencyRule("N3", "N4")]; types.Should().HaveCount(2); types.Should().Contain("T1"); types.Should().Contain("T2"); } { var types = allowedRules[new DependencyRule("N5", "N6")]; types.Should().BeNull(); } } [Fact] public void Parse_DisallowedRules() { var xDocument = LoadXml("DisallowedRules.nsdepcop"); var config = XmlConfigParser.Parse(xDocument); var disallowedRules = config.DisallowRules; disallowedRules.Should().HaveCount(2); disallowedRules.Should().Contain(new DependencyRule("N1", "N2")); disallowedRules.Should().Contain(new DependencyRule("N3", "N4")); } [Fact] public void Parse_VisibleMembers() { var xDocument = LoadXml("VisibleMembers.nsdepcop"); var config = XmlConfigParser.Parse(xDocument); var visibleTypesByNamespace = config.VisibleTypesByNamespace; visibleTypesByNamespace.Should().HaveCount(2); { var types = visibleTypesByNamespace[new Domain("N1")]; types.Should().HaveCount(2); types.Should().Contain("T1"); types.Should().Contain("T2"); } { var types = visibleTypesByNamespace[new Domain("N2")]; types.Should().HaveCount(1); types.Should().Contain("T3"); } } [Fact] public void Parse_AllowedAssemblyRules() { var xDocument = LoadXml("AllowedAssemblyRules.nsdepcop"); var config = XmlConfigParser.Parse(xDocument); var allowedAssemblyRules = config.AllowedAssemblyRules; allowedAssemblyRules.Should().HaveCount(2); allowedAssemblyRules.Should().Contain(new DependencyRule("A1", "A2")); allowedAssemblyRules.Should().Contain(new DependencyRule("A3", "A4")); } [Fact] public void Parse_DisallowedAssemblyRules() { var xDocument = LoadXml("DisallowedAssemblyRules.nsdepcop"); var config = XmlConfigParser.Parse(xDocument); var disallowedAssemblyRules = config.DisallowedAssemblyRules; disallowedAssemblyRules.Should().HaveCount(2); disallowedAssemblyRules.Should().Contain(new DependencyRule("A1", "A2")); disallowedAssemblyRules.Should().Contain(new DependencyRule("A3", "A4")); } [Fact] public void Parse_AllowedRuleForWildcardNamespaceWithVisibleMembers_Throws() { var xDocument = LoadXml("AllowedRuleForWildcardNamespaceWithVisibleMembers.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*must be a single namespace*"); } [Fact] public void Parse_AllowedRuleForNamespaceWithVisibleMembersWithOfNamespaceAttribute_Throws() { var xDocument = LoadXml("AllowedRuleForNamespaceWithVisibleMembersWithOfNamespaceAttribute.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*'OfNamespace' attribute must not be defined*"); } [Fact] public void Parse_VisibleMembersOfNamespaceMissing_Throws() { var xDocument = LoadXml("VisibleMembersOfNamespaceMissing.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*'OfNamespace' attribute missing*"); } [Fact] public void Parse_AllowedRuleFromAttributeMissing_Throws() { var xDocument = LoadXml("AllowedRuleFromAttributeMissing.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*'From' attribute missing*"); } [Fact] public void Parse_AllowedRuleToAttributeMissing_Throws() { var xDocument = LoadXml("AllowedRuleToAttributeMissing.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*'To' attribute missing*"); } [Fact] public void Parse_VisibleMembersTypeNameAttributeMissing_Throws() { var xDocument = LoadXml("VisibleMembersTypeNameAttributeMissing.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*'Name' attribute missing*"); } [Fact] public void Parse_NsDepCopConfigElementNotFound_Throws() { var xDocument = LoadXml("NsDepCopConfigElementNotFound.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*root element not found*"); } [Fact] public void Parse_InvalidNamespaceString_Throws() { var xDocument = LoadXml("InvalidNamespaceString.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*not a valid Domain*"); } [Fact] public void Parse_InvalidDuplicatedWildcardNamespaceString_Throws() { var xDocument = LoadXml("InvalidDuplicatedWildcardNamespaceString.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*not a valid WildcardDomain*"); } [Fact] public void Parse_InvalidAttributeValue_Throws() { var xDocument = LoadXml("InvalidAttributeValue.nsdepcop"); Action a = () => XmlConfigParser.Parse(xDocument); a.Should().Throw().WithMessage("*Error parsing 'IsEnabled' value*"); } [Fact] public void UpdateMaxIssueCount_HasMaxIssueCountAttribute_Works() { const int newMaxIssueCount = 101; var xDocument = LoadXml("RootAttributes.nsdepcop"); XmlConfigParser.UpdateMaxIssueCount(xDocument, newMaxIssueCount); xDocument.Element("NsDepCopConfig")?.Attribute("MaxIssueCount")?.Value.Should().Be(newMaxIssueCount.ToString()); } [Fact] public void UpdateMaxIssueCount_NoMaxIssueCountAttribute_Works() { const int newMaxIssueCount = 101; var xDocument = LoadXml("NoRootAttributes.nsdepcop"); XmlConfigParser.UpdateMaxIssueCount(xDocument, newMaxIssueCount); xDocument.Element("NsDepCopConfig")?.Attribute("MaxIssueCount")?.Value.Should().Be(newMaxIssueCount.ToString()); } private XDocument LoadXml(string filename) { var path = GetFilePathInTestClassFolder(filename); return XDocument.Load(path); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/Disabled.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/Enabled.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/Erronous.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/Excluded File 2.cs ================================================ class ExcludedFile2b { } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/ExcludedFile1.cs ================================================ class ExcludedFile1b { } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/ExcludedFiles.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/RefreshConfig_EnabledToConfigError.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests/RefreshConfig_EnabledToDisabled.nsdepcop ================================================  ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigProviderTests.cs ================================================ using System; using System.IO; using System.Linq; using System.Threading; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.Config.Implementation; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Implementation.Config { public class XmlFileConfigProviderTests : XmlFileConfigTestBase { [Fact] public void Properties_ConfigNotFound() { var path = GetFilePathInTestClassFolder("NonExisting.nsdepcop"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.NoConfig); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void Properties_ConfigEnabled() { var path = GetFilePathInTestClassFolder("Enabled.nsdepcop"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().NotBeNull(); } [Fact] public void Properties_ExcludedFiles_AreRooted() { var path = GetFilePathInTestClassFolder("ExcludedFiles.nsdepcop"); var configProvider = CreateConfigProvider(path); var expectedExcludedFiles = new[] { GetFilePathInTestClassFolder("ExcludedFile1.cs"), GetFilePathInTestClassFolder("Excluded File 2.cs") }; var exclusionPatterns = configProvider.Config.SourcePathExclusionPatterns; exclusionPatterns.Select(Path.GetFullPath).Should().BeEquivalentTo(expectedExcludedFiles.Select(Path.GetFullPath)); exclusionPatterns.All(File.Exists).Should().BeTrue(); } [Fact] public void Properties_ConfigDisabled() { var path = GetFilePathInTestClassFolder("Disabled.nsdepcop"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Disabled); configProvider.ConfigException.Should().BeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void Properties_ConfigError() { var path = GetFilePathInTestClassFolder("Erronous.nsdepcop"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.ConfigError); configProvider.ConfigException.Should().NotBeNull(); configProvider.Config.Should().BeNull(); } [Fact] public void RefreshConfig_Unchanged() { var path = GetFilePathInTestClassFolder("Enabled.nsdepcop"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); var savedConfig = configProvider.Config; configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); configProvider.Config.Should().Be(savedConfig); } [Fact] public void RefreshConfig_EnabledToDisabled() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToDisabled.nsdepcop"); SetAttribute(path, "IsEnabled", "true"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); Thread.Sleep(10); SetAttribute(path, "IsEnabled", "false"); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Disabled); } [Fact] public void RefreshConfig_EnabledToConfigError() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToConfigError.nsdepcop"); SetAttribute(path, "IsEnabled", "true"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); Thread.Sleep(10); SetAttribute(path, "IsEnabled", "maybe"); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.ConfigError); } [Fact] public void RefreshConfig_NoConfigToEnabled() { var path = GetFilePathInTestClassFolder("RefreshConfig_NoConfigToEnabled.nsdepcop"); Delete(path); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.NoConfig); CreateConfigFile(path, "true"); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); } [Fact] public void RefreshConfig_EnabledToNoConfig() { var path = GetFilePathInTestClassFolder("RefreshConfig_EnabledToNoConfig.nsdepcop"); Delete(path); CreateConfigFile(path, "true"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Enabled); Delete(path); configProvider.RefreshConfig(); configProvider.ConfigState.Should().Be(AnalyzerConfigState.NoConfig); } [Fact] public void UpdateMaxIssueCount_DisabledConfig_Throws() { var path = GetFilePathInTestClassFolder("UpdateMaxIssueCount_DisabledConfig.nsdepcop"); Delete(path); CreateConfigFile(path, "false"); var configProvider = CreateConfigProvider(path); configProvider.ConfigState.Should().Be(AnalyzerConfigState.Disabled); Action a = () => configProvider.UpdateMaxIssueCount(142); a.Should().Throw().Where(i => i.Message.Contains(AnalyzerConfigState.Disabled.ToString())); Delete(path); } [Fact] public void UpdateMaxIssueCount_FromNoneToNewValue() { var path = GetFilePathInTestClassFolder("UpdateMaxIssueCount_FromNoneToNewValue.nsdepcop"); Delete(path); CreateConfigFile(path, "true", maxIssueCount: null); var configProvider = CreateConfigProvider(path); configProvider.Config.MaxIssueCount.Should().Be(ConfigDefaults.MaxIssueCount); configProvider.UpdateMaxIssueCount(142); configProvider.Config.MaxIssueCount.Should().Be(142); Delete(path); } [Fact] public void UpdateMaxIssueCount_FromOldValueToNewValue() { var path = GetFilePathInTestClassFolder("UpdateMaxIssueCount_FromOldValueToNewValue.nsdepcop"); Delete(path); CreateConfigFile(path, "true", maxIssueCount: 42); var configProvider = CreateConfigProvider(path); configProvider.Config.MaxIssueCount.Should().Be(42); configProvider.UpdateMaxIssueCount(142); configProvider.Config.MaxIssueCount.Should().Be(142); Delete(path); } private static XmlFileConfigProvider CreateConfigProvider(string path) { return new XmlFileConfigProvider(path, traceMessageHandler: null); } } } ================================================ FILE: source/NsDepCop.Test/Implementation/Config/XmlFileConfigTestBase.cs ================================================ using System.Xml.Linq; namespace Codartis.NsDepCop.Test.Implementation.Config { public class XmlFileConfigTestBase : FileBasedTestsBase { protected static void CreateConfigFile(string path, string isEnabledString, int inheritanceDepth = 0, int? maxIssueCount = null) { var document = XDocument.Parse($""); document.Save(path); } private static string GetMaxIssueCountAttributeString(int? maxIssueCount) { return maxIssueCount == null ? string.Empty : $" MaxIssueCount='{maxIssueCount}' "; } protected static string GetAttribute(string path, string attributeName) { var document = XDocument.Load(path); return document.Root.Attribute(attributeName)?.Value; } protected static void SetAttribute(string path, string attributeName, string value) { var document = XDocument.Load(path); var xAttribute = document.Root.Attribute(attributeName); if (xAttribute == null) document.Root.Add(new XAttribute(attributeName, value)); else xAttribute.SetValue(value); document.Save(path); } protected static void RemoveAttribute(string path, string attributeName) { var document = XDocument.Load(path); document.Root.Attribute(attributeName)?.Remove(); document.Save(path); } } } ================================================ FILE: source/NsDepCop.Test/Interface/Config/DomainSpecificationParserTests.cs ================================================ using System; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Interface.Config { public class DomainSpecificationParserTests { [Theory] [InlineData(".")] [InlineData("A.B")] public void Parse_DomainSpecification(string domainString) { DomainSpecificationParser.Parse(domainString).Should().Be(new Domain(domainString)); } [Theory] [InlineData("*")] [InlineData("A.*")] [InlineData("A.*.B")] [InlineData("A.?.B")] [InlineData("*.B")] [InlineData("?.B")] [InlineData("A.B.?")] [InlineData("A.B.*")] public void Parse_WildcardDomainSpecification(string wildcardDomainString) { DomainSpecificationParser.Parse(wildcardDomainString).Should().Be(new WildcardDomain(wildcardDomainString)); } [Theory] [InlineData("/Unit\\.Test/")] [InlineData("/^Unit\\.Test$/")] public void Parse_RegexDomainSpecification(string regexDomainString) { DomainSpecificationParser.Parse(regexDomainString).Should().Be(new RegexDomain(regexDomainString)); } [Theory] [InlineData("..")] [InlineData(".A")] [InlineData("A.")] [InlineData("*.*")] [InlineData("/foo{2,1}/")] public void Parse_Invalid(string invalidString) { Assert.Throws(() => DomainSpecificationParser.Parse(invalidString)); } } } ================================================ FILE: source/NsDepCop.Test/Interface/Config/DomainSpecificationTests.cs ================================================ using System.Linq; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Interface.Config { public class DomainSpecificationTests { [Theory] [InlineData("A.B", "D", 0)] [InlineData("A.B", "D.*", 0)] [InlineData("A.B", "A.B.C.*", 0)] [InlineData("A.B", "A", 0)] [InlineData("A.B", ".", 0)] [InlineData("A.B", "A.B", int.MaxValue)] [InlineData("A.B", "A.?", int.MaxValue - 1)] [InlineData("A.B", "A.*", int.MaxValue - 2)] [InlineData("A.B", "A.B.*", int.MaxValue - 1)] [InlineData("A.B", "A.B.?", 0)] [InlineData("A.B", "/A\\.B/", 1)] [InlineData("A.B", "/A\\../", 1)] [InlineData("A.B", "/A\\.[A-Z]/", 1)] [InlineData("A.B", "/A\\.[0-9]/", 0)] [InlineData("A.C.D", "B.?.D", 0)] [InlineData("A.C.D", "*.E", 0)] [InlineData("A.C.D", "A.C.D", int.MaxValue)] [InlineData("A.C.D", "A.?.D", int.MaxValue - 1)] [InlineData("A.C.D", "A.C.?", int.MaxValue - 1)] [InlineData("A.C.D", "?.C.D", int.MaxValue - 1)] [InlineData("A.C.D", "A.?.?", int.MaxValue - 2)] [InlineData("A.C.D", "?.?.D", int.MaxValue - 2)] [InlineData("A.C.D", "?.?.?", int.MaxValue - 3)] [InlineData("A.C.D", "A.*", int.MaxValue - 3)] [InlineData("A.C.D", "*.D", int.MaxValue - 3)] [InlineData("A.C.D", "*.?.D", int.MaxValue - 3)] [InlineData("A.B.C.D", "*.?.D", int.MaxValue - 4)] [InlineData("A.C.D", "*", int.MaxValue - 4)] [InlineData("A.F1.B.C", "A.*.B", 0)] [InlineData("A.F1.B.C", "A.*.B.*", int.MaxValue - 4)] [InlineData("A.F1.B.C", "A.*.B.?", int.MaxValue - 3)] [InlineData("A.F1.B.C", "A.?.B.?", int.MaxValue - 2)] [InlineData("A.F1.B.C", "A.?.B.*", int.MaxValue - 3)] public void GetMatchRelevance_ShouldReturnTheExpectedValue(string domainString, string domainSpecificationString, int expectedMatchRelevance) { var domain = new Domain(domainString); var domainSpecification = DomainSpecificationParser.Parse(domainSpecificationString); domainSpecification.GetMatchRelevance(domain).Should().Be(expectedMatchRelevance); } /// /// This test verifies that the matching rules are prioritized correctly, according to their match relevance. /// [Theory] [InlineData("A.B", "A.B", "A.B.*", "A.?", "A.*", "/A\\.B/")] public void GetMatchRelevance_ShouldPrioritizeProperly(string domainString, params string[] domainSpecificationStrings) { var domain = new Domain(domainString); var domainSpecifications = domainSpecificationStrings.Select(DomainSpecificationParser.Parse); domainSpecifications.Select(i => i.GetMatchRelevance(domain)).Should().BeInDescendingOrder(); } } } ================================================ FILE: source/NsDepCop.Test/Interface/Config/DomainTests.cs ================================================ using System; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Interface.Config { public class DomainTests { [Theory] [InlineData("A")] [InlineData("A.B")] [InlineData(".")] public void Create_Works(string domainString) { new Domain(domainString).ToString().Should().Be(domainString); } [Theory] [InlineData("")] [InlineData("")] public void Create_GlobalDomainRepresentationNormalized(string globalDomainString) { new Domain(globalDomainString).ToString().Should().Be("."); } [Fact] public void Create_WithNull_ThrowsArgumentNullException() { Assert.Throws(() => new Domain(null)); } [Theory] [InlineData("*")] [InlineData("A.*")] public void Create_AnyDomain_ThrowsFormatException(string domainString) { Assert.Throws(() => new Domain(domainString)); } [Fact] public void IsSubDomainOf_Works() { new Domain("A").IsSubDomain(new Domain(".")).Should().BeTrue(); new Domain("A.B").IsSubDomain(new Domain(".")).Should().BeTrue(); new Domain("A.B").IsSubDomain(new Domain("A")).Should().BeTrue(); new Domain("A.B.C").IsSubDomain(new Domain("A")).Should().BeTrue(); new Domain(".").IsSubDomain(new Domain("A")).Should().BeFalse(); new Domain("A").IsSubDomain(new Domain("A")).Should().BeFalse(); new Domain("A").IsSubDomain(new Domain("A.B")).Should().BeFalse(); } [Fact] public void Equals_Works() { (new Domain("A") == new Domain("A")).Should().BeTrue(); (new Domain("A") == new Domain("B")).Should().BeFalse(); } [Fact] public void GlobalDomain_IsEqualToOtherInstanceOfGlobalDomain() { (new Domain(".") == Domain.GlobalDomain).Should().BeTrue(); } } } ================================================ FILE: source/NsDepCop.Test/Interface/Config/RegexDomainTests.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Interface.Config; public sealed class RegexDomainTests { [Theory] [InlineData("/Unit\\.Test/")] [InlineData("/^Unit\\.Test(\\.[A-Za-z_][A-Za-z0-9_]*)*$/")] public void Create_Works(string regexDomainString) { new RegexDomain(regexDomainString).ToString().Should().Be(regexDomainString); } [Fact] public void Create_WithNull_ThrowsArgumentNullException() { var act = () => new RegexDomain(null); act.Should().Throw(); } [Theory] [InlineData("")] [InlineData("//")] [InlineData("^Unit\\.Test(\\.[A-Za-z_][A-Za-z0-9_]*)*$")] [InlineData("/foo{2,1}/")] [InlineData("/(abc\\Kdef)/")] [InlineData("/((a|b|)/")] public void Create_InvalidRegexDomain_ThrowsFormatException(string regexDomainString) { var act = () => new RegexDomain(regexDomainString); act.Should().Throw(); } [Fact] [SuppressMessage("ReSharper", "EqualExpressionComparison")] public void Equals_Works() { (new RegexDomain("/Unit\\.Test/") == new RegexDomain("/Unit\\.Test/")).Should().BeTrue(); (new RegexDomain("/Unit\\.Test/") == new RegexDomain("/^Unit\\.Test$/")).Should().BeFalse(); } [Fact] public void GetMatchRelevance_WhenTimeoutOccurs_ShouldReturnNoMatch_WithoutThrowingException() { var rule = new RegexDomain("/(a|aa)+$/", regexTimeout: TimeSpan.FromTicks(1)); var domain = new Domain(new string('a', 10)); var action = () => rule.GetMatchRelevance(domain); action.Should().NotThrow(); } } ================================================ FILE: source/NsDepCop.Test/Interface/Config/WildcardDomainTests.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Codartis.NsDepCop.Config; using FluentAssertions; using Xunit; namespace Codartis.NsDepCop.Test.Interface.Config { public class WildcardDomainTests { [Theory] [InlineData("*")] [InlineData("A.*")] [InlineData("*.A.*")] [InlineData("*.A.?")] [InlineData("*.A.?.B")] [InlineData("A.?.?.B")] [InlineData("A.*.?.?.B")] public void Create_Works(string wildcardDomainString) { new WildcardDomain(wildcardDomainString).ToString().Should().Be(wildcardDomainString); } [Fact] public void Create_WithNull_ThrowsArgumentNullException() { Assert.Throws(() => new WildcardDomain(null)); } [Theory] [InlineData("A")] [InlineData("A.B")] public void Create_NotAWildcardDomain_ThrowsFormatException(string wildcardDomainString) { Assert.Throws(() => new WildcardDomain(wildcardDomainString)); } [Theory] [InlineData("")] [InlineData(".")] [InlineData("A..")] [InlineData("A.B.")] [InlineData("A.B?.C")] [InlineData("A*.B")] [InlineData("A.**.B")] [InlineData("A.*.*.B")] [InlineData(".*.B")] public void Create_InvalidWildcardDomain_ThrowsFormatException(string wildcardDomainString) { Assert.Throws(() => new WildcardDomain(wildcardDomainString)); } [Fact] [SuppressMessage("ReSharper", "EqualExpressionComparison")] public void Equals_Works() { (new WildcardDomain("A.*") == new WildcardDomain("A.*")).Should().BeTrue(); (new WildcardDomain("A.*") == new WildcardDomain("B.*")).Should().BeFalse(); } [Fact] [SuppressMessage("ReSharper", "EqualExpressionComparison")] public void AnyDomain_IsEqualToOtherInstanceOfAnyDomain() { (new WildcardDomain("*") == new WildcardDomain("*")).Should().BeTrue(); } } } ================================================ FILE: source/NsDepCop.Test/NsDepCop.Test.csproj ================================================  net8.0 Codartis.NsDepCop.Test false false true latest all runtime; build; native; contentfiles; analyzers; buildtransitive PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest Designer PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest PreserveNewest ================================================ FILE: source/NsDepCop.Test/Properties/AssemblyInfo.cs ================================================ using System.Reflection; [assembly: AssemblyTitle("NsDepCop.Analyzer.Test")] [assembly: AssemblyDescription("Unit tests for NsDepCop.Analyzer")] ================================================ FILE: source/NsDepCop.Test/RoslynAnalyzer/AnalyzerProviderTests.cs ================================================ using Codartis.NsDepCop.Analysis; using Codartis.NsDepCop.Config; using Codartis.NsDepCop.RoslynAnalyzer; using Moq; using Xunit; namespace Codartis.NsDepCop.Test.RoslynAnalyzer { public class AnalyzerProviderTests { private readonly Mock _dependencyAnalyzerFactoryMock; private readonly Mock _assemblyDependencyAnalyzerFactoryMock; private readonly Mock _configProviderFactoryMock; private readonly Mock _typeDependencyEnumeratorMock; public AnalyzerProviderTests() { _dependencyAnalyzerFactoryMock = new Mock(); _assemblyDependencyAnalyzerFactoryMock = new Mock(); _configProviderFactoryMock = new Mock(); _typeDependencyEnumeratorMock = new Mock(); } [Fact] public void GetDependencyAnalyzer_RetrievedOnce_CallsFactory() { const string filePath = "myFilePath"; var analyzerProvider = CreateAnalyzerProvider(); analyzerProvider.GetDependencyAnalyzer(filePath); VerifyDependencyAnalyzerFactoryCall(Times.Once()); } [Fact] public void GetDependencyAnalyzer_RetrievedTwice_CallsFactoryThenAnalyzerRefresh() { const string filePath = "myFilePath"; var analyzerProvider = CreateAnalyzerProvider(); var analyzerMock = new Mock(); SetUpFactoryCall(analyzerMock.Object); analyzerProvider.GetDependencyAnalyzer(filePath); VerifyDependencyAnalyzerFactoryCall(Times.Once()); analyzerMock.Verify(i => i.RefreshConfig(), Times.Never); analyzerProvider.GetDependencyAnalyzer(filePath); VerifyDependencyAnalyzerFactoryCall(Times.Once()); analyzerMock.Verify(i => i.RefreshConfig(), Times.Once); } [Fact] public void GetAssemblyDependencyAnalyzer_RetrievedTwice_CallsFactoryThenAnalyzerRefresh() { const string filePath = "myFilePath"; var analyzerProvider = CreateAnalyzerProvider(); var analyzerMock = new Mock(); SetUpFactoryCall(analyzerMock.Object); analyzerProvider.GetAssemblyDependencyAnalyzer(filePath); VerifyAssemblyDependencyAnalyzerFactoryCall(Times.Once()); analyzerMock.Verify(i => i.RefreshConfig(), Times.Never); analyzerProvider.GetAssemblyDependencyAnalyzer(filePath); VerifyAssemblyDependencyAnalyzerFactoryCall(Times.Once()); analyzerMock.Verify(i => i.RefreshConfig(), Times.Once); } private void SetUpFactoryCall(IDependencyAnalyzer analyzer) { _dependencyAnalyzerFactoryMock .Setup(i => i.Create(It.IsAny(), _typeDependencyEnumeratorMock.Object)) .Returns(analyzer); } private void VerifyDependencyAnalyzerFactoryCall(Times times) { _dependencyAnalyzerFactoryMock .Verify(i => i.Create(It.IsAny(), _typeDependencyEnumeratorMock.Object), times); } private void VerifyAssemblyDependencyAnalyzerFactoryCall(Times times) { _assemblyDependencyAnalyzerFactoryMock.Verify(i => i.Create(It.IsAny()), times); } private void SetUpFactoryCall(IAssemblyDependencyAnalyzer analyzer) { _assemblyDependencyAnalyzerFactoryMock .Setup(i => i.Create(It.IsAny())) .Returns(analyzer); } private IAnalyzerProvider CreateAnalyzerProvider() { return new AnalyzerProvider( _dependencyAnalyzerFactoryMock.Object, _assemblyDependencyAnalyzerFactoryMock.Object, _configProviderFactoryMock.Object, _typeDependencyEnumeratorMock.Object ); } } } ================================================ FILE: source/NsDepCop.Vsix/NsDepCop.Vsix.csproj ================================================  net472 false false false false false false Roslyn Program $(DevEnvDir)devenv.exe c:\temp\simpleapp\simpleapp.sln /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) /log c:\temp\activitylog.xml ================================================ FILE: source/NsDepCop.Vsix/readme.txt ================================================ If the VSIX project launches the experimental Visual Studio instance successfully, but the breakpoints are not hit, then turn off "Run code analysis in separate process" in the Options of the experimental VS instance. See: https://github.com/dotnet/roslyn-sdk/issues/515#issuecomment-1223731899 Also, if the project that is used for testing, has a package reference to the NsDepCop nuget, it might be necessary to remove it, to force the analyzer to run from the VSIX package. ================================================ FILE: source/NsDepCop.Vsix/source.extension.vsixmanifest ================================================ NsDepCop - Code dependency checker tool for C# Static code analysis tool for enforcing namespace-based type dependency rules in C# projects. ================================================ FILE: source/NsDepCop.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33403.182 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AB1A4FF5-D372-416A-9986-6C3548F78B3B}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig ..\appveyor.yml = ..\appveyor.yml ..\CHANGELOG.md = ..\CHANGELOG.md config.nsdepcop = config.nsdepcop ..\Contribute.md = ..\Contribute.md Directory.Build.targets = Directory.Build.targets ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Include", "Include", "{71A874EB-DA8D-4F27-8D68-335B7EF77671}" ProjectSection(SolutionItems) = preProject include\CommonAssemblyInfo.cs = include\CommonAssemblyInfo.cs include\VersionInfo.cs = include\VersionInfo.cs EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{EFA91D33-558D-4191-8786-8A6A8CFA7CD3}" ProjectSection(SolutionItems) = preProject ..\doc\DependencyControl.md = ..\doc\DependencyControl.md ..\doc\Diagnostics.md = ..\doc\Diagnostics.md ..\doc\Help.md = ..\doc\Help.md ..\doc\Troubleshooting.md = ..\doc\Troubleshooting.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NsDepCop.Analyzer", "NsDepCop.Analyzer\NsDepCop.Analyzer.csproj", "{BC26589E-79B0-4F55-B64D-DB11534D3476}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NsDepCop.Test", "NsDepCop.Test\NsDepCop.Test.csproj", "{96044EE5-D220-4CED-A687-35AED0115A35}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NsDepCop.NuGet", "NsDepCop.NuGet\NsDepCop.NuGet.csproj", "{0FB37BF6-7E56-4974-829D-917AB6207257}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NsDepCop.SourceTest", "NsDepCop.SourceTest\NsDepCop.SourceTest.csproj", "{F6554236-1D67-4343-816A-F145F7750526}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NsDepCop.Vsix", "NsDepCop.Vsix\NsDepCop.Vsix.csproj", "{17A44684-B673-42F8-982F-01ABEA3D5BBE}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NsDepCop.ConfigSchema", "NsDepCop.ConfigSchema", "{44F96B9A-C418-43A2-8138-CC64E3FEAF08}" ProjectSection(SolutionItems) = preProject NsDepCop.ConfigSchema\NsDepCopCatalog.xml = NsDepCop.ConfigSchema\NsDepCopCatalog.xml NsDepCop.ConfigSchema\NsDepCopConfig.xsd = NsDepCop.ConfigSchema\NsDepCopConfig.xsd EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NsDepCop.Benchmarks", "NsDepCop.Benchmarks\NsDepCop.Benchmarks.csproj", "{3E9D9807-52F9-4853-A4B5-6AC30AD88D25}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|Mixed Platforms = Debug|Mixed Platforms Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|Mixed Platforms = Release|Mixed Platforms Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {BC26589E-79B0-4F55-B64D-DB11534D3476}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Debug|x86.ActiveCfg = Debug|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Release|Any CPU.Build.0 = Release|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Release|Mixed Platforms.Build.0 = Release|Any CPU {BC26589E-79B0-4F55-B64D-DB11534D3476}.Release|x86.ActiveCfg = Release|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Debug|Any CPU.Build.0 = Debug|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Debug|x86.ActiveCfg = Debug|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Release|Any CPU.ActiveCfg = Release|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Release|Any CPU.Build.0 = Release|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Release|Mixed Platforms.Build.0 = Release|Any CPU {96044EE5-D220-4CED-A687-35AED0115A35}.Release|x86.ActiveCfg = Release|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Debug|x86.ActiveCfg = Debug|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Debug|x86.Build.0 = Debug|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Release|Any CPU.Build.0 = Release|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Release|Mixed Platforms.Build.0 = Release|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Release|x86.ActiveCfg = Release|Any CPU {0FB37BF6-7E56-4974-829D-917AB6207257}.Release|x86.Build.0 = Release|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Debug|Any CPU.Build.0 = Debug|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Debug|x86.ActiveCfg = Debug|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Debug|x86.Build.0 = Debug|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Release|Any CPU.ActiveCfg = Release|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Release|Any CPU.Build.0 = Release|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Release|x86.ActiveCfg = Release|Any CPU {F6554236-1D67-4343-816A-F145F7750526}.Release|x86.Build.0 = Release|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Debug|Any CPU.Build.0 = Debug|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Debug|x86.ActiveCfg = Debug|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Debug|x86.Build.0 = Debug|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Release|Any CPU.ActiveCfg = Release|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Release|Any CPU.Build.0 = Release|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Release|Mixed Platforms.Build.0 = Release|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Release|x86.ActiveCfg = Release|Any CPU {17A44684-B673-42F8-982F-01ABEA3D5BBE}.Release|x86.Build.0 = Release|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Debug|x86.ActiveCfg = Debug|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Debug|x86.Build.0 = Debug|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Release|Any CPU.Build.0 = Release|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Release|Mixed Platforms.Build.0 = Release|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Release|x86.ActiveCfg = Release|Any CPU {3E9D9807-52F9-4853-A4B5-6AC30AD88D25}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EBB04CE1-A8C8-417E-95FF-EF622AD8EAFE} EndGlobalSection EndGlobal ================================================ FILE: source/NsDepCop.sln.DotSettings ================================================  NEVER 160 True True True True True True True True True True True ================================================ FILE: source/config.nsdepcop ================================================  ================================================ FILE: source/include/CommonAssemblyInfo.cs ================================================ using System.Reflection; [assembly: AssemblyCompany("Codartis")] [assembly: AssemblyProduct("NsDepCop")] [assembly: AssemblyCopyright("Copyright © 2013-2025 Ferenc Vizkeleti")]