All Projects → javierbrea → Eslint Plugin Boundaries

javierbrea / Eslint Plugin Boundaries

Licence: mit
Eslint plugin checking architecture boundaries between elements

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Eslint Plugin Boundaries

Eslint Plugin Import
ESLint plugin with rules that help validate proper imports.
Stars: ✭ 3,722 (+2270.7%)
Mutual labels:  eslint-plugin, eslint, import
Eslint Plugin Monorepo
ESLint Plugin for monorepos
Stars: ✭ 56 (-64.33%)
Mutual labels:  eslint, eslint-plugin
Eslint Plugin Lwc
Official ESLint rules for LWC
Stars: ✭ 51 (-67.52%)
Mutual labels:  eslint, eslint-plugin
Domain Driven Hexagon
Guide on Domain-Driven Design, software architecture, design patterns, best practices etc.
Stars: ✭ 4,417 (+2713.38%)
Mutual labels:  architecture, architectural-patterns
Eslint Import Resolver Jest
🃏 Jest import resolution plugin for eslint-plugin-import
Stars: ✭ 29 (-81.53%)
Mutual labels:  eslint, import
Super Simple Architecture
🧩 Super Simple Architecture in Swift
Stars: ✭ 44 (-71.97%)
Mutual labels:  architecture, architectural-patterns
Sowing Machine
🌱A React UI toolchain & JSX alternative
Stars: ✭ 64 (-59.24%)
Mutual labels:  eslint, eslint-plugin
Ribs
Uber's cross-platform mobile architecture framework.
Stars: ✭ 6,641 (+4129.94%)
Mutual labels:  architecture, architectural-patterns
Typescript Eslint
✨ Monorepo for all the tooling which enables ESLint to support TypeScript
Stars: ✭ 10,831 (+6798.73%)
Mutual labels:  eslint, eslint-plugin
Eslint Plugin I18n Json
Fully extendable eslint plugin for JSON i18n translation files.
Stars: ✭ 101 (-35.67%)
Mutual labels:  eslint, eslint-plugin
Eslint Plugin Css Modules
Project status: NOT MAINTAINED; Checks that you are using the existent css/scss classes, no more no less
Stars: ✭ 115 (-26.75%)
Mutual labels:  eslint, eslint-plugin
Eslint Plugin
ESLint configurations and additional rules for me
Stars: ✭ 19 (-87.9%)
Mutual labels:  eslint, eslint-plugin
Mvpart
🎨 A new Android MVP architecture (此框架旨在解决传统 MVP 类和接口太多, 并且 Presenter 和 View 通过接口通信过于繁琐, 重用 Presenter 代价太大等问题).
Stars: ✭ 776 (+394.27%)
Mutual labels:  architecture, architectural-patterns
Eslint Plugin Vue I18n
🌐 ESLint plugin for Vue I18n
Stars: ✭ 50 (-68.15%)
Mutual labels:  eslint, eslint-plugin
Eslint Plugin Node
Additional ESLint's rules for Node.js
Stars: ✭ 740 (+371.34%)
Mutual labels:  eslint, eslint-plugin
Porto
Porto is a Modern Software Architectural Pattern that scales with your business!
Stars: ✭ 1,106 (+604.46%)
Mutual labels:  architecture, architectural-patterns
Peasy Js
A business logic micro-framework for javascript
Stars: ✭ 121 (-22.93%)
Mutual labels:  architecture, architectural-patterns
Xo
❤️ JavaScript/TypeScript linter (ESLint wrapper) with great defaults
Stars: ✭ 6,277 (+3898.09%)
Mutual labels:  eslint, eslint-plugin
Eslint Plugin Jest
ESLint plugin for Jest
Stars: ✭ 699 (+345.22%)
Mutual labels:  eslint, eslint-plugin
Eslint Mdx
ESLint Parser/Plugin for MDX
Stars: ✭ 89 (-43.31%)
Mutual labels:  eslint, eslint-plugin

Build status Coverage Status Quality Gate

NPM dependencies Renovate Last commit Last release

NPM downloads License

eslint-plugin-boundaries

In words of Robert C. Martin, "Software architecture is the art of drawing lines that I call boundaries. Those boundaries separate software elements from one another, and restrict those on one side from knowing about those on the other." (*acknowledgements)

