Git Product home page Git Product logo

eslint-config's Introduction

WIP: At the moment at beta-testing - use carefully

npm npm npm bundle size GitHub Workflow Status

Linting of FeatureSliced concepts by existing eslint-plugins

Rules

Each rule has its own test cases and customization aspects

Get Started

  1. You'll first need to install ESLint:

    $ npm install -D eslint
    # or by yarn
    $ yarn add -D eslint
  2. Next, install @feature-sliced/eslint-config and dependencies:

    $ npm install -D @feature-sliced/eslint-config eslint-plugin-import eslint-plugin-boundaries
    # or by yarn
    $ yarn add -D @feature-sliced/eslint-config eslint-plugin-import eslint-plugin-boundaries
  3. Add config to the extends section of your .eslintrc configuration file (for recommended rules). You can omit the eslint-config postfix:

    {
        "extends": ["@feature-sliced"]
    }
  4. TYPESCRIPT-ONLY: Also setup TS-parser and TS-plugin (why?)

    Details

    Install dependencies:

    $ npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript
    # or by yarn
    $ yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript

    Configure @typescript-eslint/parser as parser and setup the eslint-import-resolver-typescript resolver in the .eslintrc config file:

    {
      "parser": "@typescript-eslint/parser",
      "settings": {
        "import/resolver": {
          "typescript": {
            "alwaysTryTypes": true
          }
        }
      }
    }

Usage

  • Support general aliases

    import { Input } from "~/shared/ui/input";
    import { Input } from "@/shared/ui/input";
    import { Input } from "@shared/ui/input";
    import { Input } from "$shared/ui/input";
    // But not - import { Input } from "$UIKit/input";
  • Support relative and absolute imports (but look at recommendations)

    import { ... } from "entities/foo";    // absolute imports
    import { ... } from "@/entities/foo";  // aliased imports
    import { ... } from "../entities/foo"; // relative imports
  • Case-agnostic

    import { ... } from "entities/user-post";  // Support kebab-case (recommended)
    import { ... } from "entities/UserPost";   // Support PascalCase
    import { ... } from "entities/userPost";   // Support camelCase
    import { ... } from "entities/user_post";  // Support snake_case
  • For exceptional cases, support ⚠️DANGEROUS-mode⚠️ (see more for specific rule)

Customization

  1. You can partially use the rules

    WARN: Don't use main config ("@feature-sliced") in customization to avoid rules conflicts.

    "extends": [
      "@feature-sliced/eslint-config/rules/import-order",
      "@feature-sliced/eslint-config/rules/public-api",
      "@feature-sliced/eslint-config/rules/layers-slices",
    ]
  2. You can use alternative experimental rules

    • Use import-order/experimental for formatting with spaces between groups and reversed order of layers (why?)

      "extends": [
        // ... Other rules or config
        "@feature-sliced/eslint-config/rules/import-order/experimental",
      ]
    • Use public-api/lite for less strict PublicAPI boundaries (why?)

      "extends": [
        // ... Other rules or config
        "@feature-sliced/eslint-config/rules/public-api/lite",
      ]
  3. You can use warnings instead of errors for specific rules

    "rules": {
       // feature-sliced/import-order
       "import/order": "warn" // ~ 1,
       // feature-sliced/public-api
       "import/no-internal-modules": "warn" // ~ 1,
       // feature-sliced/layers-slices
       "boundaries/element-types": "warn" // ~ 1,
    }
  4. You can use advanced FSD-specific messages processing

    # (feature-sliced/public-api)
    - 'Reaching to "features/search/ui" is not allowed.'
    + 'Violated usage of modules Public API | https://git.io/Jymjf'

See also

eslint-config's People

Contributors

azinit avatar illright avatar js2me avatar krakazybik avatar rokashkovvd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eslint-config's Issues

LINT: (Public API) Add `semantic-imports` rule

Glossary

  • "External module" - entity/foo <-> entity/bar, entity/foo <-> shared/ui/button
  • "Internal module" - entity/foo <-> entity/foo/ui, entity/foo/ui/smth <-> entitiy/foo/lib

