Skip to content

olstakh/ReferenceProtector

Repository files navigation

ReferenceProtector

NuGet Version GitHub license

Protect from unwanted dependencies in your repository. As repo gets bigger - there's often a need to secure from bad dependencies between projects, or to unwanted packages.

Rules

The following warnings are generated by this package:

Id Description
RP0001 Provide DependencyRulesFile property to specify valid dependency rules file.
RP0002 Make sure the dependency rules file is in the correct json format
RP0003 No dependency rules matched the current project
RP0004 Project reference 'x' ==> 'y' violates dependency rule or one of its exceptions
RP0005 Package reference 'x' ==> 'y' violates dependency rule or one of its exceptions

How to use

Add a package reference to the ReferenceProtector package in your projects, or as a common package reference in the repo's Directory.Packages.props.

If you're using Central Package Management, you can use it as a GlobalPackageReference in your Directory.Packages.props to apply it to the entire repo.

  <ItemGroup>
    <GlobalPackageReference Include="ReferenceProtector" Version="{LatestVersion}" />
  </ItemGroup>

Create a .json file somewhere in your repository that will contain dependency rules, and provide full path to this file in MSBuild property for specific project, or for all projects. For example, placing DependencyRules.json file in the root of the repo, you can update Directpry.Build.props (assuming it's also in the root) with <DependencyRulesFile>$(MSBuildThisFileDirectory)DependencyRules.json</DependencyRulesFile>. You can also provide it via command line like /p:DependencyRulesFile=...

Schema of the rules file is as follows:

{
    "ProjectDependencies": [
        {
            "From": "",
            "To": "",
            "Policy": "",
            "LinkType": "",
            "Description": "",
            "Exceptions": [
                {
                    "From": "",
                    "To": "",
                    "Justification": ""
                },
                // ...
            ]
        },
        // ...
    ]
    "PackageDependencies": [
        {
            "From": "",
            "To": "",
            "Policy": "",
            // "LinkType": "", Only direct package references are analyzed, so LinkType is not needed in this section
            "Description": "",
            "Exceptions": [
                {
                    "From": "",
                    "To": "",
                    "Justification": ""
                },
                // ...
            ]
        }, 
        // ...
    ]
}

Top ProjectDependencies object will contain a list of rules to validate against. Each rule has the following schema:

  • From / To - Full path regex for source and target projects to be matched.
  • Policy - enum with values Allowed / Forbidden. Describes whether link between From and To projects should be allowed or forbidden
  • LinkType - enum with values Direct / Transitive / DirectOrTransitive. Specifies whether link between From and To projects is expected to be direct, transitive or both
  • Description - human readable explanation for this policy rule. Will be displayed in a build warning if policy is violated (rule RP0004).
  • Exceptions (Optional) - list of exceptions from this policy (can be due to tech debt, or for any other reason).

Top PackageDependences object will have the same format as ProjectDependencies with LinkType omitted, since only direct package references will be considered. Also, Description section will be part of RP0005 warning (as opposed to RP0004)

Matching logic

Each reference between the projects / packages during the build is evaluated against provided list of policies. First each pair of dependencies is evaluated against From and To patterns, based on their full path. For project dependencies - if the match is successful - their link type is evaluated: if current pair has a direct dependency on each other and LinkType value is Direct or DirectOrTransient - the match is successful, otherwise (the dependency is transient) - LinkType should be Transient or DirectOrTransient for the match to be successful. Package dependencies are only viewed as direct references. Then the exceptions are evaluated using the same pattern matching logic with From and To fields. The decision logic is as follows

  • If current Policy value is Forbidden - the rule is considered violated if no exceptions were matched
  • If current Policy value is Allowed - the rule is considered violated if there are any matched exceptions

Violations of the rule will produce RP0004 (for projects) and RP0005 (for packages) warning during build.

Note: in regex matches - * is substituted with .* for proper regex, and $ is added at the end.

Regex.Escape(pattern).Replace("\\*", ".*") + "$";

Examples

Below are few examples of potential rules

Don't allow external dependencies from projects in Infrastructure folder

{
    "description": "Infrastructure referencing application logic is fordbidden",
    "Policy": "forbidden",
    "LinkType": "DirectOrTransient",
    "from": "*\\Infrastructure\\*",
    "to": "*",
    "exceptions": [
        {
            "from": "*\\Infrastructure\\*",
            "to": "*\\Infrastructure\\*",
            "justification": "Infrastructure projects can reference each other"
        },
        {
            "from": "*Tests.csproj",
            "to": "*",
            "justification": "tech debt <work item link>"
        },
        {
            "from": "*",
            "to": "LegacyDependency.csproj",
            "justification": "tech debt <work item link>"
        }
    ]
},

Allow referencing Common projects

    {
      "description": "Referencing Common is ok",
      "Policy": "allowed",
      "LinkType": "DirectOrTransient",
      "from": "*",
      "to": "*\\Common.csproj"
    },

Forbid referencing Newtonsoft.* packages

{
    "PackageDependencies": [
        {
            "description": "Use System.Text.Json, instead of Newtonsoft.Json",
            "Policy": "forbidden",
            "from": "*",
            "to": "Newtonsoft.json",
            "exceptions": [
                // tech debt
            ]
        }
    ]
}

How it works

First - MSBuild task with gather all direct / indirect project references and dump them into a file (typically in obj/Debug/ folder), named _ReferenceProtector_DeclaredReferences.tsv (inspired by ReferenceTrimmer implementation). During the second stage - Roslyn analyzer will read this file and match it against the dependency rules, defined in a file from <DependencyRulesFile> property. Corresponding diagnostics will be produced if violations are found.

How to disable

Easiest way is to set EnableReferenceProtector variable to false (either in command line or in a project file, like <EnableReferenceProtector>false</EnableReferenceProtector>)

About

Protect from unwanted dependencies in your repository

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages