Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/thekashey/eslint-plugin-relations
Controls relationships between folders and packages 👩💻🤝👮🏻♂️
https://github.com/thekashey/eslint-plugin-relations
eslint monorepo
Last synced: 3 days ago
JSON representation
Controls relationships between folders and packages 👩💻🤝👮🏻♂️
- Host: GitHub
- URL: https://github.com/thekashey/eslint-plugin-relations
- Owner: theKashey
- License: mit
- Created: 2021-07-10T08:20:59.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2022-12-29T02:03:59.000Z (almost 2 years ago)
- Last Synced: 2024-11-08T19:04:43.039Z (5 days ago)
- Topics: eslint, monorepo
- Language: TypeScript
- Homepage:
- Size: 396 KB
- Stars: 35
- Watchers: 3
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# eslint-plugin-relations 👩💻🤝👮🏻♂️
Controls relationships between folders and packages in a monorepo environment.
# Provided rules
- [relations/correct-imports](#correct) - to autocorrect imports to only allowed paths.
- [relations/restrict](#restrictions) - to establish _controlled relationships_ between different places of your
application# Installation
- add the package
```bash
yarn add eslint-plugin-relations
```- add the plugin to eslint config
```js
// eslintrc.js
module.exports = {
plugins: [
'relations',
// other plugins
],
};
```- configure rules. **Rules are not working without configuration**. There is no _default_ preset possible.
# Correct
## Why
Because an `autoimport` feature your IDE might provide is not always working. It might result with a:
- relative import, not using package name as it should
- access "src" folder, or anything else it should not`correct` rule will restrict all imports to the explicitly defined ones, autocorrecting (trimming) anything else
## Configuration
### inside `eslintrc.js`
```js
module.exports = {
plugins: ['relations'],
rules: {
//...
'relations/correct-imports': [
'error',
{
// if you use another tool to derive package->path mapping for typescript
tsconfig: 'path-to-your-config',
// the one with `"compilerOptions": { "paths": [...] }// OR
// explicit mapping to use
pathMapping: {
packageName: 'path',
},// controls "suggestion" over "autofix" behavior
// you want this to be 'false' during development and 'true' in precommit
autofix: false,
},
],
},
};
```# Restrictions
## Why
Because of [Rock paper scissors](https://en.wikipedia.org/wiki/Rock_paper_scissors)
, [Law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter)
, [Abstraction Layers](https://en.wikipedia.org/wiki/Abstraction_layer)
and [Hexagonal Architecture](https://en.wikipedia.org/wiki/Multitier_architecture).- Some functionality should not know about some other functionality.
- Some packages should not have access to some other packages.
- Some files should not have access to some other files:Examples:
- tests can use everything, nothing can use test. (tests are not a part of your application, your application is a part
of your tests)
- everything can use core platform packages, core platform packages can use only other platform packages (you use
platform, not the other way around)
- pages can use components, components cannot use pages (the same as above)This creates a controllable _unidirectional flow_ and let you establish sound relationships between different domains.
## Prerequisites
Your packages and/or code has to be separated in zones/buckets you will be able to configure relationships in between.
Having all packages in one folder will not work.A good example of such separation can be found at [Lerna](https://github.com/lerna/lerna):
- commands
- core
- helpers
- utilsIt can be already a good idea to restrict core usage(nobody can), as well as put a boundary around helpers and utils -
they should not use commands.## Configuration
Can be configured in two ways:
### via `eslint.rc`
```js
module.exports = {
plugins: ['relations'],
rules: {
//...
'relations/restrict': [
'error',
{
rules: [
// platform cannot use packages
{
// absolute folder
from: 'platform',
// absolute folder
to: 'packages',
type: 'restricted',
message: "Let's keep them separated",
},
// one page cannot use another
{
// glob support!
// note: 'pages/a' can assess 'pages/a', but not 'pages/b'
to: 'pages/*',
from: '{app,packages}/*.{js,ts}', // note only [ and { "braces" are supported
type: 'restricted',
message: 'pages are isolated',
},
// "allow" rules should precede "restrict" ones
{
// custom RegExp
from: /__tests__/,
to: /__tests__/,
// allow tests to access tests
type: 'allowed',
message: 'do not import from tests',
},
{
// anywhere
from: '*',
//relative folder
to: /__tests__/,
type: 'restricted',
message: 'do not import from tests',
},
],
},
],
},
};
```## via `ruleGenerator`
A custom function to return rules to be used between locationFrom and locationTo
```js
// eslintrc.js
module.exports = {
plugins: ['relations'],
rules: {
//...
'relations/restrict': [
'error',
{
ruleGenerator: (fromFile, toFile) => [rule1, rule2],
},
],
},
};
```Paths(`from` and `to`) from rule generator are expected to be absolute (or regexp). You might need an exported helper to
handle this```ts
import { adoptLocation } from 'eslint-plugin-relations';const rule = {
to: adoptLocation(path /* or glob*/, cwd),
};
```### via `.relations` files
> this is a recommended way
One can put `.relations` (js,ts,json) files with the rule definitions at **various places** to have "per-folder"
configuration.**All** `.relation` files in-and-above "source" and "destination" will be merged, with lower ones overriding the top
ones, and applied. This can create self-contained definition of "layers and fences" among your application.```js
//packages/core/.relations.js
module.exports = [
// allow internal access (from/to the same location)
{
from: '.',
to: '.',
type: 'allowed',
},
// prevent access to this folder (from the outside)
{
to: '.',
type: 'restricted',
},
];
```## Test helpers
It's important to test relations in order to keep boundaries active. Just think about it - if the rule is triggered only
when you break it, and you do not breaking it - how one can know it's actually working?There are two ways:
- import something you should now and suppress eslint rule. If checked using `--report-unused-disable-directives`,
then "not violation" will generate an error
- or you can use programatic helper exposed from this package```ts
import { resolveRelation } from 'eslint-plugin-relations';resolveRelation(from, to, options); // => Rule | undefined
// match given rules
resolveRelation(from, to, { rules: [{}] }); // => Rule | undefined
// autodiscover from .relation files
resolveRelation(from, to); // => Rule | undefined
resolveRelation(from, to, { cwd: baseDir }); // => Rule | undefined
```### See also
- [no-restricted-paths](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-restricted-paths.md)
is very close to this one, with slightly different configuration and reporting, but the same idea
- [depcheck](https://github.com/depcheck/depcheck) - we strongly recommend using `depcheck` to keep your package
relations up-to-date.
- [eslint dependencies-relation](https://github.com/YutamaKotaro/eslint-plugin-dependencies-relation) - an annonation based relation control# Speed
All rules are highly optimized:
- `relations/correct-imports` uses `trie` structure, and is not even visible in eslint timing report
- `relations/restrict` takes roughly 3% of whole linting time. Using `.relation` files or `ruleGenerator` can greatly
reduce the time (via reducing the number of rules to apply)# Licence
MIT