This plugin ensures that your architecture boundaries are respected by the elements in your project checking the folders and files structure and the import statements (Read the main rules overview chapter for better comprehension.). It is not a replacement for eslint-plugin-import, on the contrary, the combination of both plugins is recommended.

Table of Contents

Details

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:

npm install --save-dev eslint eslint-plugin-boundaries

eslint-plugin-boundaries does not install eslint for you. You must install it yourself.

Activate the plugin and one of the canned configs in your .eslintrc.(yml|json|js):

{
  "plugins": ["boundaries"],
  "extends": ["plugin:boundaries/recommended"]
}

Migrating from v1.x

New v2.0.0 release has introduced many breaking changes. If you were using v1.x, you should read the "how to migrate from v1 to v2" guide.

Overview

All of the plugin rules need to be able to identify the elements in the project, so, first of all you have to define your project elements using the boundaries/elements setting.

The plugin will use the provided patterns to identify each file or local import statement as one of the element types.

{
  "settings": {
    "boundaries/elements": [
      {
        "type": "helpers",
        "pattern": "helpers/*"
      },
      {
        "type": "components",
        "pattern": "components/*"
      },
      {
        "type": "modules",
        "pattern": "modules/*"
      }
    ]
  }
}

This is only a basic example of configuration. The plugin can be configured to identify elements being a file, or elements being a folder containing files. It also supports capturing path fragments to be used afterwards on each rule options, etc. Read the configuration chapter for further info, as configuring it properly is crucial to take advantage of all of the plugin features.

Once your project elements are defined, you can use them to configure each rule using its own options. For example, you could define which elements can be dependencies of other ones configuring the element-types rule as in:

{
  "rules": {
    "boundaries/element-types": [2, {
      "default": "disallow",
      "rules": [
        {
          "from": "components",
          "allow": ["helpers", "components"]
        },
        {
          "from": "modules",
          "allow": ["helpers", "components", "modules"]
        }
      ]
    }]
  }
}

The plugin won't apply rules to a file or import when it does not recognize its element type, but you can force all files in your project to belong to an element type enabling the boundaries/no-unknown-files rule.

Main rules overview

Allowed element types

This rule ensures that dependencies between your project element types are allowed.

Examples of usage:

  • Define types in your project as "models", "views" and "controllers". Then ensure that "views" and "models" can be imported only by "controllers", and "controllers" will never be used by "views" or "models".
  • Define types in your project as "components", "views", "layouts", "pages", "helpers". Then ensure that "components" can only import "helpers", that "views" can only import "components" or "helpers", that "layouts" can only import "views", "components" or "helpers", and that "pages" can import any other element type.

Read the docs of the boundaries/element-types rule for further info.

Allowed external modules

External dependencies used by each type of element in your project can be checked using this rule. For example, you can define that "helpers" can't import react, or "components" can't import react-router-dom, or modules can't import { Link } from react-router-dom.

Read the docs of the boundaries/external rule for further info.

Private elements

This rule ensures that elements can't require other element's children. So, when an element B is children of A, B becomes a "private" element of A, and only A can use it.

Read the docs of the boundaries/no-private rule for further info.

Entry point

This rule ensures that elements can't import another file from other element than the defined entry point for that type (index.js by default)

Read the docs of the boundaries/entry-point rule for further info.

Rules

Configuration

Global settings

boundaries/element-types

