henforcer
is a Haskell
enforcer of checks to help keep your forest of Haskell modules organized
cleanly.
henforcer
has not yet been released to hackage, so you should install it from source either by
cloning and building the repo, or adding a extra dep to your stack.yaml
like below and running
stack install henforcer
extra-deps:
- git: https://github.com/flipstone/henforcer
commit: <SHA of the latest master commit>
_Note: henforcer
requires a handful of packages that are not on stackage as of lts-22.7. The following is likely to needed in the extra-deps
section of your stack.yaml
# henforcer needs pollock and tomland
- pollock-0.1.0.0
- tomland-1.3.3.2
# tomland needs validation-selective
Once the henforcer
command is installed you'll need to initialize henforcer for your project. cd
to your product directory and run the following command:
henforcer --init
or
stack exec henforcer --init
This will create a default henforcer
configuration file at henforcer.toml
in the root of your
project.
henforcer
is a GHC plugin, thus it is executed during compliation. To enable the plugin during
compliation with cabal
or stack
, add henforcer
as a dependency in your cabal file or
package.yaml as applicable and add -fplugin Henforcer
to ghc-flags
. Specifying plugin options is
done with ghc-flags
as well. Currently only supported is the path to the configuration, which if
it is at foo/bar/henforcer.toml
this would be as
"-fplugin-opt=Henforcer:-cfoo/bar/henforcer.toml"
.
henforcer
uses TOML
files for configuration. Note that the default henforcer
configuration
does not enforce any particular rules, so running henforcer
immediately after installation and
initialization will not report errors.
Configuration concepts and options are described below. Also the examples/henforcer.toml
file
shows a variety of usage possibilities.
henforcer
uses "module trees" as part of its configuration. A "module tree" refers to a root
module (e.g. Data.Text
) and all the modules are prefixed by it (e.g. Data.Text.Encoding
and
Data.Text.Lazy
). For modules in your project this almost always means a src/Foo/MyModule.hs
file
and any .hs
files contained inside the src/Foo/MyModule
directory.
Required: Yes
The forAnyModule
key is a TOML table containing all of the checks that apply when compiling any
given module. You can think of these as "global" but they do not apply to multiple modules at
once.
fieldName | type | required | description |
---|---|---|---|
allowedAliasUniqueness |
AllowedAliasUniqueness | no | Specifies either that all aliases in the module being compiled are unique except for some, or that a given set of aliases is unique but others may be duplicated. |
allowedOpenUnaliasedImports |
non-negative int | no | Specifies how many imports are allowed to be done without using the qualified keyword, or using an alias |
allowedQualifications |
array of AllowedQualification | no | Represents how certain modules should be imported. This can be thought of as a map of module name to a list of ways that module may be imported. |
encapsulatedTrees |
array of string | yes | Lets you declare that the root of a module tree is effectively a public interface that any modules outside the tree should be using. henforcer will report an error if any module outside the tree attempts to import a module from inside the encapsulated tree. |
maximumExportsPlusHeaderUndocumented |
non-negative integer | no | Mmaximum number of exported items, along with the module header, from a module that may be missing Haddock documentation. |
minimumExportsPlusHeaderDocumented |
non-negative integer | no | Minimum number of exported items, along with the module header, from a module that must have Haddock documentation. |
maximumExportsWithoutSince |
non-negative integer | no | Maximum number of exported items from a module that can be lacking the @since annotation in their Haddock. |
minimumExportsWithSince |
non-negative integer | no | Minimum number of exported items from a module that must have in their Haddock the @since annotation. |
moduleHeaderCopyrightMustExistNonEmpty |
boolean | no | If the Haddock module header field of Copyright must be populated. |
moduleHeaderDescriptionMustExistNonEmpty |
boolean | no | If the Haddock module header field of Description must be populated. |
moduleHeaderLicenseMustExistNonEmpty |
boolean | no | If the Haddock module header field of License must be populated. |
moduleHeaderMaintainerMustExistNonEmpty |
boolean | no | If the Haddock module header field of Maintainer must be populated. |
treeDependencies |
array of TreeDependency | no | Declares that one module tree depends on other trees. Declaring such a dependency tells henforcer that you don't want the dependency targets to import anything from the dependent tree, which would cause a backwards dependency rendering the two module trees logically inseparable. |
Henforcer allows for certain rules to be overriden on a module by module basis. When provided, the most specific rule will be applied.
fieldName | type | required | description |
---|---|---|---|
module |
string | yes | module is a string of the module name the rules in this table will apply to. |
allowedAliasUniqueness |
AllowedAliasUniqueness | no | Specifies either that all aliases in the module being compiled are unique except for some, or that a given set of aliases is unique but others may be duplicated. |
allowedOpenUnaliasedImports |
non-negative int | no | Specifies how many imports are allowed to be done without using the qualified keyword, or using an alias |
allowedQualifications |
array of AllowedQualification | no | Represents how certain modules should be imported. This can be thought of as a map of module name to a list of ways that module may be imported. |
maximumExportsPlusHeaderUndocumented |
non-negative integer | no | Mmaximum number of exported items, along with the module header, from a module that may be missing Haddock documentation. |
minimumExportsPlusHeaderDocumented |
non-negative integer | no | Minimum number of exported items, along with the module header, from a module that must have Haddock documentation. |
maximumExportsWithoutSince |
non-negative integer | no | Maximum number of exported items from a module that can be lacking the @since annotation in their Haddock. |
minimumExportsWithSince |
non-negative integer | no | Minimum number of exported items from a module that must have in their Haddock the @since annotation. |
moduleHeaderCopyrightMustExistNonEmpty |
boolean | no | If the Haddock module header field of Copyright must be populated. |
moduleHeaderDescriptionMustExistNonEmpty |
boolean | no | If the Haddock module header field of Description must be populated. |
moduleHeaderLicenseMustExistNonEmpty |
boolean | no | If the Haddock module header field of License must be populated. |
moduleHeaderMaintainerMustExistNonEmpty |
boolean | no | If the Haddock module header field of Maintainer must be populated. |
rulesToIgnore |
RulesToIgnore | no | Specifies what, if any, rules should be ignored for the given module. |
Henforcer supports a limited form of using patterns to match rules against multiple modules, but not any module in a more concise way.
forPatternModules
is an array of TOML tables. Effectively this is a map keyed by the pattern
field.
Important items to note:
- When determining which version of a rule to pick the definition in
forSpecifiedModules
is most preferred, followed byforPatternModules
and finallyforAnyModule
. - If there are overlapping
pattern
keys inforPatternModules
the first specified in the TOML will be chosen. - Patterns use
*
and**
.*
can be used to match up to the module seperator.
, where**
matches across the.
seperator.
fieldName | type | required | description |
---|---|---|---|
pattern |
string with wildcard | yes | module is a string, with wildcard support, of the module name the rules described here will apply to. |
allowedAliasUniqueness |
AllowedAliasUniqueness | no | Specifies either that all aliases in the module being compiled are unique except for some, or that a given set of aliases is unique but others may be duplicated. |
allowedOpenUnaliasedImports |
non-negative int | no | Specifies how many imports are allowed to be done without using the qualified keyword, or using an alias |
allowedQualifications |
array of AllowedQualification | no | Represents how certain modules should be imported. This can be thought of as a map of module name to a list of ways that module may be imported. |
maximumExportsPlusHeaderUndocumented |
non-negative integer | no | Mmaximum number of exported items, along with the module header, from a module that may be missing Haddock documentation. |
minimumExportsPlusHeaderDocumented |
non-negative integer | no | Minimum number of exported items, along with the module header, from a module that must have Haddock documentation. |
maximumExportsWithoutSince |
non-negative integer | no | Maximum number of exported items from a module that can be lacking the @since annotation in their Haddock. |
minimumExportsWithSince |
non-negative integer | no | Minimum number of exported items from a module that must have in their Haddock the @since annotation. |
moduleHeaderCopyrightMustExistNonEmpty |
boolean | no | If the Haddock module header field of Copyright must be populated. |
moduleHeaderDescriptionMustExistNonEmpty |
boolean | no | If the Haddock module header field of Description must be populated. |
moduleHeaderLicenseMustExistNonEmpty |
boolean | no | If the Haddock module header field of License must be populated. |
moduleHeaderMaintainerMustExistNonEmpty |
boolean | no | If the Haddock module header field of Maintainer must be populated. |
rulesToIgnore |
RulesToIgnore | no | Specifies what, if any, rules should be ignored for the given module. |
Below are the reused definitions between some combination of the forAnyModule
, forSpecifiedModules
and forPatternModules
rules.
This is allowed to take two forms that are both TOML tables.
This form states that all aliases in a module must be unique with an allow list for those aliases that may be repeated.
fieldName | type | required | description |
---|---|---|---|
allAliasesUniqueExcept |
array of string | yes | Aliases that are allowed to be repeated. |
note |
string | no | User defined message to be displayed with errors for additional context. |
This form states that aliases in a module may repeat with a block list for those aliases that must be unique.
fieldName | type | required | description |
---|---|---|---|
uniqueAliases |
array of string | yes | Aliases that must be unique. |
note |
string | no | User defined message to be displayed with errors for additional context. |
fieldName | type | required | description |
---|---|---|---|
module |
string | yes | module is a string of the module name. |
importScheme |
array of ImportScheme | yes | The list of specifications for each way that the given module may imported. |
fieldName | type | required | description |
---|---|---|---|
qualified | Qualified | yes | Description of ways the import can be qualified, or not. |
alias |
string | no | Controls if and what alias can be used as part of an import scheme. This is the part of an import that comes after the as keyword, such as "Foo" in import UnliftIO as Foo . |
safe |
boolean | no | Controls if the import is required to use the safe keyword. Most users are not expected to need this option. |
note |
string | no | User defined message to be displayed with errors for additional context. |
fieldName | type | required | description |
---|---|---|---|
qualifiedPre |
bool | no | Describes if import can be qualified prepositive like import qualified UnliftIO . |
qualifiedPost |
bool | no | Describes if import can be qualified postpositive like import UnliftIO qualified . |
unqualified |
bool | no | Describes if import can be unqualified like import UnliftIO . |
fieldName | type | required | description |
---|---|---|---|
moduleTree |
string | yes | The tree which depends on others. |
dependencies |
array of string | yes | The trees which are depended upon. |
note |
string | no | User defined message to be displayed with errors for additional context. |
This is allowed to take two forms that are both TOML tables.
fieldName | type | required | description |
---|---|---|---|
all |
bool | no | Controls if all rules should be ignored. |
fieldName | type | required | description |
---|---|---|---|
allowedAliasUniqueness |
boolean | no | Controls if the rule should be ignored. |
allowedOpenUnaliasedImports |
boolean | no | Controls if the rule should be ignored. |
allowedQualifications |
boolean | no | Controls if the rule should be ignored. |
encapsulatedTrees |
boolean | no | Controls if the rule should be ignored. |
maximumExportsPlusHeaderUndocumented |
boolean | no | Controls if the rule should be ignored. |
minimumExportsPlusHeaderDocumented |
boolean | no | Controls if the rule should be ignored. |
maximumExportsWithoutSince |
boolean | no | Controls if the rule should be ignored. |
minimumExportsWithSince |
boolean | no | Controls if the rule should be ignored. |
moduleHeaderCopyrightMustExistNonEmpty |
boolean | no | Controls if the rule should be ignored. |
moduleHeaderDescriptionMustExistNonEmpty |
boolean | no | Controls if the rule should be ignored. |
moduleHeaderLicenseMustExistNonEmpty |
boolean | no | Controls if the rule should be ignored. |
moduleHeaderMaintainerMustExistNonEmpty |
boolean | no | Controls if the rule should be ignored. |
treeDependencies |
boolean | no | Controls if the rule should be ignored. |