packages feed

henforcer-1.0.0.0: README.md

# henforcer

`henforcer` is a `Haskell` enforcer of project specific code standards.

## Installation/Execution

`henforcer` is a GHC plugin, thus it is executed during compilation. To enable the plugin during
compilation 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"`.

_Note: `henforcer` requires a handful of packages that are not on stackage as of lts-24.29. The following is likely to needed in the `extra-deps` section of your `stack.yaml`

```
# henforcer needs pollock
- pollock-0.1.0.4
```

## Configuration

`henforcer` uses `TOML` files for configuration. You can use a empty toml file as a beginning configuration. Using such and running `henforcer` will not report errors.

Configuration concepts and options are described below. Also the `examples/henforcer.toml` file
shows a variety of usage possibilities.

### Concepts

#### Module Trees

`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.

## Configuration reference

### forAnyModule

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       | Maximum 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. |

### forSpecifiedModules

Henforcer allows for certain rules to be overridden 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       | Maximum 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.                                                                                             |

### forPatternModules

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 by `forPatternModules` and finally `forAnyModule`.
  - If there are overlapping `pattern` keys in `forPatternModules` the first specified in the TOML will be chosen.
  - Patterns use `*` and `**`. `*` can be used to match up to the module separator `.`, where `**`
    matches across the `.` separator.

| 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       | Maximum 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.                                                                                             |

### Shared types

Below are the reused definitions between some combination of the `forAnyModule`, `forSpecifiedModules` and `forPatternModules` rules.

#### AllowedAliasUniqueness

This is allowed to take two forms that are both TOML tables.

##### First Form

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. |

##### Second Form

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. |

#### AllowedQualification

| 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. |

#### ImportScheme

| 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`. |
| `packageQualified` | string    | no       | Controls if and what package qualifier should be used. Most users are not expected to need this option.                                                                         |
| `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.                                                                                                        |

#### Qualified

| fieldName       | type    | required | description                                                                         |
|-----------------|---------|----------|-------------------------------------------------------------------------------------|
| `qualifiedPre`  | boolean | no       | Describes if import can be qualified prepositive like `import qualified UnliftIO`.  |
| `qualifiedPost` | boolean | no       | Describes if import can be qualified postpositive like `import UnliftIO qualified`. |
| `unqualified`   | boolean | no       | Describes if import can be unqualified like `import UnliftIO`.                      |

#### TreeDependency

| 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. |

#### RulesToIgnore

This is allowed to take two forms that are both TOML tables.

##### First form

| fieldName | type    | required | description                                |
|-----------|---------|----------|--------------------------------------------|
| `all`     | boolean | no       | Controls if *all* rules should be ignored. |

##### Second form

| 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. |