Define patterns to recognize each file in the project as one of this element types. All rules need this setting to be configured properly to work. The plugin tries to identify each file being analized or import statement in rules as one of the defined element types. The assigned element type will be that with the first matching pattern, in the same order that elements are defined in the array, so you should sort them from the most accurate patterns to the less ones. Properties of each element:

  • type: <string> Element type to be assigned to files or imports matching the pattern. This type will be used afterwards in the rules configuration.
  • pattern: <string>|<array> micromatch pattern. By default the plugin will try to match this pattern progressively starting from the right side of each file path. This means that you don't have to define patterns matching from the base project path, but only the last part of the path that you want to be matched. This is made because the plugin supports elements being children of other elements, and otherwise it could wrongly recognize children elements as a part of the parent one.
    For example, given a path src/helpers/awesome-helper/index.js, it will try to assign the element to a pattern matching index.js, then awesome-helper/index.js, then helpers/awesome-helper/index.js, etc. Once a pattern matches, it assign the correspondent element type, and continues searching for parents elements with the same logic until the full path has been analyzed. This behavior can be disabled setting the mode option to full, then the provided pattern will try to match the full path.
  • mode: <string> file|folder|full Optional.
    • When it is set to folder (default value), the element type will be assigned to the first file's parent folder matching the pattern. In the practice, it is like adding **/* to the given pattern, but the plugin makes it by itself because it needs to know exactly which parent folder has to be considered the element.
    • If it is set to file, the given pattern will not be modified, but the plugin will still try to match the last part of the path. So, a pattern like *.model.js would match with paths src/foo.model.js, src/modules/foo/foo.model.js, src/modules/foo/models/foo.model.js, etc.
    • If it is set to full, the given pattern will only match with patterns matching the full path. This means that you will have to provide patterns matching from the base project path. So, in order to match src/modules/foo/foo.model.js you'll have to provide patterns like **/*.model.js, **/*/*.model.js, src/*/*/*.model.js, etc. (the chosen pattern will depend on what do you want to capture from the path)
  • capture: <array> Optional. This is a very powerful feature of the plugin. It allows to capture values of some fragments in the matching path to use them later in the rules configuration. It uses micromatch capture feature under the hood, and stores each value in an object with the given capture key being in the same index of the captured array.
    For example, given pattern: "helpers/*/*.js", capture: ["category", "elementName"], and a path helpers/data/parsers.js, it will result in { category: "data", elementName: "parsers" }.
{
  "settings": {
    "boundaries/elements": [
      {
        "type": "helpers",
        "pattern": "helpers/*/*.js",
        "mode": "file",
        "capture": ["category", "elementName"]
      },
      {
        "type": "components",
        "pattern": "components/*/*",
        "capture": ["family", "elementName"]
      },
      {
        "type": "modules",
        "pattern": "module/*",
        "capture": ["elementName"]
      }
    ]
  }
}

Tip: You can enable the debug mode when configuring the plugin, and you will get information about the type assigned to each file in the project.

boundaries/ignore

Files or dependencies matching these micromatch patterns will be ignored by the plugin.

{
  "settings": {
    "boundaries/ignore": ["**/*.spec.js", "src/legacy-code/**/*"]
  }
}

Predefined configurations

This plugin is distributed with two different predefined configurations: "recommended" and "strict".

Recommended

We recommend to use this setting if you are applying the plugin to an already existing project. Rules boundaries/no-unknown, boundaries/no-unknown-files and boundaries/no-ignored are disabled, so it allows to have parts of the project non-compliant with your element types, allowing to refactor the code progressively.

{
  "extends": ["plugin:boundaries/recommended"]
}

Strict

All rules are enabled, so all elements in the project will be compliant with your architecture boundaries. 😃

{
  "extends": ["plugin:boundaries/strict"]
}

Rules configuration

Some rules require extra configuration, and it has to be defined in each specific rule property of the .eslintrc..(yml|json|js) file. For example, allowed element types relationships has to be provided as an option to the boundaries/element-types rule. Rules requiring extra configuration will print a warning in case they are enabled without the needed options.

Main format of rules options

The docs of each rule contains an specification of their own options, but the main rules share the format in which the options have to be defined. The format described here is valid for options of element-types, external and entry-point rules.

Options set an "allow/disallow" value by default, and provide an array of rules. Each matching rule will override the default value and the value returned by previous matching rules. So, the final result of the options, once processed for each case, will be "allow" or "disallow", and this value will be applied by the plugin rule in the correspondant way, making it to produce an eslint error or not.

{
  "rules": {
    "boundaries/element-types": [2, {
      "default": "allow",
      "rules": [
        {
          "from": ["helpers"],
          "disallow": ["modules", "components", "helpers"]
        },
        {
          "from": ["components"],
          "disallow": ["modules"]
        }
      ]
    }]
  }
}

Remember that:

  • All rules are executed, and the resultant value will be the one returned by the last matching one.
  • If one rule contains both allow and disallow properties, the disallow one has priority. It will not try to match the allow one if disallow matches. The result for that rule will be disallow in that case.
