All Projects → theKashey → eslint-plugin-relations

theKashey / eslint-plugin-relations

Licence: MIT license
Controls relationships between folders and packages 👩‍💻🤝👮🏻‍♂️

Programming Languages

typescript
32286 projects
javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to eslint-plugin-relations

introduction-nodejs
Introduction to NodeJS
Stars: ✭ 13 (-51.85%)
Mutual labels:  monorepo
Nishan
An ecosystem of packages for notion written in typescript.
Stars: ✭ 161 (+496.3%)
Mutual labels:  monorepo
injective-ts
Collection of TypeScript packages that consume and interact with the Injective Chain
Stars: ✭ 35 (+29.63%)
Mutual labels:  monorepo
moon-design
Moon Design System for React
Stars: ✭ 209 (+674.07%)
Mutual labels:  monorepo
react-native-web-monorepo-navigation
⚛️ An opinionated universal navigation strategy for codebases containing both React & React Native
Stars: ✭ 24 (-11.11%)
Mutual labels:  monorepo
comptroller
A simple and lightweight tool to manage your monorepo.
Stars: ✭ 26 (-3.7%)
Mutual labels:  monorepo
Rushstack
Monorepo for tools developed by the Rush Stack community
Stars: ✭ 3,465 (+12733.33%)
Mutual labels:  monorepo
itwinjs-core
Monorepo for iTwin.js Library
Stars: ✭ 493 (+1725.93%)
Mutual labels:  monorepo
yall
Yarn/npm for monorepos
Stars: ✭ 27 (+0%)
Mutual labels:  monorepo
awesome-nx
An awesome list that curates the best Nrwl Nx tools, tutorials, articles and more.
Stars: ✭ 45 (+66.67%)
Mutual labels:  monorepo
reactXstarter
React + Redux + React Native Starter Kit with reusable business logic. Sample TODO app built in.
Stars: ✭ 42 (+55.56%)
Mutual labels:  monorepo
flex-plugin-builder
Packages related to building a Twilio Flex Plugin
Stars: ✭ 69 (+155.56%)
Mutual labels:  monorepo
monorepify
A boilerplate for monorepo architecture using frameworks.
Stars: ✭ 37 (+37.04%)
Mutual labels:  monorepo
hasura-node-monolith-example
Example of a monolithic web application using Hasura GraphQL Engine + Node.js + Next.js
Stars: ✭ 25 (-7.41%)
Mutual labels:  monorepo
shared-react-components-example
An example of a mono-repository of shared React components libraries!
Stars: ✭ 85 (+214.81%)
Mutual labels:  monorepo
Clean Architecture Manga
🌀 Clean Architecture with .NET6, C#10 and React+Redux. Use cases as central organizing structure, completely testable, decoupled from frameworks
Stars: ✭ 3,104 (+11396.3%)
Mutual labels:  monorepo
nextjs-dapp-starter-ts
A fullstack monorepo template to develop ethereum dapps
Stars: ✭ 228 (+744.44%)
Mutual labels:  monorepo
expo-next-monorepo-example
Create a universal React app using Expo and Next.js in a monorepo
Stars: ✭ 268 (+892.59%)
Mutual labels:  monorepo
monorepo-utils
🔧 Helpful functions to manage monorepos (using Yarn Workspaces)
Stars: ✭ 25 (-7.41%)
Mutual labels:  monorepo
lerna-sync
A package to synchronize distributed GitHub repos inside a Lerna monorepo.
Stars: ✭ 15 (-44.44%)
Mutual labels:  monorepo

eslint-plugin-relations 👩‍💻🤝👮🏻‍♂️

Controls relationships between folders and packages in a monorepo environment.

Provided rules

Installation

  • add the package
yarn add eslint-plugin-relations
  • add the plugin to eslint config
// 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

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 , Law of Demeter , Abstraction Layers and Hexagonal 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:

  • commands
  • core
  • helpers
  • utils

It 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

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

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

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.

//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
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 is very close to this one, with slightly different configuration and reporting, but the same idea
  • depcheck - we strongly recommend using depcheck to keep your package relations up-to-date.
  • eslint 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

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