Description

  • Restrict relative imports for external modules (enforce for internalmodules)
  • Restrict absolute imports for internalmodules (enforce for external modules)

Example

// Fail 👎 
// @path features/baz/model/index.ts
import { ... } from "../../bar" // Sibling feature-slice
import { ... } from "@/features/baz/model/smth" // Invalid access to internal resource as external module
// Pass 👍 
// @path features/baz/model/index.ts
import { ... } from "../lib" // Import of internal module as internal resource - no illegal
import { ... } from "../entities/foo" // Lower layer slice - no illegal
import { ... } from "@entities/foo" // Import as external module
import { ... } from "shared/ui/button" // Import as external module

Reference

feature-sliced/documentation#52 (reply in thread)

LINT: Add import aliases support

Description

Should work for each rule

  • imports-order
  • layers-slices
  • public-api

Reference

// Pass
import { ... } from "entities/user";
import { ... } from "@/entities/user";
import { ... } from "@root/entities/user";
import { ... } from "@root/src/entities/user";
import { ... } from "~/entities/user";

LINT: Actualize rules names

Problem

  • Compatibility (better decide "too earlier", than "too late")
  • A lot of variations of rules for same concept

    PublicAPI: private-imports, segments-api, relative-imports, unified-api, ...
    Decomposition: layers-imports, slices-imports, ...

image
image

Bug: false trigger on valid imports

I have an app with react, effector, patronum, antd and bunch of other stuff

After i had added @feature-sliced/eslint-config, errors like this started to appear:

  2:26  error  Reaching to "effector-react/ssr" is not allowed  import/no-internal-modules
  3:23  error  Reaching to "patronum/delay" is not allowed      import/no-internal-modules

The problem is that imports like that are perfectly valid and intended by these libraries 🤷‍♂️
I suggest not to run import/no-internal-modules rule against imports from node_modules (or against non-feature-sliced paths at all)

Small reproduce: https://stackblitz.com/edit/fs-eslint-false-trigger?file=src/app/index.js
Run npm run lint in the terminal to see the issue

LINT: Add relative-imports support

Description

Should work for each rule

  • imports-order
  • layers-slices
  • public-api

Reference

// Pass
import { ... } from "entities/user";
import { ... } from "../entities/user";
import { ... } from "../../entities/user";
...

LINT(CUSTOM): Add advanced customization of config

Add remained options from #22

  • Option 4. Customize impl of configs
    • "I want to enable shared/styles in public-api linting"
    • "I want to add my own layer into existing"
  • Option 2. Only atomized specific rules
    • Not "layers-slices" but "layers" or "slices"
    • Not "public-api" but "slices-public-api" or "segments-public-api"

      It's bad, that we prohibit two boundaries both implicitly

LINT: Public API boundaries (private imports)

// Fail
import { Issues } from "pages/issues/ui";
import { IssueDetails } from "widgets/issue-details/ui/details"
import { AuthForm } from "features/auth-form/ui/form"
import { Button } from "shared/ui/button/button";
import { saveOrder } from "entities/order/model/actions";
import { orderModel } from "entities/order/model";
import { TicketCard } from "@/entities/ticket/ui";

// Pass
import { Issues } from "pages/issues";
import { IssueDetails } from "widgets/issue-details"
import { AuthForm } from "features/auth-form"
import { Button } from "shared/ui/button";
import { orderModel } from "entities/order";
import { TicketCard } from "@/entities/ticket";

// Pass: extra (if possible, or by next iteration)
import { AuthForm } from "features/auth/form"
import { Button } from "shared/ui";

LINT: Actualize old rules impl

Make it great again

plugins: [
"import",
],
rules: {
"import/first": 2,
"import/no-unresolved": 0, // experimental
"import/order": [
2,
{
pathGroups: PUBLIC_PATHS.map(
(pattern) => ({
pattern,
group: "internal",
position: "after",
}),
),
pathGroupsExcludedImportTypes: ["builtin"],
groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
},
],
// TODO: with messages (https://github.com/feature-sliced/eslint-config/issues/3)
"no-restricted-imports": [
2,
{
patterns: [...PRIVATE_PATHS, ...RELATIVE_PATHS]
}
],
},
};

LINT: import boundaries (between slices)