Rules options properties
  • from/target: <element matchers> Depending of the rule to which the options are for, the rule will be applied only if the file being analized matches with this element matcher (from), or the dependency being imported matches with this element matcher (target).
  • disallow/allow: <value matchers> If the plugin rule target matches with this, then the result of the rule will be "disallow/allow". Each rule will require a type of value here depending of what it is checking. In the case of the element-types rule, for example, another <element matcher> has to be provided in order to check the type of the local dependency.

Tip: All properties can receive a single matcher, or an array of matchers.

Elements matchers

Elements matchers used in the rules options can have the next formats:

  • <string>: Will return true when the element type matches with this micromatch pattern.
  • [<string>, <capturedValuesObject>]: Will return true whe when the element type matches with the first element in the array, and all of the captured values also match.
    The <capturedValuesObject> has to be an object containing capture keys from the boundaries/element-types setting of the element as keys, and micromatch patterns as values.
    For example, for an element of type "helpers" with settings as { type: "helpers", pattern": "helpers/*/*.js", "capture": ["category", "elementName"]}, you could write element matchers as:
    • ["helpers", { category: "data", elementName: "parsers"}]: Will only match with helpers with category "data" and elementName "parsers" (helpers/data/parsers.js).
    • ["helpers", { category: "data" }]: Will match with all helpers with category "data" (helpers/data/*.js)
Advanced example of a rule configuration

Just to illustrate the high level of customization that the plugin supports, here is an example of advanced options for the boundaries/element-types rule based on the previous global elements settings example:

{
  "rules": {
    "boundaries/element-types": [2, {
      // disallow importing any element by default
      "default": "disallow",
      "rules": [
        {
          // allow importing helpers files from helpers files
          "from": ["helpers"],
          "allow": ["helpers"]
        },
        {
          // when file is inside an element of type "components"
          "from": ["components"],
          "allow": [
            // allow importing components of the same family
            ["components", { "family": "${family}" }],
            // allow importing helpers with captured category "data"
            ["helpers", { "category": "data" }],
          ]
        },
        {
          // when component has captured family "molecule"
          "from": [["components", { "family": "molecule" }]],
          "allow": [
            // allow importing components with captured family "atom"
            ["components", { "family": "atom" }],
          ],
        },
        {
          // when component has captured family "atom"
          "from": [["components", { "family": "atom" }]],
          "disallow": [
            // disallow importing helpers with captured category "data"
            ["helpers", { "category": "data" }]
          ]
        },
        {
          // when file is inside a module
          "from": ["modules"],
          "allow": [
            // allow importing any type of component or helper
            "helpers",
            "components"
          ]
        },
        {
          // when module name starts by "page-"
          "from": [["modules", { "elementName": "page-*" }]],
          "disallow": [
            // disallow importing any type of component not being of family layout
            ["components", { "family": "!layout" }]
          ]
        }
      ]
    }]
  }
}

Resolvers

"With the advent of module bundlers and the current state of modules and module syntax specs, it's not always obvious where import x from 'module' should look to find the file behind module." (**Quote from the eslint-plugin-import docs)

This plugin uses eslint-module-utils/resolve module under the hood, which is a part of the eslint-plugin-import plugin. So the import/resolver setting can be used to use custom resolvers for this plugin too.

Read the resolvers chapter of the eslint-plugin-import plugin for further info.

{
  "settings": {
    "import/resolver": {
      "eslint-import-resolver-node": {},
      "some-other-custom-resolver": { "someConfig": "value" }
    }
  }
}

Debug mode

In order to help during the configuration process, the plugin can trace information about the files and imports being analyzed. The information includes the file path, the assigned element type, the captured values, etc. So, it can help you to check that your elements setting works as expected. You can enable it using the ESLINT_PLUGIN_BOUNDARIES_DEBUG environment variable.

ESLINT_PLUGIN_BOUNDARIES_DEBUG=1 npm run lint

Acknowledgements

* Quote from Robert C. Martin's book "Clean Architecture: A Craftsman's Guide to Software Structure and Design".

** This plugin uses internally the eslint-module-utils/resolve module, which is a part of the eslint-plugin-import plugin. Thanks to the maintainers of that plugin for their awesome work.

Contributing

Contributors are welcome. Please read the contributing guidelines and code of conduct.

License

MIT, see LICENSE for details.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].