Dependency rules define what kind of dependencies you wish to block or allow in your software. If you want to know why dependency rules are important, you can find more information from this article.
Let's start with a very simple example:
[
[
"DepRule",
"src/"
"-!->",
"test/",
{
"description": "Implementation must not depend on any test code"
}
]
]
This straightforward rule tells that there should be no references from actual implementation to any test code. The other way around is perfectly fine though.
In real-world use your code paths and rule needs might be much more complex. This article helps you understand the full syntax of Softagram dependency rules and construct your own set of rules that you can place in the root of your git repository in the .softagram.json
file.
If you have any issues working with the rules, please ping us in the chat - we are happy to help!
More complex example
A more complex example with aliases, multiple source/target elements and different types of rules would be here:
[
[
"Alias",
"tests",
"/MyProject/MyRepo/tests"
],
[
"Alias",
"middleware",
"/MyProject/MyRepo/src/middleware"
],
[
"Alias",
"apps",
"/MyProject/MyRepo/src/apps"
],
[
"DepRule",
[
"middleware",
"apps"
]
"-!->",
"tests",
{
"description": "Implementation must not depend on any test code"
}
],
[
"DepRule",
"apps",
"<--",
"tests",
{
"description": "Only test code can depend on application code"
}
],
[
"GrepRule",
[
"#include.*test.h",
"#include.*mock.h"
],
"MustBeLocatedAt",
[
"/test/"
],
{
"description": "Test code must reside in the /test folder"
}
],
]
This example defines that 1) production code in middleware and apps folders must not use any code in the tests folder and 2) only test code can have dependencies to apps folder. There is also a 3) GrepRule for defining that certain kinds of test code related imports can be only present in test folder. Source and target paths are simplified by using Aliases so that we avoid repeating the same long paths in multiple rules.
Dependency Rules JSON reference
Example of dependency rule:
["DepRule", "/Project/repo1", "-!->", "/Project/repo2", {"description": "Repo1 should not use repo2!"}]
Every Softagram dependency rule has four mandatory fields:
Dependency Rule type (
DepRule
,RegExpDepRule
, orGrepRule
)Source element path
DependencyOperator
Target element path
and Optional settings, described farther below in this article.
Next we walk through all the fields; Dependency Rule Types, the Source/Target elements, DependencyOperators and lastly Optional settings that are common for all Rule Types.
DEPENDENCY RULE TYPES
Softagram has tree rule types, DepRule is used to detect unwanted dependencies based on the Softagram dependency analysis. RexExpDepRule to use regular Expression to create more complex rules. GrepRule can be used for more complex cases, as it is not based on detected dependencies but pattern matching.
DepRule
DepRule element can be used to set up simple element path based rules. Example of DepRule:
["DepRule", "/Project/repo1", "-!->", "/Project/repo2", {"description": "This rule ensures repo1 doesn't use repo2"}]
The specification of the rule:
["DepRule", SourcePath, DependencyOperator, TargetPath, SettingsMap]
Source and Target paths are defined in section Element Paths
DependencyOperators are defined in section Dependency Operators
SettingsMap have only general settings defined in section Optional Settings
GrepRule
GrepRule is a bit different from the dependency rules. It can be used to perform grep statements and validate software based on the location of the grep matches. GrepRule supports "MustBeLocatedOnlyAt" and "MustNotBeLocatedAt" operators. When need to specify the whitelisting patterns, use MustBeLocatedOnlyAt operator. If blacklisting approach is needed, MustNotBeLocatedAt works for that purpose.
Currently GrepRule is restricted to Java, C#, Python, JavaScript, TypeScript, C/C++, JSON, VBProj, CSProj and Kotlin code files.
For example, the example GrepRule below checks if the test code is located in correctly named directories:
MustNotBeLocatedOnlyAt example
[
"GrepRule",
[ "#include.*gtest.h"],
"MustBeLocatedAt",
["/test/"],
{"description": "Always locate test code in test dir."}
],
With this rule in place, putting #include.*gtest.h
in any file under any other directory than /test/
would trigger a dependency violation that is shown and alerted by Softagram. Thus effectively helping to avoid using test libraries in implementation code.
MustNotBeLocatedOnlyAt example
[
"GrepRule",
[ "ForbiddenKeyword"],
"MustNotBeLocatedOnlyAt",
["src", "another-name-that-matches-to-directory-or-file-name"],
{"description": "Always locate test code in test dir."}
],
Using simply "src" as the location parameter, as above, to make something forbidden in all directories and files that contain src in the name. In case you need to make it forbidden everywhere, use empty list: []. This is good if you want to keep track of illegal words such as statements that configure hard coded secrets into source code.
RegExpDepRule
When you want to define the source or target element as a regex that matches to multiple similar paths, use the RegExpDepRule as a Dependency Type
["RegExpDepRule", UserRegExp, UsedRegExp, DepTypeRegExp, SettingsMap]
For each dependency A -> B, UserRegExp is matched against A and UsedRegExp is matched against B. Also DepTypeRegExp is applied against dep type (e.g. “import”, "inc", "function_ref", "inherits"). Note that DepTypeRegExp can be left as empty string which matches to all dependency types.
Example:
["RegExpDepRule", "/Project/R1/fooX/", "bar", "", {}]
With these dependencies:
/Project/R1/fooX/File.java → /Project/R2/other/File.java
/Project/R1/fooX/File.java → /Project/R2/bar/File.java
/Project/R1/fooX/File.java → /Project/R2/other/File.java
Dependencies 1 and 3 are not matched, but dependency 2 is matched.
Example: #2
["RegExpDepRule", "foo", "bar", "", {}]
This matches with all of the three dependencies above.
SettingsMap have general settings defined in section Optional Settings and ReqExprDepRule specific settings.
ignore_patterns_if_both_equal: Regular expression that will be matched to both user path and used path and if both matches, the dependency will be ignored.
ignore_if_originating_from_src_to_siblings: By setting this as "true", it places additional constraint for the dependency matcher to ignore all dependencies where the user path contains src directory and the used path is located under the same src directory or in one of its siblings.
grouping: in case of capturing groups are used in regular expression, it is possible to assign names for the groups, e.g. here the first group of the left (user) expression is named as pkg1, and the first group of the right (used) expression gets name pkg2.
"grouping": {
"left": [
"pkg1"
],
"right": [
"pkg2"
]
},
conditions: Currently it supports == and != operators. It allows specifying constraints based on the state of the named variable parts. Below it requires pkg1 to have in-equal values of test and src.
"conditions": [
[
"pkg1",
"!=",
"'test'"
],
[
"pkg1",
"!=",
"'src'"
]
],
ELEMENT PATHS
Source and target element paths follow the same syntax. Element path is a slash-delimited path of the element. We talk about element paths instead of file or folder paths, because in Softagram also classes and functions have paths. E.g. /ProjectX/main-repo/src/MyClass.java/MyClass/myFunction
You can use direct element path, multiple paths or aliases in the rule definitions.
Example of a short full path: /ProjectX/main-repo/src/file.h. Full path is always starting with a slash and the project name. Project name is the name of Softagram project!
Three dots (...) in the beginning of the path looks the partial path everywhere, as in path ".../src/file.h".
It is also possible to specify some directory as a named variable part using curly braces: "/Project/repo1/{some-directory}/src"
If both source and target path refer to identically named variable part, the value of that variable part are required to be identical.(Update?)
Multiple source or target paths
To apply the same rule to multiple source or target paths, you can use a list of element paths instead of a single path, example below with "DepRule" and one source and 2 target paths:
["DepRule", "/myproj/source/path", "-!->", ["/myproj/target1/path", "/myproj/target2/path"]]
Aliases
To avoid repeating long element paths again and again, you can also define aliases for your most common paths that are used in multiple dependency rules.
["Alias", "MyFile", "/MyProject/my-repo/path/to/my/file"]
After defining an alias like this, you can simply use "MyFile" as the source or target path instead of the full element path.
DEPENDENCY OPERATORS
DependencyOperators between the source and target paths, e.g. "-!->", specifies the matching type.
These are the operators:
-!-> "Cannot use". Saying A -!-> B means there should not be any dependencies from A to B, and the rule matcher will yield all the violations. When using this operator, B can also be a list of Used expressions instead of a single item.
<-!- "Cannot be used by". Sometimes it is more convenient to say A should not be used by B. That is possible by A <-!- B. When using this operator, B can also be a list of Used expressions instead of a single item.
<-!-> "No relationship between". In case there should not be any dependencies between A and B, use two directional operator.
--> "Can only use". If you know what are the allowed outbound dependencies of some element, this "can only use" operator lets you to specify this so all the other dependencies of A will be matched. For example, A --> B means A can use B but all the other dependencies, e.g. C, are reported as violations. When using this operator, B can also be a list of Used expressions instead of a single item, e.g. A --> [B, C, D],
<-- "Can only be used by". This is a mirror of the above, saying that what are the allowed users of some element. For example, A <-- B means that any usage of A will be reported as a violation, except when it's used from B. As previously, a list of expressions can be used instead of a single string value.
NOTE: in GrepRule, only "MustBeLocatedAt" operand can be used
OPTIONAL SETTINGS
The following general optional keys, also called a SettingsMap, can be set to any rule definition:
description
Rule severity
ignore_patterns_if_both_equal: Regular expression that will be matched to both user path and used path and if both matches, the dependency will be ignored. Allows fine-tuning for more complex cases.
ignore_if_used_matches: Regular expression that will be matched to used path, and if matches, the dependency will be ignored. Allows fine-tuning for more complex cases.
conditions: There are two ways to use this: In case you use named variable parts in both user and used paths expressions, setting "conditions": [[ "allow_same" ]] ensures that the match also happens when those variable parts have the identical value.
Description
We recommend always adding a description that tells why the rule exists, as it will be shown up in code-review when someone breaks the rule. You add the description with following format inside the SettingsMap:
{"description": "Your very descriptive note for the violation"}
Rule Severity
DepRule and GrepRule may have severity in the settings map. It defines the directives that can adjust the severity of the rule match. These directives are applied so that for any dependency, the last matching directive is effective. Severity directive has four parts:
severity (e.g. warning)
user path: e.g. /Project/repo
used path: e.g. /Project/repo
dependency type: e.g. import
Example: severity directives for an example rule:
"severity": [
["warning", "", "", ""], // The first directive...
["ignored", "/Project/repo/dir1/file1.ext", "", "deptype1"],
["ignored", "/Project/repo/dir2/file1.ext", "", "deptype1"],
["ignored", "", "/Some/Component/That/can/be/used/everywhere", ""],
],
In this case the first directive sets all matches of the rule to be warning while some exception cases are set to be ignored. By switching the order of the directives it is possible to turn around how the matching works. In this case opposite order would indicate all matches to be warning, as there is empty string used as the element path. The empty string indicates to match all elements.
Rule severity can be any of critical (default, displayed in red), warning (displayed in yellow), info (displayed in blue), or ignored.
HOW TO ACTIVATE DEPENDENCY RULES
Softagram offers two ways to add new dependency rules for your project:
add the rules into a special
.softagram.json
file in your repository's rootadd the rules via the Softagram web interface's settings menu:
We recommend using the .softagram.json
approach, as this allows you to keep your dependency rules in version control, making it easier to maintain. However, when you want to quickly test a new rule without making changes to your git repository, you can use the settings in Softagram web UI. If you define any rules in the Softagram web UI, any rules defined in .softagram.json
are dismissed. They'll become active again when you empty up the dependency rules in web UI.