// Fail
// features/auth-form/index.ts
import { getAuthCtx } from "features/logout";
import { UserAvatar } from "features/viewer-picker";

// Pass
// features/auth-form/index.ts
import { getAuthCtx } from "entities/session";
import { UserAvatar } from "entities/user";

LINT: Refactor repo (Important!!!)

  • Базово привести в порядок репу
  • Поправить README
  • Релизнуть 0.0.0 версию (чтобы забить место)
  • Довести до yarn

LINT: (Actualize) Plugins and rules init tests.

После вливания #32 - тесты конфига и плагинов на валидность скипнуты тут т.к. правила подключаются в отдельных конфигах.

Возможные решения: если в extends находим локальные пути до конфигов, то идём в него и рекурсивно валидируем так всё, что найдём.

Можно в принципе не опускаться ниже чем на 1 уровень, т.к. я не думаю что мы упоримся в декомпозицию настолько, что будет актуальным рекурсивно ходить по конфигам 🤔

LINT(GET-STARTED): Adapt for usage

Compare with ready solutions (effector, airbnb, ...)

  • Ignore redundant files in dist
  • Check basic usage (recommended + custom)
  • Other refinements
    • Remained docs refinements
  • #22
    • Refine customizing?
    • Refine configs splitting?
    • Add exporting of "bootstrap-init" helper function?
  • Rename rules (for preventing future problems):
    import-order
    - public-api-boundaries
    + public-api
    - layers-slices-boundaries
    + layers-slices
    • + Rename at README
  • Rename local rules/index.md to README.md

LINT: Impl "segment-public-api" boundaries

Description

Each segment should have local public-api file declaration

/** @path features/smth/index.ts */
// Fail
export { SubmitButton } from "./ui/button";
export { SmthForm } from "./ui/form";
// Pass
export { SubmitButton, SmthForm } from "./ui";
/** @path features/smth/index.ts */
// Fail
export * from "./model/actions";
export { selectSmthById } from "./model/selectors";
// Pass
export * from "./model";
export * as smthModel from "./model";
export { selectSmthById, ... } from "./model";

ToRefine

  • shared/ui no-required reexports?
  • shared/lib no-required reexports?

Implement base version

With primary functional:

to restrict imports (not private paths, only public API)
// Fail
import { Issues } from "pages/issues";
import { IssueDetails } from "features/issue-details"
import { Button } from "shared/components/button";

// Pass
import Routing from "pages"; // specific pages shouldn't be reexported
import { IssueDetails } from "features" // all features should be reexported, for usage
import { Button } from "shared/components"; // all components should be reexported, for usage
to order imports (app > pages > features > shared > models)
// Fail
import { Helper } from "./helpers";
import axios from "axios";
import { data } from "../fixtures";
import { Button } from "shared/components"
import { IssueDetails, RepoList } from "features"
import { debounce } from "shared/helpers"

// Pass
import axios from "axios"; // 1) external libs
import { IssueDetails, RepoList } from "features" // 2) features
import { Button } from "shared/components" // 3) shared/**
import { debounce } from "shared/helpers"
import { data } from "../fixtures"; // 4) parent
import { Helper } from "./helpers"; // 5) sibling
to use only absolute imports (relative - only for module internal using)

NOTE: Be sure, that your tsconfig allows you to use absolute imports

  • baseUrl: "./src"
// Fail
import Routing from "../../pages"
import { IssueDetails } from "../features";
import { Button } from "../shared/components";

// Pass
import Routing from "pages"
import { IssueDetails } from "features";
import { Button } from "shared/components";

LINT(INTEGRATION): Move rule-configs close to tests

Description

  • Group unit configs and tests
  • (If possible) Add base doc for each "rule-unit"
   // As variant
   // @see https://github.com/effector/eslint-plugin/tree/master/rules/no-getState
   📁 rules/
        📁 layers-slices/
              index.js          // config of boundaries
              fixtures.js       // test fixtures (if need)
              index.test.js     // unit test of boundaries
              index.md          // docs of boundaries (in future)
        📁 public-api/

Reference

https://github.com/effector/eslint-plugin/tree/master/rules/no-getState

LINT: Imports order

Description

  • Process order of imports
  • For each layer
    (app > processes > pages > widgets > features > entities > shared)
  • (If possible) with tests
// Fail
import { getSmth } from "./lib";
import axios from "axios";
import { data } from "../fixtures";
import { authModel } from "entities/auth";
import { Button } from "shared/ui";
import { LoginForm } from "features/login-form";
import { Header } from "widgets/header";
import { debounce } from "shared/lib/fp";

// Pass
import axios from "axios";                           // 1) external libs
import { Header } from "widgets/header";             // 2.1) Layers: widgets
import { LoginForm } from "features/login-form";     // 2.2) Layers: features
import { authModel } from "entities/auth";           // 2.3) Layers: entities
import { Button } from "shared/ui";                  // 2.4) Layers: shared
import { debounce } from "shared/lib/fp";            // 2.4) Layers: shared
import { data } from "../fixtures";                  // 3) parent
import { getSmth } from "./lib";                     // 4) sibling

Reference

plugins: [
"import",
],
rules: {
"import/first": 2,
"import/no-unresolved": 0, // experimental
"import/order": [
2,
{
pathGroups: PUBLIC_PATHS.map(
(pattern) => ({
pattern,
group: "internal",
position: "after",
}),
),
pathGroupsExcludedImportTypes: ["builtin"],
groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
},
],
// TODO: with messages (https://github.com/feature-sliced/eslint-config/issues/3)
"no-restricted-imports": [
2,
{
patterns: [...PRIVATE_PATHS, ...RELATIVE_PATHS]
}
],
},
};

LINT(GET-STARTED): Check customization of config

Description

Add posibility to customize config by "concepts"-presets

We should support at least these options for integration:

  • Option 1. All recommended

    "I want all recommended boundaries"

  • Option 2. Only one specific

    "I want ONLY layers-boundaries / slice-boundaries / public-api-boundaries / import-order-boundaries / ... "

  • Option 3. Only few specific

    "I want ONLY layers-boundaries AND private-imports-boundaries"

  • Option 4. Customize impl of configs (see #45)
    • "I want to enable shared/styles in public-api linting"
    • "I want to add my own layer into existing"

Suggestion

As variant, to split these options by eslint rules presets:

But if you have better ideas - you're welcome ✊

// Option 1.  All recommended
extends: [
   "@feature-sliced/recommended"
],
// Option 2.  Only one specific
extends: [
   "@feature-sliced/rules/layers",
   // OR "@feature-sliced/rules/slices"
   // OR "@feature-sliced/rules/private-imports"
   // OR "@feature-sliced/rules/import-order"
],
// Option 3. Only few specific
extends: [
   "@feature-sliced/rules/layers",
   "@feature-sliced/rules/private-imports",
],
// Option 4. Customize impl of configs
// No ideas... Maybe "initConfig(...)" setup-helpers?

LINT(INTEGRATION): Add integration tests for common config

Description

  • Add some integration tests for global config
    Should check, that every unit-config works correctly without affecting
    • Restrict imports between layers (lower imports higher)
    • Restrict imports between slices (slice import another on the same layer)
    • Restrict private imports (segments access)
  • (If need) Clean unit-configs from redundant base-fields
    #32 (comment)

LINT: import boundaries (between layers)

// Fail
// features/auth-form/index.ts
import { getRoute } from "pages/auth";
import { getStore } from "app/store";

// Pass
// features/auth-form/index.ts
import { sessionModel } from "entities/session";
import { Form, Button } from "shared/ui";

LINT: Add `segments-boundaries` rule

Description

  • Segments (in shared and in slices) should follow the next rule:

    ui => model => api => lib => config

  • Ideally:

    [ANY_CUSTOM_SEGMENT] => ui => model => api => lib => config => [ANY_CUSTOM_SEGMENT]

#55 (reply in thread)

Example

/** @path features/smth/ui/** */
// Pass
export { getSmth } from "../lib";
export { selectById } from "../../model";
/** @path features/smth/api/** */
// Fail
export { FormType } from "../../../ui";
export { selectById } from "../../model";
// Pass
export { getSmth } from "../lib";

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.