├── .eslintrc.json
├── .github
├── pull_request_template.md
└── workflows
│ └── test.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc.js
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FAQ.md
├── LICENSE
├── README.md
├── index.js
├── index.legacy.js
├── package.json
├── packages
├── cli
│ ├── README.md
│ ├── bin.js
│ ├── package.json
│ ├── rollup.conf.js
│ ├── src
│ │ ├── index.js
│ │ ├── log.js
│ │ ├── packages.js
│ │ ├── run.js
│ │ └── ui.js
│ ├── tests
│ │ └── index.test.js
│ └── yarn.lock
└── processor
│ ├── README.md
│ ├── index.js
│ ├── messages.js
│ └── package.json
├── rules
├── import-order
│ ├── README.md
│ ├── experimental.js
│ ├── experimental.test.js
│ ├── index.js
│ └── index.test.js
├── integration.test.js
├── integration.ts.test.js
├── layers-slices
│ ├── README.md
│ ├── index.js
│ ├── layers.test.js
│ └── slices.test.js
└── public-api
│ ├── README.md
│ ├── index.js
│ ├── index.test.js
│ ├── lite.js
│ ├── lite.test.js
│ └── segment-public-api.test.js
├── test
├── config.test.js
└── lint.test.js
├── utils
├── config
│ ├── index.js
│ └── mock-resolver.js
├── index.js
├── layers.js
└── types.js
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@eslint-kit/patch",
4 | "@eslint-kit/base",
5 | "@eslint-kit/prettier"
6 | ],
7 | "rules": {
8 | "no-undef": "off"
9 | }
10 | }
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | ## References
4 |
5 | ## Checklist
6 | - [ ] Description added
7 | - [ ] Self-reviewed
8 | - [ ] CI pass
9 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test current build
2 | on: [push]
3 | jobs:
4 | test_build:
5 | runs-on: ubuntu-latest
6 | timeout-minutes: 3
7 | strategy:
8 | matrix:
9 | node-version: [12.x]
10 | steps:
11 | - name: Checkout Repository
12 | uses: actions/checkout@v1
13 |
14 | - name: Use Node.js ${{ matrix.node-version }}
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: ${{ matrix.node-version }}
18 |
19 | - name: Install deps and run tests
20 | run: |
21 | npm install
22 | npm run test
23 | env:
24 | CI: true
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .idea
3 | node_modules/
4 | dist/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.test.*
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/**
2 | dist/**
3 | build/**
4 | public/
5 | .github
6 | .workflows
7 | *.md
8 | *.mdx
9 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | semi: true,
4 | singleQuote: false,
5 | tabWidth: 4,
6 | quoteProps: "consistent",
7 | endOfLine: "lf",
8 | importOrder: ["^[./]"],
9 | trailingComma: "all",
10 | arrowParens: "always",
11 | };
12 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at martis.azin@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | First of all, thanks for taking the time to contribute! :+1:
4 |
5 | ## How Can I Contribute?
6 |
7 | [issues]: https://github.com/feature-sliced/eslint-config/issues
8 | [issues-new]: https://github.com/feature-sliced/eslint-config/issues/new
9 | [pr]: https://github.com/feature-sliced/eslint-config/pulls
10 | [pr-new]: https://github.com/feature-sliced/eslint-config/compare
11 | [disc]: https://github.com/feature-sliced/eslint-config/discussions/7
12 |
13 |
14 |
15 | - 📢 [Give feedback][disc]
16 | > We'll glad to get any feedback from you!
17 | - 💡 [Report bugs, suggest improvements][issues-new]
18 | > If something specific doesn't work well for you or can be done better, please let us know!
19 | - 💬 Estimate & discuss [issues][issues]
20 | > Share your opinion, evaluate given problem context from author
21 | - 🔩 Repeat difficult [issues][issues]
22 | > Some issues hard to repeat
23 | - 🛡️ Review [pull requests][pr]
24 | > Share your opinion and help us with others' suggestions
25 | - ⚒️ Suggest [your own pull-requests!][pr-new]
26 | > Reinforce project by your code solution
27 |
28 | ## Workflow
29 | 1. Fork repository
30 | 2. Add your changes
31 | - Ensure that **commits messages conforms** to [Conventional Commits](https://www.conventionalcommits.org) spec.
32 | - Ensure that **all tests are passing**
33 | ```sh
34 | $ npm run test # mocha will be started
35 | ```
36 | 3. Propose your pull-request by *your forked branch* and specify related issues, if they are exist
37 | - Ensure that **[CI](https://github.com/feature-sliced/eslint-config/actions)** is passing for your PR
38 | > Our goal - to dev good-quality solution in every sense
39 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | ### How to test eslint-config locally without publishing?
4 |
5 | See [npm-link](https://docs.npmjs.com/cli/v8/commands/npm-link)
6 |
7 | ### Is there any existing eslint-config drafts?
8 |
9 | See [here](https://gist.github.com/azinit/4cb940a1d4a3e05ef47e15aa18a9ecc5)
10 |
11 | ### Where I can find actual rules?
12 |
13 | See [here](../index.js)
14 |
15 | ### How to publish last added rules?
16 |
17 | You only need to send [pull-request](https://github.com/feature-sliced/eslint-config/pulls)
18 |
19 | Publishing is realized only by FeatureSliced core-team 🍰
20 |
21 | ### Where I can suggest new boundaries?
22 |
23 | Create [new issue](https://github.com/feature-sliced/eslint-config/issues/new)
24 |
25 | ### Are there plans about migrating to eslint-plugin?
26 |
27 | At the moment we wanna to use existing solutions for "MVP" linting of FeatureSliced best practices
28 |
29 | Later, if we'll make sure that eslint-config is not enough - then we'll migrate to eslint-plugin with compatibility and migration guides of eslint-config users
30 |
31 | > Compatibility First
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 feature-sliced
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [@feature-sliced/eslint-config](https://www.npmjs.com/package/@feature-sliced/eslint-config)
2 |
3 | > `WIP:` At the moment at beta-testing - [use carefully](https://github.com/feature-sliced/eslint-config/discussions/75)
4 |
5 | [npm]: https://www.npmjs.com/package/@feature-sliced/eslint-config
6 |
7 | [][npm]
8 | [][npm]
9 | [][npm]
10 | [](https://github.com/feature-sliced/eslint-config/actions)
11 |
12 |
13 |
14 | Linting of [FeatureSliced](https://github.com/feature-sliced/documentation) concepts *by existing eslint-plugins*
15 |
16 | - Control [**Isolation**](https://feature-sliced.design/docs/concepts/low-coupling) & [**Decomposition**](https://feature-sliced.design/docs/concepts/app-splitting)
17 | - Control [**Public API**](https://feature-sliced.design/docs/concepts/public-api)
18 | - Control [**Layers & Scopes**](https://feature-sliced.design/docs/reference/layers)
19 | - Control [**Naming**](https://feature-sliced.design/docs/concepts/naming-adaptability)
20 |
21 |
30 |
31 | ## Rules
32 |
33 | Each rule has its own test cases and customization aspects
34 |
35 | - [`import-order`](./rules/import-order)
36 | - [`public-api`](./rules/public-api)
37 | - [`layers-slices`](./rules/layers-slices)
38 |
39 | ## Get Started
40 |
41 | 1. You'll first need to install [ESLint](http://eslint.org):
42 |
43 | ```sh
44 | $ npm install -D eslint
45 | # or by yarn
46 | $ yarn add -D eslint
47 | $ or by pnpm
48 | $ pnpm add -D eslint
49 | ```
50 |
51 | 2. Next, install `@feature-sliced/eslint-config` and dependencies:
52 |
53 | ```sh
54 | $ npm install -D @feature-sliced/eslint-config eslint-plugin-import eslint-plugin-boundaries
55 | # or by yarn
56 | $ yarn add -D @feature-sliced/eslint-config eslint-plugin-import eslint-plugin-boundaries
57 | # or by pnpm
58 | $ pnpm add -D @feature-sliced/eslint-config eslint-plugin-import eslint-plugin-boundaries
59 | ```
60 |
61 | 3. Add config to the `extends` section of your `.eslintrc` configuration file (for **recommended** rules). You can omit the `eslint-config` postfix:
62 |
63 | ```json
64 | {
65 | "extends": ["@feature-sliced"]
66 | }
67 | ```
68 |
69 | 4. `TYPESCRIPT-ONLY:` Also setup TS-parser and TS-plugin [(why?)](https://github.com/javierbrea/eslint-plugin-boundaries#usage-with-typescript)
70 |
71 | Details
72 |
73 | **Install dependencies:**
74 |
75 | ```sh
76 | $ npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript
77 | # or by yarn
78 | $ yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript
79 | ```
80 |
81 | **Configure `@typescript-eslint/parser` as parser and setup the `eslint-import-resolver-typescript` resolver in the `.eslintrc` config file:**
82 |
83 | ```json
84 | {
85 | "parser": "@typescript-eslint/parser",
86 | "settings": {
87 | "import/resolver": {
88 | "typescript": {
89 | "alwaysTryTypes": true
90 | }
91 | }
92 | }
93 | }
94 | ```
95 |
96 |
97 |
98 | ## Usage
99 |
100 | - Support general **aliases**
101 |
102 | ```js
103 | import { Input } from "~/shared/ui/input";
104 | import { Input } from "@/shared/ui/input";
105 | import { Input } from "@shared/ui/input";
106 | import { Input } from "$shared/ui/input";
107 | // But not - import { Input } from "$UIKit/input";
108 | ```
109 |
110 | - Support **relative** and **absolute** imports (but look at [recommendations](https://github.com/feature-sliced/eslint-config/issues/29))
111 |
112 | ```js
113 | import { ... } from "entities/foo"; // absolute imports
114 | import { ... } from "@/entities/foo"; // aliased imports
115 | import { ... } from "../entities/foo"; // relative imports
116 | ```
117 |
118 | - **Case**-agnostic
119 |
120 | ```js
121 | import { ... } from "entities/user-post"; // Support kebab-case (recommended)
122 | import { ... } from "entities/UserPost"; // Support PascalCase
123 | import { ... } from "entities/userPost"; // Support camelCase
124 | import { ... } from "entities/user_post"; // Support snake_case
125 | ```
126 |
127 | - For exceptional cases, support ⚠️**DANGEROUS-mode**⚠️ (see more for [specific rule](#rules))
128 |
129 | ## Customization
130 |
131 | 1. You can *partially use* the rules
132 |
133 | > **WARN:** Don't use main config (`"@feature-sliced"`) in customization to avoid rules conflicts.
134 |
135 | ```js
136 | "extends": [
137 | "@feature-sliced/eslint-config/rules/import-order",
138 | "@feature-sliced/eslint-config/rules/public-api",
139 | "@feature-sliced/eslint-config/rules/layers-slices",
140 | ]
141 | ```
142 |
143 | 1. You can use *alternative experimental rules*
144 | - Use [`import-order/experimental`](./rules/import-order#Experimental) for formatting with spaces between groups and reversed order of layers [(why?)](https://github.com/feature-sliced/eslint-config/issues/85)
145 |
146 | ```js
147 | "extends": [
148 | // ... Other rules or config
149 | "@feature-sliced/eslint-config/rules/import-order/experimental",
150 | ]
151 | ```
152 |
153 | - Use [`public-api/lite`](./rules/public-api#Lite) for less strict PublicAPI boundaries [(why?)](https://github.com/feature-sliced/eslint-config/issues/90)
154 |
155 | ```js
156 | "extends": [
157 | // ... Other rules or config
158 | "@feature-sliced/eslint-config/rules/public-api/lite",
159 | ]
160 | ```
161 |
162 | 1. You can use *warnings* instead of *errors* for specific rules
163 |
164 | ```js
165 | "rules": {
166 | // feature-sliced/import-order
167 | "import/order": "warn" // ~ 1,
168 | // feature-sliced/public-api
169 | "import/no-internal-modules": "warn" // ~ 1,
170 | // feature-sliced/layers-slices
171 | "boundaries/element-types": "warn" // ~ 1,
172 | }
173 | ```
174 |
175 | 1. You can use *[advanced FSD-specific messages processing](https://www.npmjs.com/package/@feature-sliced/eslint-plugin-messages)*
176 |
177 | ```diff
178 | # (feature-sliced/public-api)
179 | - 'Reaching to "features/search/ui" is not allowed.'
180 | + 'Violated usage of modules Public API | https://git.io/Jymjf'
181 | ```
182 |
183 | ## See also
184 |
185 | - [FAQ](./FAQ.md)
186 | - [Releases & Changelog](https://github.com/feature-sliced/eslint-config/releases)
187 | - [**How can I help?**](./CONTRIBUTING.md)
188 | - ⭐ Rate us on GitHub
189 | - 💫 **Any assistance is important** - from *feedback to participation in the development of the methodology*!
190 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = {
4 | parserOptions: {
5 | "ecmaVersion": "2015",
6 | "sourceType": "module",
7 | },
8 | extends: [
9 | path.resolve(__dirname, "./rules/public-api"),
10 | path.resolve(__dirname, "./rules/layers-slices"),
11 | path.resolve(__dirname, "./rules/import-order")
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/index.legacy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DO NOT MODIFY THIS FILE
3 | * THERE ARE ONLY OLD RULES THAT SHOULD BE ACTUALIZED
4 | * @see https://github.com/feature-sliced/eslint-config/issues/17
5 | */
6 |
7 | // Allowed paths for public API
8 | const PUBLIC_PATHS = [
9 | "app",
10 | "pages",
11 | "features",
12 | "shared",
13 | "shared/**",
14 | "models",
15 | ];
16 | // Private imports are prohibited, use public imports instead
17 | const PRIVATE_PATHS = [
18 | "app/**",
19 | "pages/**",
20 | "features/**",
21 | "shared/*/**",
22 | ];
23 | // Prefer absolute imports instead of relatives (for root modules)
24 | const RELATIVE_PATHS = [
25 | "../**/app",
26 | "../**/pages",
27 | "../**/features",
28 | "../**/shared",
29 | "../**/models",
30 | ];
31 |
32 | module.exports = {
33 | parserOptions: {
34 | ecmaVersion: "2015",
35 | sourceType: "module",
36 | },
37 | plugins: [
38 | "import",
39 | ],
40 | rules: {
41 | "import/first": 2,
42 | "import/no-unresolved": 0, // experimental
43 | "import/order": [
44 | 2,
45 | {
46 | pathGroups: PUBLIC_PATHS.map(
47 | (pattern) => ({
48 | pattern,
49 | group: "internal",
50 | position: "after",
51 | }),
52 | ),
53 | pathGroupsExcludedImportTypes: ["builtin"],
54 | groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
55 | },
56 | ],
57 | // TODO: with messages (https://github.com/feature-sliced/eslint-config/issues/3)
58 | "no-restricted-imports": [
59 | 2,
60 | {
61 | patterns: [...PRIVATE_PATHS, ...RELATIVE_PATHS]
62 | }
63 | ],
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@feature-sliced/eslint-config",
3 | "version": "0.1.0-beta.6",
4 | "description": "🍰 Lint feature-sliced concepts by existing eslint plugins",
5 | "main": "index.js",
6 | "files": [
7 | "rules/**/experimental.js",
8 | "rules/**/lite.js",
9 | "rules/**/index.js",
10 | "utils/**",
11 | "index.js"
12 | ],
13 | "repository": "https://github.com/feature-sliced/eslint-config.git",
14 | "author": "FeatureSliced core-team",
15 | "license": "MIT",
16 | "keywords": [
17 | "eslint",
18 | "eslintconfig",
19 | "eslint-config",
20 | "feature-sliced",
21 | "feature-slices",
22 | "feature-driven",
23 | "feature-based"
24 | ],
25 | "scripts": {
26 | "publish:prepatch": "npm version prepatch && npm publish",
27 | "publish:patch": "npm version patch && npm publish",
28 | "publish:minor": "npm version minor && npm publish",
29 | "publish:major": "npm version major && npm publish",
30 | "prettier:fix": "prettier --write **/*.js",
31 | "clean": "git clean -fxd",
32 | "test": "mocha \"*(test|rules)/**/*.test.js\""
33 | },
34 | "peerDependencies": {
35 | "eslint-plugin-boundaries": ">=2",
36 | "eslint-plugin-import": ">=2"
37 | },
38 | "publishConfig": {
39 | "access": "public"
40 | },
41 | "devDependencies": {
42 | "@eslint-kit/eslint-config-base": "^5.0.2",
43 | "@eslint-kit/eslint-config-patch": "^1.0.0",
44 | "@eslint-kit/eslint-config-prettier": "^4.0.0",
45 | "@typescript-eslint/parser": "^5.6.0",
46 | "eslint": "7.10.0",
47 | "eslint-import-resolver-node": "^0.3.6",
48 | "eslint-plugin-boundaries": "^2.6.0",
49 | "eslint-plugin-import": "^2.25.3",
50 | "mocha": "^8.2.1",
51 | "prettier": "2.3.0",
52 | "typescript": "^4.5.3"
53 | },
54 | "dependencies": {}
55 | }
56 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # [@feature-sliced/eslint-config-cli](https://www.npmjs.com/package/@feature-sliced/config-cli)
2 |
3 | > `WIP:` At the moment at beta-testing - [use carefully](https://github.com/feature-sliced/eslint-config/discussions/75)
4 |
5 |
6 |
7 | CLI for more comfortable usage of [@feature-sliced/eslint-config](https://www.npmjs.com/package/@feature-sliced/eslint-config)
8 |
9 | - Quick bootstraping with dependencies installing
10 | - *TBA: Config customization*
11 |
12 | ## How it works?
13 |
14 | CLI stores the dependencies necessary for @feature-sliced/eslint-config to work.
15 |
16 | 1. **At start, it checks whether the project is a Typescript project**
17 | The package file is parsed.the user's json, and if the @types/* packages or the typescript package are found in it, the project is recognized as a TS project.
18 |
19 | 2. **Next, the ui-prompt is launched, which confirms the installation of TS packages from the user, and the installation in general.**
20 | With the consent of the user, the installation process is started, the presence of installed packages is checked by the user, in case of presence, the package is skipped (filtered), if started with force, all packages will be forcibly installed.
21 |
--------------------------------------------------------------------------------
/packages/cli/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require("./dist/cli.js");
3 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@feature-sliced/eslint-cli",
3 | "version": "0.0.0-concept.3",
4 | "description": "🍰 CLI for eslint-config convenient usage",
5 | "keywords": [
6 | "eslint",
7 | "eslintconfig",
8 | "eslint-config",
9 | "feature-sliced",
10 | "feature-slices",
11 | "feature-driven",
12 | "feature-based"
13 | ],
14 | "main": "dist/cli.js",
15 | "bin": {
16 | "@feature-sliced/eslint-cli": "bin.js"
17 | },
18 | "scripts": {
19 | "build": "rollup -c rollup.conf.js",
20 | "test": "DEBUG=2 mocha tests/*.test.js",
21 | "cli:run": "node src/index.js",
22 | "cli:dev": "DEBUG=1 node src/index.js"
23 | },
24 | "publishConfig": {
25 | "access": "public"
26 | },
27 | "repository": "https://github.com/feature-sliced/eslint-config.git",
28 | "author": "FeatureSliced core-team",
29 | "license": "MIT",
30 | "dependencies": {
31 | },
32 | "devDependencies": {
33 | "@rollup/plugin-commonjs": "^21.0.1",
34 | "@rollup/plugin-json": "^4.1.0",
35 | "@rollup/plugin-node-resolve": "^13.1.3",
36 | "clear": "^0.1.0",
37 | "lodash": "^4.17.21",
38 | "mocha": "^9.2.1",
39 | "picocolors": "^1.0.0",
40 | "prompts": "^2.4.2",
41 | "rollup": "^2.67.3",
42 | "rollup-plugin-terser": "^7.0.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/cli/rollup.conf.js:
--------------------------------------------------------------------------------
1 | import commonjs from "@rollup/plugin-commonjs";
2 | import json from "@rollup/plugin-json";
3 | import { nodeResolve } from "@rollup/plugin-node-resolve";
4 | import { terser } from "rollup-plugin-terser";
5 |
6 | export default {
7 | input: "src/index.js",
8 | output: {
9 | file: "dist/cli.js",
10 | format: "cjs",
11 | },
12 | plugins: [nodeResolve({ include: ["node_modules/**"] }), commonjs(), json(), terser()],
13 | };
14 |
--------------------------------------------------------------------------------
/packages/cli/src/index.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const _ = require("lodash");
3 | const { installCmdBuilder, installDependencies } = require("./run");
4 | const {
5 | getPkgManger,
6 | depsPackages,
7 | basicPackages,
8 | typescriptPackages,
9 | filterInstalledDeps,
10 | getUserDeps,
11 | isTypeScriptProject,
12 | } = require("./packages");
13 | const { ui } = require("./ui");
14 | const { log } = require("./log");
15 |
16 | const packageJsonRaw = fs.readFileSync("package.json");
17 | const packageInfo = JSON.parse(packageJsonRaw);
18 | const userDeps = getUserDeps(packageInfo);
19 |
20 | function bootstrap({ withTs, force = false }) {
21 | if (process.env.DEBUG) console.info("Bootstraping with ts/force:", withTs, force);
22 |
23 | log.info("@feature-sliced/eslint-config/cli");
24 |
25 | const userPkgManager = getPkgManger();
26 | if (!userPkgManager) {
27 | return;
28 | }
29 | log.info(`Found ${userPkgManager}. Start install missing dependencies.`);
30 |
31 | const runInstall = installCmdBuilder(userPkgManager);
32 | const installDeps = force ? depsPackages : filterInstalledDeps(depsPackages, userDeps);
33 | let tsDeps = {};
34 |
35 | if (withTs) {
36 | tsDeps = force ? typescriptPackages : filterInstalledDeps(typescriptPackages, userDeps);
37 | }
38 |
39 | installDependencies(runInstall, _.merge(installDeps, basicPackages, tsDeps));
40 |
41 | log.info(`Done.`);
42 | }
43 |
44 | ui(bootstrap, isTypeScriptProject(userDeps));
45 |
--------------------------------------------------------------------------------
/packages/cli/src/log.js:
--------------------------------------------------------------------------------
1 | const pc = require("picocolors");
2 |
3 | const log = {
4 | error: (text) => console.error(pc.red(text)),
5 | warn: (text) => console.warn(pc.yellow(text)),
6 | info: (text) => console.info(pc.green(text)),
7 | };
8 |
9 | module.exports = { log };
10 |
--------------------------------------------------------------------------------
/packages/cli/src/packages.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 | const { log } = require("./log");
4 | const _ = require("lodash");
5 |
6 | const basicPackages = {
7 | "@feature-sliced/eslint-config": "latest",
8 | };
9 |
10 | const depsPackages = {
11 | "eslint-plugin-boundaries": "^2.8.0",
12 | "eslint-plugin-import": "^2.25.4",
13 | };
14 |
15 | const typescriptPackages = {
16 | "@typescript-eslint/eslint-plugin": "latest",
17 | "@typescript-eslint/parser": "latest",
18 | "eslint-import-resolver-typescript": "latest",
19 | };
20 |
21 | const PkgMangers = {
22 | npm: { lock: "package-lock.json", install: "install" },
23 | yarn: { lock: "yarn.lock", install: "add" },
24 | pnpm: { lock: "pnpm-lock.yaml", install: "install" },
25 | };
26 |
27 | function isTypeScriptProject(userDeps) {
28 | if (process.env.DEBUG) console.info("Detected user dependencies:", userDeps);
29 | for (const dep in userDeps) {
30 | if (process.env.DEBUG) console.info("processed:", dep);
31 | if (dep.includes("@types/") || dep.includes("typescript")) {
32 | if (process.env.DEBUG) console.info("Detected TS on:", dep);
33 | return true;
34 | }
35 | }
36 | return false;
37 | }
38 |
39 | function getUserDeps(packageInfo) {
40 | return _.merge(packageInfo.dependencies, packageInfo.devDependencies);
41 | }
42 |
43 | function filterInstalledDeps(installDeps, existDeps) {
44 | const exist = Object.keys(existDeps);
45 | return Object.keys(installDeps).reduce(
46 | (result, dep) => (exist.includes(dep) ? result : { ...result, [dep]: installDeps[dep] }),
47 | {},
48 | );
49 | }
50 |
51 | function getPkgManger() {
52 | const pkgManagersNames = Object.keys(PkgMangers);
53 |
54 | const selectedPkgManagers = pkgManagersNames.reduce((result, pkgManager) => {
55 | const pkgManagerPath = path.resolve(PkgMangers[pkgManager].lock);
56 |
57 | try {
58 | const exist = fs.existsSync(pkgManagerPath);
59 | if (exist) return [...result, pkgManager];
60 | } catch (error) {}
61 |
62 | return result;
63 | }, []);
64 |
65 | if (selectedPkgManagers.length === 0) {
66 | log.error("Something wrong! No one package manager found in project! Stopped!");
67 | return null;
68 | }
69 |
70 | if (selectedPkgManagers.length > 1) {
71 | log.error("Something wrong! Find more then one package manager in project! Stopped!");
72 | return null;
73 | }
74 |
75 | return selectedPkgManagers[0];
76 | }
77 |
78 | function withPkgManager(cmdExecutor, pkgManager) {
79 | return function () {
80 | cmdExecutor.call(null, ...arguments, pkgManager);
81 | };
82 | }
83 |
84 | module.exports = {
85 | withPkgManager,
86 | getPkgManger,
87 | PkgMangers,
88 | basicPackages,
89 | depsPackages,
90 | typescriptPackages,
91 | getUserDeps,
92 | filterInstalledDeps,
93 | isTypeScriptProject,
94 | };
95 |
--------------------------------------------------------------------------------
/packages/cli/src/run.js:
--------------------------------------------------------------------------------
1 | const { log } = require("./log");
2 | const { spawnSync } = require("child_process");
3 | const { PkgMangers, withPkgManager } = require("./packages");
4 |
5 | function runCmdFactory(cmd, executor) {
6 | return function (cmdArgs) {
7 | executor.call(null, [cmd, cmdArgs]);
8 | };
9 | }
10 |
11 | function exec(cmd, pkgManager = null) {
12 | if (!pkgManager) {
13 | log.error("No one package manager found in cmd scope!");
14 | return;
15 | }
16 |
17 | log.info(`> install ${cmd.slice(-1)}`);
18 | if (process.env.DEBUG) return;
19 |
20 | try {
21 | const spawnResultBuffer = spawnSync(pkgManager, [...cmd], {
22 | shell: true,
23 | stdio: ["ignore", process.stdout, process.stderr],
24 | });
25 | } catch (error) {
26 | console.error(error);
27 | }
28 | }
29 |
30 | function installCmdBuilder(userPkgManager) {
31 | const installCmd = PkgMangers[userPkgManager].install;
32 | const userExec = withPkgManager(exec, userPkgManager);
33 | return runCmdFactory(installCmd, userExec);
34 | }
35 |
36 | function installDependencies(installFn, dependencies, dev = true) {
37 | const depsString = Object.keys(dependencies).reduce((result, dep) => {
38 | const version = dependencies[dep] && `@${dependencies[dep]}`;
39 | return `${result} "${dep + version}"`;
40 | }, "");
41 |
42 | const installArgs = `${dev && "-D"}${depsString}`;
43 |
44 | return installFn(installArgs);
45 | }
46 |
47 | module.exports = { exec, installDependencies, installCmdBuilder };
48 |
--------------------------------------------------------------------------------
/packages/cli/src/ui.js:
--------------------------------------------------------------------------------
1 | const prompts = require("prompts");
2 | const { log } = require("./log");
3 |
4 | const HELLO_MESSAGE = "Welcome to @feature-sliced/eslint-config installer.";
5 | const INSTALL_MESSAGE = "Run installation?";
6 | const TYPESCRIPT_MESSAGE =
7 | "Typescript detected in your project. Install additionally typescript dependencies?";
8 |
9 | const questions = [
10 | {
11 | type: "confirm",
12 | name: "install",
13 | message: INSTALL_MESSAGE,
14 | },
15 | ];
16 |
17 | const tsQuestions = [
18 | {
19 | type: "confirm",
20 | name: "typescript",
21 | message: TYPESCRIPT_MESSAGE,
22 | },
23 | ];
24 |
25 | async function ui(install, typescript) {
26 | const usedQuestions = typescript ? [...tsQuestions, ...questions] : questions;
27 |
28 | log.info(HELLO_MESSAGE);
29 |
30 | if (process.env.DEBUG === "2") {
31 | install({ withTs: true, force: true });
32 | return;
33 | }
34 |
35 | const answers = await prompts(usedQuestions);
36 | if (answers.install) {
37 | install({ withTs: answers.typescript });
38 | }
39 | }
40 |
41 | module.exports = { ui };
42 |
--------------------------------------------------------------------------------
/packages/cli/tests/index.test.js:
--------------------------------------------------------------------------------
1 | const assert = require("assert");
2 |
3 | it("Should run with normall output", async () => {
4 | const run = new Promise((resolve, reject) => {
5 | const spawn = require("child_process").spawn;
6 | const command = spawn("node", ["./src/index.js"]);
7 | let result = "";
8 | command.stdout.on("data", (data) => {
9 | result += data.toString();
10 | });
11 | command.on("close", (_) => {
12 | resolve(result);
13 | });
14 | command.on("error", (error) => {
15 | reject(error);
16 | });
17 | });
18 |
19 | await assert.doesNotReject(run);
20 |
21 | const result = await run;
22 |
23 | assert.strictEqual(result.includes("Done."), true);
24 | });
25 |
--------------------------------------------------------------------------------
/packages/cli/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@babel/code-frame@^7.10.4":
6 | version "7.16.7"
7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
8 | integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
9 | dependencies:
10 | "@babel/highlight" "^7.16.7"
11 |
12 | "@babel/helper-validator-identifier@^7.16.7":
13 | version "7.16.7"
14 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
15 | integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
16 |
17 | "@babel/highlight@^7.16.7":
18 | version "7.16.10"
19 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
20 | integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
21 | dependencies:
22 | "@babel/helper-validator-identifier" "^7.16.7"
23 | chalk "^2.0.0"
24 | js-tokens "^4.0.0"
25 |
26 | "@rollup/plugin-commonjs@^21.0.1":
27 | version "21.0.1"
28 | resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz#1e57c81ae1518e4df0954d681c642e7d94588fee"
29 | integrity sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==
30 | dependencies:
31 | "@rollup/pluginutils" "^3.1.0"
32 | commondir "^1.0.1"
33 | estree-walker "^2.0.1"
34 | glob "^7.1.6"
35 | is-reference "^1.2.1"
36 | magic-string "^0.25.7"
37 | resolve "^1.17.0"
38 |
39 | "@rollup/plugin-json@^4.1.0":
40 | version "4.1.0"
41 | resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
42 | integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==
43 | dependencies:
44 | "@rollup/pluginutils" "^3.0.8"
45 |
46 | "@rollup/plugin-node-resolve@^13.1.3":
47 | version "13.1.3"
48 | resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz#2ed277fb3ad98745424c1d2ba152484508a92d79"
49 | integrity sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==
50 | dependencies:
51 | "@rollup/pluginutils" "^3.1.0"
52 | "@types/resolve" "1.17.1"
53 | builtin-modules "^3.1.0"
54 | deepmerge "^4.2.2"
55 | is-module "^1.0.0"
56 | resolve "^1.19.0"
57 |
58 | "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
59 | version "3.1.0"
60 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
61 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
62 | dependencies:
63 | "@types/estree" "0.0.39"
64 | estree-walker "^1.0.1"
65 | picomatch "^2.2.2"
66 |
67 | "@types/estree@*":
68 | version "0.0.51"
69 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
70 | integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
71 |
72 | "@types/estree@0.0.39":
73 | version "0.0.39"
74 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
75 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
76 |
77 | "@types/node@*":
78 | version "17.0.18"
79 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074"
80 | integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==
81 |
82 | "@types/resolve@1.17.1":
83 | version "1.17.1"
84 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
85 | integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
86 | dependencies:
87 | "@types/node" "*"
88 |
89 | "@ungap/promise-all-settled@1.1.2":
90 | version "1.1.2"
91 | resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
92 | integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
93 |
94 | ansi-colors@4.1.1:
95 | version "4.1.1"
96 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
97 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
98 |
99 | ansi-regex@^5.0.1:
100 | version "5.0.1"
101 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
102 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
103 |
104 | ansi-styles@^3.2.1:
105 | version "3.2.1"
106 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
107 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
108 | dependencies:
109 | color-convert "^1.9.0"
110 |
111 | ansi-styles@^4.0.0, ansi-styles@^4.1.0:
112 | version "4.3.0"
113 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
114 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
115 | dependencies:
116 | color-convert "^2.0.1"
117 |
118 | anymatch@~3.1.2:
119 | version "3.1.2"
120 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
121 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
122 | dependencies:
123 | normalize-path "^3.0.0"
124 | picomatch "^2.0.4"
125 |
126 | argparse@^2.0.1:
127 | version "2.0.1"
128 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
129 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
130 |
131 | balanced-match@^1.0.0:
132 | version "1.0.2"
133 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
134 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
135 |
136 | binary-extensions@^2.0.0:
137 | version "2.2.0"
138 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
139 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
140 |
141 | brace-expansion@^1.1.7:
142 | version "1.1.11"
143 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
144 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
145 | dependencies:
146 | balanced-match "^1.0.0"
147 | concat-map "0.0.1"
148 |
149 | braces@~3.0.2:
150 | version "3.0.2"
151 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
152 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
153 | dependencies:
154 | fill-range "^7.0.1"
155 |
156 | browser-stdout@1.3.1:
157 | version "1.3.1"
158 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
159 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
160 |
161 | buffer-from@^1.0.0:
162 | version "1.1.2"
163 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
164 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
165 |
166 | builtin-modules@^3.1.0:
167 | version "3.2.0"
168 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
169 | integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==
170 |
171 | camelcase@^6.0.0:
172 | version "6.3.0"
173 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
174 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
175 |
176 | chalk@^2.0.0:
177 | version "2.4.2"
178 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
179 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
180 | dependencies:
181 | ansi-styles "^3.2.1"
182 | escape-string-regexp "^1.0.5"
183 | supports-color "^5.3.0"
184 |
185 | chalk@^4.1.0:
186 | version "4.1.2"
187 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
188 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
189 | dependencies:
190 | ansi-styles "^4.1.0"
191 | supports-color "^7.1.0"
192 |
193 | chokidar@3.5.3:
194 | version "3.5.3"
195 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
196 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
197 | dependencies:
198 | anymatch "~3.1.2"
199 | braces "~3.0.2"
200 | glob-parent "~5.1.2"
201 | is-binary-path "~2.1.0"
202 | is-glob "~4.0.1"
203 | normalize-path "~3.0.0"
204 | readdirp "~3.6.0"
205 | optionalDependencies:
206 | fsevents "~2.3.2"
207 |
208 | clear@^0.1.0:
209 | version "0.1.0"
210 | resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a"
211 | integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==
212 |
213 | cliui@^7.0.2:
214 | version "7.0.4"
215 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
216 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
217 | dependencies:
218 | string-width "^4.2.0"
219 | strip-ansi "^6.0.0"
220 | wrap-ansi "^7.0.0"
221 |
222 | color-convert@^1.9.0:
223 | version "1.9.3"
224 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
225 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
226 | dependencies:
227 | color-name "1.1.3"
228 |
229 | color-convert@^2.0.1:
230 | version "2.0.1"
231 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
232 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
233 | dependencies:
234 | color-name "~1.1.4"
235 |
236 | color-name@1.1.3:
237 | version "1.1.3"
238 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
239 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
240 |
241 | color-name@~1.1.4:
242 | version "1.1.4"
243 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
244 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
245 |
246 | commander@^2.20.0:
247 | version "2.20.3"
248 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
249 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
250 |
251 | commondir@^1.0.1:
252 | version "1.0.1"
253 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
254 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
255 |
256 | concat-map@0.0.1:
257 | version "0.0.1"
258 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
259 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
260 |
261 | debug@4.3.3:
262 | version "4.3.3"
263 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
264 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
265 | dependencies:
266 | ms "2.1.2"
267 |
268 | decamelize@^4.0.0:
269 | version "4.0.0"
270 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
271 | integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
272 |
273 | deepmerge@^4.2.2:
274 | version "4.2.2"
275 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
276 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
277 |
278 | diff@5.0.0:
279 | version "5.0.0"
280 | resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
281 | integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
282 |
283 | emoji-regex@^8.0.0:
284 | version "8.0.0"
285 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
286 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
287 |
288 | escalade@^3.1.1:
289 | version "3.1.1"
290 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
291 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
292 |
293 | escape-string-regexp@4.0.0:
294 | version "4.0.0"
295 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
296 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
297 |
298 | escape-string-regexp@^1.0.5:
299 | version "1.0.5"
300 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
301 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
302 |
303 | estree-walker@^1.0.1:
304 | version "1.0.1"
305 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
306 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
307 |
308 | estree-walker@^2.0.1:
309 | version "2.0.2"
310 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
311 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
312 |
313 | fill-range@^7.0.1:
314 | version "7.0.1"
315 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
316 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
317 | dependencies:
318 | to-regex-range "^5.0.1"
319 |
320 | find-up@5.0.0:
321 | version "5.0.0"
322 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
323 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
324 | dependencies:
325 | locate-path "^6.0.0"
326 | path-exists "^4.0.0"
327 |
328 | flat@^5.0.2:
329 | version "5.0.2"
330 | resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
331 | integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
332 |
333 | fs.realpath@^1.0.0:
334 | version "1.0.0"
335 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
336 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
337 |
338 | fsevents@~2.3.2:
339 | version "2.3.2"
340 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
341 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
342 |
343 | function-bind@^1.1.1:
344 | version "1.1.1"
345 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
346 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
347 |
348 | get-caller-file@^2.0.5:
349 | version "2.0.5"
350 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
351 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
352 |
353 | glob-parent@~5.1.2:
354 | version "5.1.2"
355 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
356 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
357 | dependencies:
358 | is-glob "^4.0.1"
359 |
360 | glob@7.2.0, glob@^7.1.6:
361 | version "7.2.0"
362 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
363 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
364 | dependencies:
365 | fs.realpath "^1.0.0"
366 | inflight "^1.0.4"
367 | inherits "2"
368 | minimatch "^3.0.4"
369 | once "^1.3.0"
370 | path-is-absolute "^1.0.0"
371 |
372 | growl@1.10.5:
373 | version "1.10.5"
374 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
375 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
376 |
377 | has-flag@^3.0.0:
378 | version "3.0.0"
379 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
380 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
381 |
382 | has-flag@^4.0.0:
383 | version "4.0.0"
384 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
385 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
386 |
387 | has@^1.0.3:
388 | version "1.0.3"
389 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
390 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
391 | dependencies:
392 | function-bind "^1.1.1"
393 |
394 | he@1.2.0:
395 | version "1.2.0"
396 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
397 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
398 |
399 | inflight@^1.0.4:
400 | version "1.0.6"
401 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
402 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
403 | dependencies:
404 | once "^1.3.0"
405 | wrappy "1"
406 |
407 | inherits@2:
408 | version "2.0.4"
409 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
410 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
411 |
412 | is-binary-path@~2.1.0:
413 | version "2.1.0"
414 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
415 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
416 | dependencies:
417 | binary-extensions "^2.0.0"
418 |
419 | is-core-module@^2.8.1:
420 | version "2.8.1"
421 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
422 | integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
423 | dependencies:
424 | has "^1.0.3"
425 |
426 | is-extglob@^2.1.1:
427 | version "2.1.1"
428 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
429 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
430 |
431 | is-fullwidth-code-point@^3.0.0:
432 | version "3.0.0"
433 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
434 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
435 |
436 | is-glob@^4.0.1, is-glob@~4.0.1:
437 | version "4.0.3"
438 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
439 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
440 | dependencies:
441 | is-extglob "^2.1.1"
442 |
443 | is-module@^1.0.0:
444 | version "1.0.0"
445 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
446 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
447 |
448 | is-number@^7.0.0:
449 | version "7.0.0"
450 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
451 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
452 |
453 | is-plain-obj@^2.1.0:
454 | version "2.1.0"
455 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
456 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
457 |
458 | is-reference@^1.2.1:
459 | version "1.2.1"
460 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
461 | integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
462 | dependencies:
463 | "@types/estree" "*"
464 |
465 | is-unicode-supported@^0.1.0:
466 | version "0.1.0"
467 | resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
468 | integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
469 |
470 | isexe@^2.0.0:
471 | version "2.0.0"
472 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
473 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
474 |
475 | jest-worker@^26.2.1:
476 | version "26.6.2"
477 | resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
478 | integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
479 | dependencies:
480 | "@types/node" "*"
481 | merge-stream "^2.0.0"
482 | supports-color "^7.0.0"
483 |
484 | js-tokens@^4.0.0:
485 | version "4.0.0"
486 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
487 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
488 |
489 | js-yaml@4.1.0:
490 | version "4.1.0"
491 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
492 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
493 | dependencies:
494 | argparse "^2.0.1"
495 |
496 | kleur@^3.0.3:
497 | version "3.0.3"
498 | resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
499 | integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
500 |
501 | locate-path@^6.0.0:
502 | version "6.0.0"
503 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
504 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
505 | dependencies:
506 | p-locate "^5.0.0"
507 |
508 | lodash@^4.17.21:
509 | version "4.17.21"
510 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
511 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
512 |
513 | log-symbols@4.1.0:
514 | version "4.1.0"
515 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
516 | integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
517 | dependencies:
518 | chalk "^4.1.0"
519 | is-unicode-supported "^0.1.0"
520 |
521 | magic-string@^0.25.7:
522 | version "0.25.7"
523 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
524 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
525 | dependencies:
526 | sourcemap-codec "^1.4.4"
527 |
528 | merge-stream@^2.0.0:
529 | version "2.0.0"
530 | resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
531 | integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
532 |
533 | minimatch@3.0.4:
534 | version "3.0.4"
535 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
536 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
537 | dependencies:
538 | brace-expansion "^1.1.7"
539 |
540 | minimatch@^3.0.4:
541 | version "3.1.2"
542 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
543 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
544 | dependencies:
545 | brace-expansion "^1.1.7"
546 |
547 | mocha@^9.2.1:
548 | version "9.2.1"
549 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.1.tgz#a1abb675aa9a8490798503af57e8782a78f1338e"
550 | integrity sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==
551 | dependencies:
552 | "@ungap/promise-all-settled" "1.1.2"
553 | ansi-colors "4.1.1"
554 | browser-stdout "1.3.1"
555 | chokidar "3.5.3"
556 | debug "4.3.3"
557 | diff "5.0.0"
558 | escape-string-regexp "4.0.0"
559 | find-up "5.0.0"
560 | glob "7.2.0"
561 | growl "1.10.5"
562 | he "1.2.0"
563 | js-yaml "4.1.0"
564 | log-symbols "4.1.0"
565 | minimatch "3.0.4"
566 | ms "2.1.3"
567 | nanoid "3.2.0"
568 | serialize-javascript "6.0.0"
569 | strip-json-comments "3.1.1"
570 | supports-color "8.1.1"
571 | which "2.0.2"
572 | workerpool "6.2.0"
573 | yargs "16.2.0"
574 | yargs-parser "20.2.4"
575 | yargs-unparser "2.0.0"
576 |
577 | ms@2.1.2:
578 | version "2.1.2"
579 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
580 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
581 |
582 | ms@2.1.3:
583 | version "2.1.3"
584 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
585 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
586 |
587 | nanoid@3.2.0:
588 | version "3.2.0"
589 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
590 | integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
591 |
592 | normalize-path@^3.0.0, normalize-path@~3.0.0:
593 | version "3.0.0"
594 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
595 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
596 |
597 | once@^1.3.0:
598 | version "1.4.0"
599 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
600 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
601 | dependencies:
602 | wrappy "1"
603 |
604 | p-limit@^3.0.2:
605 | version "3.1.0"
606 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
607 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
608 | dependencies:
609 | yocto-queue "^0.1.0"
610 |
611 | p-locate@^5.0.0:
612 | version "5.0.0"
613 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
614 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
615 | dependencies:
616 | p-limit "^3.0.2"
617 |
618 | path-exists@^4.0.0:
619 | version "4.0.0"
620 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
621 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
622 |
623 | path-is-absolute@^1.0.0:
624 | version "1.0.1"
625 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
626 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
627 |
628 | path-parse@^1.0.7:
629 | version "1.0.7"
630 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
631 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
632 |
633 | picocolors@^1.0.0:
634 | version "1.0.0"
635 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
636 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
637 |
638 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2:
639 | version "2.3.1"
640 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
641 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
642 |
643 | prompts@^2.4.2:
644 | version "2.4.2"
645 | resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
646 | integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
647 | dependencies:
648 | kleur "^3.0.3"
649 | sisteransi "^1.0.5"
650 |
651 | randombytes@^2.1.0:
652 | version "2.1.0"
653 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
654 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
655 | dependencies:
656 | safe-buffer "^5.1.0"
657 |
658 | readdirp@~3.6.0:
659 | version "3.6.0"
660 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
661 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
662 | dependencies:
663 | picomatch "^2.2.1"
664 |
665 | require-directory@^2.1.1:
666 | version "2.1.1"
667 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
668 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
669 |
670 | resolve@^1.17.0, resolve@^1.19.0:
671 | version "1.22.0"
672 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
673 | integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
674 | dependencies:
675 | is-core-module "^2.8.1"
676 | path-parse "^1.0.7"
677 | supports-preserve-symlinks-flag "^1.0.0"
678 |
679 | rollup-plugin-terser@^7.0.2:
680 | version "7.0.2"
681 | resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
682 | integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
683 | dependencies:
684 | "@babel/code-frame" "^7.10.4"
685 | jest-worker "^26.2.1"
686 | serialize-javascript "^4.0.0"
687 | terser "^5.0.0"
688 |
689 | rollup@^2.67.3:
690 | version "2.67.3"
691 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.67.3.tgz#3f04391fc296f807d067c9081d173e0a33dbd37e"
692 | integrity sha512-G/x1vUwbGtP6O5ZM8/sWr8+p7YfZhI18pPqMRtMYMWSbHjKZ/ajHGiM+GWNTlWyOR0EHIdT8LHU+Z4ciIZ1oBw==
693 | optionalDependencies:
694 | fsevents "~2.3.2"
695 |
696 | safe-buffer@^5.1.0:
697 | version "5.2.1"
698 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
699 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
700 |
701 | serialize-javascript@6.0.0:
702 | version "6.0.0"
703 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
704 | integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
705 | dependencies:
706 | randombytes "^2.1.0"
707 |
708 | serialize-javascript@^4.0.0:
709 | version "4.0.0"
710 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
711 | integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
712 | dependencies:
713 | randombytes "^2.1.0"
714 |
715 | sisteransi@^1.0.5:
716 | version "1.0.5"
717 | resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
718 | integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
719 |
720 | source-map-support@~0.5.20:
721 | version "0.5.21"
722 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
723 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
724 | dependencies:
725 | buffer-from "^1.0.0"
726 | source-map "^0.6.0"
727 |
728 | source-map@^0.6.0:
729 | version "0.6.1"
730 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
731 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
732 |
733 | source-map@~0.7.2:
734 | version "0.7.3"
735 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
736 | integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
737 |
738 | sourcemap-codec@^1.4.4:
739 | version "1.4.8"
740 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
741 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
742 |
743 | string-width@^4.1.0, string-width@^4.2.0:
744 | version "4.2.3"
745 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
746 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
747 | dependencies:
748 | emoji-regex "^8.0.0"
749 | is-fullwidth-code-point "^3.0.0"
750 | strip-ansi "^6.0.1"
751 |
752 | strip-ansi@^6.0.0, strip-ansi@^6.0.1:
753 | version "6.0.1"
754 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
755 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
756 | dependencies:
757 | ansi-regex "^5.0.1"
758 |
759 | strip-json-comments@3.1.1:
760 | version "3.1.1"
761 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
762 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
763 |
764 | supports-color@8.1.1:
765 | version "8.1.1"
766 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
767 | integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
768 | dependencies:
769 | has-flag "^4.0.0"
770 |
771 | supports-color@^5.3.0:
772 | version "5.5.0"
773 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
774 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
775 | dependencies:
776 | has-flag "^3.0.0"
777 |
778 | supports-color@^7.0.0, supports-color@^7.1.0:
779 | version "7.2.0"
780 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
781 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
782 | dependencies:
783 | has-flag "^4.0.0"
784 |
785 | supports-preserve-symlinks-flag@^1.0.0:
786 | version "1.0.0"
787 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
788 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
789 |
790 | terser@^5.0.0:
791 | version "5.10.0"
792 | resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc"
793 | integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==
794 | dependencies:
795 | commander "^2.20.0"
796 | source-map "~0.7.2"
797 | source-map-support "~0.5.20"
798 |
799 | to-regex-range@^5.0.1:
800 | version "5.0.1"
801 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
802 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
803 | dependencies:
804 | is-number "^7.0.0"
805 |
806 | which@2.0.2:
807 | version "2.0.2"
808 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
809 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
810 | dependencies:
811 | isexe "^2.0.0"
812 |
813 | workerpool@6.2.0:
814 | version "6.2.0"
815 | resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
816 | integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
817 |
818 | wrap-ansi@^7.0.0:
819 | version "7.0.0"
820 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
821 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
822 | dependencies:
823 | ansi-styles "^4.0.0"
824 | string-width "^4.1.0"
825 | strip-ansi "^6.0.0"
826 |
827 | wrappy@1:
828 | version "1.0.2"
829 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
830 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
831 |
832 | y18n@^5.0.5:
833 | version "5.0.8"
834 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
835 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
836 |
837 | yargs-parser@20.2.4:
838 | version "20.2.4"
839 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
840 | integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
841 |
842 | yargs-parser@^20.2.2:
843 | version "20.2.9"
844 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
845 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
846 |
847 | yargs-unparser@2.0.0:
848 | version "2.0.0"
849 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
850 | integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
851 | dependencies:
852 | camelcase "^6.0.0"
853 | decamelize "^4.0.0"
854 | flat "^5.0.2"
855 | is-plain-obj "^2.1.0"
856 |
857 | yargs@16.2.0:
858 | version "16.2.0"
859 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
860 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
861 | dependencies:
862 | cliui "^7.0.2"
863 | escalade "^3.1.1"
864 | get-caller-file "^2.0.5"
865 | require-directory "^2.1.1"
866 | string-width "^4.2.0"
867 | y18n "^5.0.5"
868 | yargs-parser "^20.2.2"
869 |
870 | yocto-queue@^0.1.0:
871 | version "0.1.0"
872 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
873 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
874 |
--------------------------------------------------------------------------------
/packages/processor/README.md:
--------------------------------------------------------------------------------
1 | # [@feature-sliced/eslint-plugin-messages](https://www.npmjs.com/package/@feature-sliced/eslint-plugin-messages)
2 |
3 | > `WIP:` At the moment at beta-testing - [use carefully](https://github.com/feature-sliced/eslint-config/discussions/75)
4 |
5 |
6 |
7 | Custom messages processing for [@feature-sliced/eslint-config](https://www.npmjs.com/package/@feature-sliced/eslint-config)
8 |
9 | - Methodology specific messages
10 | - Only important details
11 | - With documentation references
12 |
13 | ## Get Started
14 |
15 | 1. You'll first need to setup [@feature-sliced/eslint-config](https://www.npmjs.com/package/@feature-sliced/eslint-config)
16 |
17 | 2. Next, install `@feature-sliced/eslint-plugin-messages`
18 |
19 | ```sh
20 | $ npm install -D @feature-sliced/eslint-plugin-messages
21 | # or by yarn
22 | $ yarn add -D @feature-sliced/eslint-plugin-messages
23 | ```
24 |
25 | 3. Add config to the `plugins` and `processor` sections of your `.eslintrc` configuration file:
26 |
27 | ```json
28 | {
29 | "plugins": [
30 | ...
31 | "@feature-sliced/eslint-plugin-messages"
32 | ],
33 | "processor": "@feature-sliced/messages/fs",
34 | }
35 | ```
36 |
37 | 4. See upgraded messages 🍰
38 |
39 | ```js
40 | // Before
41 | > '"widgets" is not allowed to import "widgets" | See rules: https://feature-sliced.design/docs/reference/layers/overview'
42 | > 'Reaching to "features/search/ui" is not allowed.'
43 | > 'entities/auth/model` import should occur before import of `shared/config'
44 | // After
45 | > 'Violated isolation between layers or slices: "widgets" => "widgets" | https://git.io/Jymh2'
46 | > 'Violated usage of modules Public API | https://git.io/Jymjf'
47 | > 'Broken order of imports | https://git.io/JymjI'
48 | ```
49 |
50 | ## FAQ
51 |
52 | ### Why processor as plugin?
53 |
54 | Because of [ESlint restrictions](https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins)
55 |
--------------------------------------------------------------------------------
/packages/processor/index.js:
--------------------------------------------------------------------------------
1 | const { patchMessage } = require("./messages");
2 |
3 | module.exports = {
4 | processors: {
5 | "fs": {
6 | postprocess: function (messages, ...all) {
7 | return messages[0].map((message) => {
8 | return patchMessage(message);
9 | });
10 | },
11 | supportsAutofix: true,
12 | },
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/packages/processor/messages.js:
--------------------------------------------------------------------------------
1 | // TODO: https://gist.github.com/Krakazybik/53cebb2c763305be13e31042d59a7c72#file-gistfile1-js-L31
2 |
3 | const getRuleMessage = (msg) => {
4 | switch (msg.ruleId) {
5 | case 'import/order': {
6 | return {
7 | message: 'Broken order of imports | https://git.io/JymjI',
8 | }
9 | }
10 | case 'import/no-internal-modules': {
11 | return {
12 | message: 'Violated usage of modules Public API | https://git.io/Jymjf',
13 | }
14 | }
15 | case 'boundaries/element-types': {
16 | const { groups } = msg.message.match(/(?"\S+").+(?"\S+")/i);
17 | const from = groups?.from || '';
18 | const to = groups?.to || '';
19 | return {
20 | message: `Violated isolation between layers or slices: ${from} => ${to} | https://git.io/Jymh2`,
21 | }
22 | }
23 | }
24 | }
25 |
26 | const patchMessage = (rawMsg) => {
27 | const msg = getRuleMessage(rawMsg);
28 | if (!msg) return rawMsg;
29 |
30 | return { ...rawMsg, ...msg};
31 | }
32 |
33 | module.exports = { patchMessage };
34 |
--------------------------------------------------------------------------------
/packages/processor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@feature-sliced/eslint-plugin-messages",
3 | "version": "0.1.0-beta.2",
4 | "description": "🍰 Custom messages processor",
5 | "keywords": [
6 | "eslint",
7 | "eslintconfig",
8 | "eslint-config",
9 | "feature-sliced",
10 | "feature-slices",
11 | "feature-driven",
12 | "feature-based"
13 | ],
14 | "main": "index.js",
15 | "repository": "https://github.com/feature-sliced/eslint-config.git",
16 | "author": "FeatureSliced core-team",
17 | "license": "MIT"
18 | }
19 |
--------------------------------------------------------------------------------
/rules/import-order/README.md:
--------------------------------------------------------------------------------
1 | # @feature-sliced/import-order
2 |
3 | #### Reference: [Layers](https://feature-sliced.design/docs/reference/layers)
4 |
5 | ## Usage
6 |
7 | Add `"@feature-sliced/eslint-config/rules/import-order"` to your `extends` section in ESLint config.
8 |
9 | ```js
10 | // 👎 Fail
11 | import { getSmth } from "./lib";
12 | import axios from "axios";
13 | import { data } from "../fixtures";
14 | import { authModel } from "entities/auth";
15 | import { Button } from "shared/ui";
16 | import { LoginForm } from "features/login-form";
17 | import { Header } from "widgets/header";
18 | import { debounce } from "shared/lib/fp";
19 |
20 | // 👍 Pass
21 | import axios from "axios"; // 1) external libs
22 | import { Header } from "widgets/header"; // 2.1) Layers: widgets
23 | import { LoginForm } from "features/login-form"; // 2.2) Layers: features
24 | import { authModel } from "entities/auth"; // 2.3) Layers: entities
25 | import { Button } from "shared/ui"; // 2.4) Layers: shared
26 | import { debounce } from "shared/lib/fp"; // 2.4) Layers: shared
27 | import { data } from "../fixtures"; // 3) parent
28 | import { getSmth } from "./lib"; // 4) sibling
29 | ```
30 |
31 | > `WARN:` Rule supports layer-based imports, but [its recommended](../public-api) to prefer more specified imports
32 | >
33 | > ```js
34 | > import { ... } from "shared"; // Non-critical
35 | > import { ... } from "shared/ui"; // Better
36 | > import { ... } from "shared/ui/button"; // Perfect
37 | > ```
38 |
39 | ## Experimental
40 |
41 | **With reversed order ("from abstract to specific") and spaces between layers groups**
42 | [(why experimental?)](https://github.com/feature-sliced/eslint-config/issues/85)
43 |
44 | Add `"@feature-sliced/eslint-config/rules/import-order/experimental"` to your `extends` section in ESLint config.
45 |
46 | *Only for @^0.1.0-beta.4*
47 |
48 | ```js
49 | import axios from "axios"; // 1) external libs
50 |
51 | import { debounce } from "shared/lib/fp"; // 2.1) Layers: shared
52 | import { Button } from "shared/ui"; // 2.1) Layers: shared
53 |
54 | import { authModel } from "entities/auth"; // 2.2) Layers: entities
55 |
56 | import { LoginForm } from "features/login-form"; // 2.3) Layers: features
57 |
58 | import { Header } from "widgets/header"; // 2.4) Layers: widgets
59 |
60 | import { data } from "../fixtures"; // 3) parent
61 | import { getSmth } from "./lib"; // 4) sibling
62 | ```
63 |
--------------------------------------------------------------------------------
/rules/import-order/experimental.js:
--------------------------------------------------------------------------------
1 | const { layersLib } = require("../../utils");
2 | const REVERSED_FS_LAYERS = [...layersLib.FS_LAYERS].reverse();
3 |
4 | module.exports = {
5 | plugins: [
6 | "import",
7 | ],
8 | rules: {
9 | "import/order": [
10 | 2,
11 | {
12 | alphabetize: {
13 | order: 'asc',
14 | caseInsensitive: true,
15 | },
16 | pathGroupsExcludedImportTypes: ["builtin"],
17 | groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
18 |
19 | // experimental features
20 | 'newlines-between': 'always',
21 | pathGroups: REVERSED_FS_LAYERS.map(
22 | (layer) => ({
23 | pattern: `**/?(*)${layer}{,/**}`,
24 | group: "internal",
25 | position: "after",
26 | }),
27 | ),
28 | },
29 | ],
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/rules/import-order/experimental.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require("./experimental");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(cfg),
9 | });
10 |
11 | describe("Import order experimental:", () => {
12 |
13 | it("should lint with errors.", async () => {
14 | const report = await eslint.lintText(`
15 | import { Cart } from "@/entities/cart";
16 | import { Input } from "~/shared/ui";
17 | import { getSmth } from "./lib";
18 | import axios from "axios";
19 | import { data } from "../fixtures";
20 | import { authModel } from "entities/auth";
21 | import { Button } from "shared/ui";
22 | import { LoginForm } from "features/login-form";
23 | import { Header } from "widgets/header";
24 | import { debounce } from "shared/lib/fp";
25 | import { One } from "@entities/one";
26 | import { Two } from "~entities/two";
27 | `);
28 |
29 | assert.strictEqual(report[0].errorCount, 18);
30 | });
31 |
32 | it("should lint without errors.", async () => {
33 | const report = await eslint.lintText(`
34 | import axios from "axios";
35 |
36 | import { Shared } from "shared";
37 | import { debounce } from "shared/lib/fp";
38 | import { model } from "shared/model";
39 | import { Button } from "shared/ui";
40 |
41 | import { globalEntities } from "entities";
42 | import { authModel } from "entities/auth";
43 | import { Cart } from "entities/cart";
44 | import { One } from "entities/one";
45 | import { Two } from "entities/two";
46 |
47 | import { LoginForm } from "features/login-form";
48 |
49 | import { Widgets } from "widgets";
50 | import { Header } from "widgets/header";
51 | import { Zero } from "widgets/zero";
52 |
53 | import { data } from "../fixtures";
54 |
55 | import { getSmth } from "./lib";
56 | `);
57 |
58 | assert.strictEqual(report[0].errorCount, 0);
59 | });
60 |
61 |
62 | it("~aliased should lint without errors.", async () => {
63 | const report = await eslint.lintText(`
64 | import axios from "axios";
65 |
66 | import { debounce } from "~shared/lib/fp";
67 | import { model } from "~shared/model";
68 | import { Button } from "~shared/ui";
69 |
70 | import { authModel } from "~entities/auth";
71 | import { Cart } from "~entities/cart";
72 | import { One } from "~entities/one";
73 | import { Two } from "~entities/two";
74 |
75 | import { LoginForm } from "~features/login-form";
76 |
77 | import { Widgets } from "~widgets";
78 | import { Header } from "~widgets/header";
79 | import { Zero } from "~widgets/zero";
80 |
81 | import { data } from "../fixtures";
82 |
83 | import { getSmth } from "./lib";
84 | `);
85 |
86 | assert.strictEqual(report[0].errorCount, 0);
87 | });
88 |
89 |
90 | it("~/aliased should lint without errors.", async () => {
91 | const report = await eslint.lintText(`
92 | import axios from "axios";
93 |
94 | import { debounce } from "~/shared/lib/fp";
95 | import { model } from "~/shared/model";
96 | import { Button } from "~/shared/ui";
97 |
98 | import { authModel } from "~/entities/auth";
99 | import { Cart } from "~/entities/cart";
100 | import { One } from "~/entities/one";
101 | import { Two } from "~/entities/two";
102 |
103 | import { LoginForm } from "~/features/login-form";
104 |
105 | import { Widgets } from "~/widgets";
106 | import { Header } from "~/widgets/header";
107 | import { Zero } from "~/widgets/zero";
108 |
109 | import { data } from "../fixtures";
110 |
111 | import { getSmth } from "./lib";
112 | `);
113 |
114 | assert.strictEqual(report[0].errorCount, 0);
115 | });
116 |
117 |
118 | it("should be alphabetic", async () => {
119 | const report = await eslint.lintText(`
120 | import { Apple } from 'features/apple';
121 | import { Bee } from 'features/bee';
122 | import { Cord } from 'features/cord';
123 | import { Dream } from 'features/dream';
124 | `);
125 |
126 | assert.strictEqual(report[0].errorCount, 0);
127 | });
128 |
129 | it("should be alphabetic error", async () => {
130 | const report = await eslint.lintText(`
131 | import { Dream } from 'features/dream';
132 | import { Cord } from 'features/cord';
133 | import { Bee } from 'features/bee';
134 | import { Apple } from 'features/apple';
135 | `);
136 |
137 | assert.strictEqual(report[0].errorCount, 3);
138 | });
139 |
140 | it("should be with spaces between layers", async () => {
141 | const report = await eslint.lintText(`
142 | import { Dream } from 'shared/dream';
143 |
144 | import { Cord } from 'entities/cord';
145 |
146 | import { Bee } from 'features/bee';
147 |
148 | import { Apple } from 'app/apple';
149 | `);
150 |
151 | assert.strictEqual(report[0].errorCount, 0);
152 | });
153 |
154 | it("should be with spaces between layers errors", async () => {
155 | const report = await eslint.lintText(`
156 | import React from 'react';
157 | import { Dream } from 'shared/dream';
158 | import { Cord } from 'entities/cord';
159 | import { Bee } from 'features/bee';
160 | import { Apple } from 'app/apple';
161 | `);
162 |
163 | assert.strictEqual(report[0].errorCount, 4);
164 | });
165 | });
166 |
--------------------------------------------------------------------------------
/rules/import-order/index.js:
--------------------------------------------------------------------------------
1 | const { layersLib } = require("../../utils");
2 |
3 | module.exports = {
4 | plugins: [
5 | "import",
6 | ],
7 | rules: {
8 | "import/order": [
9 | 2,
10 | {
11 | alphabetize: {
12 | order: 'asc',
13 | caseInsensitive: true,
14 | },
15 | pathGroups: layersLib.FS_LAYERS.map(
16 | (layer) => ({
17 | pattern: `**/?(*)${layer}{,/**}`,
18 | group: "internal",
19 | position: "after",
20 | }),
21 | ),
22 | pathGroupsExcludedImportTypes: ["builtin"],
23 | groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
24 | },
25 | ],
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/rules/import-order/index.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require("./");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(cfg),
9 | });
10 |
11 | describe("Import order:", () => {
12 |
13 | it("should lint with errors.", async () => {
14 | const report = await eslint.lintText(`
15 | import { Cart } from "@/entities/cart"; // 5
16 | import { Input } from "~/shared/ui"; // 3.1
17 | import { getSmth } from "./lib"; // 1
18 | import axios from "axios"; // 10
19 | import { data } from "../fixtures"; // 2
20 | import { authModel } from "entities/auth"; // 5
21 | import { Button } from "shared/ui"; // 4
22 | import { LoginForm } from "features/login-form"; // 8
23 | import { Header } from "widgets/header"; // 9
24 | import { debounce } from "shared/lib/fp"; // 3
25 | import { One } from "@entities/one"; // 6
26 | import { Two } from "~entities/two"; // 7
27 | `);
28 |
29 | assert.strictEqual(report[0].errorCount, 8);
30 | });
31 |
32 | it("should lint without errors.", async () => {
33 | const report = await eslint.lintText(`
34 | // warn: specific order in mixed alias ~/layer => ~layer => layer
35 | import axios from "axios"; // 1) external libs
36 | import { Header } from "widgets/header"; // 2.1) Layers: widgets
37 | import { Zero } from "widgets/zero"; // 2.1) Layers: widget
38 | import { LoginForm } from "features/login-form"; // 2.2) Layers: features
39 | import { globalEntities } from "entities"; // 2.4) Layers: entities
40 | import { authModel } from "entities/auth"; // 2.4) Layers: entities
41 | import { Cart } from "entities/cart"; // 2.4) Layers: entities
42 | import { One } from "entities/one"; // 2.4) Layers: entities
43 | import { Two } from "entities/two"; // 2.4) Layers: entities
44 | import { debounce } from "shared/lib/fp"; // 2.5) Layers: shared
45 | import { Button } from "shared/ui"; // 2.5) Layers: shared
46 | import { Input } from "shared/ui"; // 2.5) Layers: shared
47 | import { data } from "../fixtures"; // 3) parent
48 | import { getSmth } from "./lib"; // 4) sibling
49 | `);
50 |
51 | assert.strictEqual(report[0].errorCount, 0);
52 | });
53 |
54 |
55 | it("should lint without errors.", async () => {
56 | const report = await eslint.lintText(`
57 | // warn: specific order in mixed alias ~/layer => ~layer => layer
58 | // not used in real, but test aliases support
59 | import axios from "axios"; // 1) external libs
60 | import { Zero } from "@widgets/zero"; // 2.1) Layers: widget - Alias
61 | import { Widgets } from "widgets"; // 2.1) Layers: widgets
62 | import { Header } from "widgets/header"; // 2.1) Layers: widgets
63 | import { LoginForm } from "features/login-form"; // 2.2) Layers: features
64 | import { Cart } from "@/entities/cart"; // 2.3) Layers: entities - Alias
65 | import { One } from "@entities/one"; // 2.3) Layers: entities - Alias
66 | import { Two } from "@entities/two"; // 2.3) Layers: entities - Alias
67 | import { authModel } from "entities/auth"; // 2.3) Layers: entities
68 | import { Shared } from "shared"; // 2.4) Layers: shared
69 | import { debounce } from "shared/lib/fp"; // 2.4) Layers: shared
70 | import { Button } from "shared/ui"; // 2.4) Layers: shared
71 | import { Input } from "~/shared/ui"; // 2.4) Layers: shared - Alias
72 | import { data } from "../fixtures"; // 3) parent
73 | import { getSmth } from "./lib"; // 4) sibling
74 | `);
75 |
76 | assert.strictEqual(report[0].errorCount, 0);
77 | });
78 |
79 | it("aliased layers should lint with errors.", async () => {
80 | const report = await eslint.lintText(`
81 | import { Third } from '@shared/third';
82 | import { Second } from '@entities/second';
83 | import { First } from '@features/first';
84 | `);
85 |
86 | assert.strictEqual(report[0].errorCount, 2);
87 | });
88 |
89 | it("aliased layers should lint without errors.", async () => {
90 | const report = await eslint.lintText(`
91 | import { Widgets } from "@widgets";
92 | import { First } from '@features/first';
93 | import { Second } from '@entities/second';
94 | import { Third } from '@shared/third';
95 | `);
96 |
97 | assert.strictEqual(report[0].errorCount, 0);
98 | });
99 |
100 | it("~aliased should lint without errors.", async () => {
101 | const report = await eslint.lintText(`
102 | import axios from "axios";
103 | import { Widgets } from "~widgets";
104 | import { Header } from "~widgets/header";
105 | import { Zero } from "~widgets/zero";
106 | import { LoginForm } from "~features/login-form";
107 | import { authModel } from "~entities/auth";
108 | import { Cart } from "~entities/cart";
109 | import { One } from "~entities/one";
110 | import { Two } from "~entities/two";
111 | import { debounce } from "~shared/lib/fp";
112 | import { model } from "~shared/model";
113 | import { Button } from "~shared/ui";
114 | import { data } from "../fixtures";
115 | import { getSmth } from "./lib";
116 | `);
117 |
118 | assert.strictEqual(report[0].errorCount, 0);
119 | });
120 |
121 |
122 | it("~/aliased should lint without errors.", async () => {
123 | const report = await eslint.lintText(`
124 | import axios from "axios";
125 | import { Widgets } from "~/widgets";
126 | import { Header } from "~/widgets/header";
127 | import { Zero } from "~/widgets/zero";
128 | import { LoginForm } from "~/features/login-form";
129 | import { authModel } from "~/entities/auth";
130 | import { Cart } from "~/entities/cart";
131 | import { One } from "~/entities/one";
132 | import { Two } from "~/entities/two";
133 | import { debounce } from "~/shared/lib/fp";
134 | import { model } from "~/shared/model";
135 | import { Button } from "~/shared/ui";
136 | import { data } from "../fixtures";
137 | import { getSmth } from "./lib";
138 | `);
139 |
140 | assert.strictEqual(report[0].errorCount, 0);
141 | });
142 |
143 | describe("Alphabetic sort feature:", () => {
144 |
145 | it("should be alphabetic", async () => {
146 | const report = await eslint.lintText(`
147 | import { Apple } from 'features/apple';
148 | import { Bee } from 'features/bee';
149 | import { Cord } from 'features/cord';
150 | import { Dream } from 'features/dream';
151 | `);
152 |
153 | assert.strictEqual(report[0].errorCount, 0);
154 | });
155 |
156 | it("should be alphabetic error", async () => {
157 | const report = await eslint.lintText(`
158 | import { Dream } from 'features/dream';
159 | import { Cord } from 'features/cord';
160 | import { Bee } from 'features/bee';
161 | import { Apple } from 'features/apple';
162 | `);
163 |
164 | assert.strictEqual(report[0].errorCount, 3);
165 | });
166 |
167 | })
168 |
169 | });
170 |
--------------------------------------------------------------------------------
/rules/integration.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../utils");
4 | const cfg = require("../");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.mockImports(cfg),
9 | });
10 |
11 | describe("Integration tests:", () => {
12 | it("Global config should lint with errors", async () => {
13 | const report = await eslint.lintText(`
14 | import { getSmth } from "./lib"; // import-order
15 | import axios from "axios";
16 | import { data } from "../fixtures"; // import-order
17 | import { authModel } from "entities/auth"; // import-order
18 | import { Button } from "shared/ui"; // import-order
19 | import { LoginForm } from "features/login-form"; // import-order
20 | import { Header } from "widgets/header"; // import-order, import-boundaries
21 | import { debounce } from "shared/lib/fp"; // import-order
22 | import { AuthPage } from "pages/auth"; // import-boundaries
23 | import { IssueDetails } from "widgets/issue-details/ui/details"; // import-order, publicAPI
24 | `, {
25 | filePath: "src/widgets/mock/index.js",
26 | });
27 |
28 | assert.strictEqual(report[0].errorCount, 11);
29 | });
30 |
31 | it("Global config should lint without errors", async () => {
32 | const report = await eslint.lintText(`
33 | import { getRoute } from "pages/auth";
34 | import { Header } from "widgets/header";
35 | import { LoginForm } from "features/login-form";
36 | import { Phone } from "features/login-form/phone";
37 | import { Article } from "entities/article";
38 | import { LoginAPI } from "shared/api";
39 | import { Button } from "shared/ui/button";
40 | import { model } from "../model";
41 | import { styles } from "./styles.module.scss";
42 | `, { filePath: "src/app/ui/index.js" });
43 |
44 | assert.strictEqual(report[0].errorCount, 0);
45 | });
46 |
47 | it("Global config should lint only with import-order error", async () => {
48 | const report = await eslint.lintText(`
49 | import { LoginAPI } from "shared/api";
50 | import { getRoute } from "pages/auth";
51 | `, { filePath: "src/app/ui/index.js" });
52 |
53 | assert.strictEqual(report[0].errorCount, 1);
54 | });
55 |
56 | it("Global config should lint only with layer error", async () => {
57 | const report = await eslint.lintText(`
58 | import { LoginForm } from "features/login-form";
59 | `, { filePath: "src/entities/ui/index.js" });
60 |
61 | assert.strictEqual(report[0].errorCount, 1);
62 | });
63 |
64 | it("Global config should lint only with slice error", async () => {
65 | const report = await eslint.lintText(`
66 | import { Article } from "entities/article";
67 | `, { filePath: "src/entities/avatar/ui/index.js" });
68 |
69 | assert.strictEqual(report[0].errorCount, 1);
70 | });
71 |
72 | it("Global config should lint only with PublicAPI error", async () => {
73 | const report = await eslint.lintText(`
74 | import { orderModel } from "entities/order/model";
75 | `, { filePath: "src/features/profile/ui/index.js" });
76 |
77 | assert.strictEqual(report[0].errorCount, 1);
78 | });
79 |
80 | it("Global config should pass with global node_modules", async () => {
81 | const report = await eslint.lintText(`
82 | import { orderModel } from "home/work/npm/node_modules/packages/custom/ci/index.js";
83 | import { Something } from "home/work/npm/node_modules/packages/fancy-ui-kiy/some/index.js";
84 | import { useDelay } from "home/work/npm/node_modules/packages/reduxium/use-delay/index.js";
85 | `, { filePath: "src/features/profile/ui/index.js" });
86 |
87 | assert.strictEqual(report[0].errorCount, 0);
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/rules/integration.ts.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../utils");
4 | const cfg = require("../");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setTSParser(
9 | configLib.mockImports(cfg, "ts")
10 | ),
11 | });
12 |
13 | describe("TypeScript integration tests:", () => {
14 | it("Global config with TS should lint with errors", async () => {
15 | const report = await eslint.lintText(`
16 | import { getSmth } from "./lib"; // import-order
17 | import axios from "axios";
18 | import { data } from "../fixtures"; // import-order
19 | import { authModel } from "entities/auth"; // import-order
20 | import { Button } from "shared/ui"; // import-order
21 | import { LoginForm } from "features/login-form"; // import-order
22 | import { Header } from "widgets/header"; // import-order, import-boundaries
23 | import { debounce } from "shared/lib/fp"; // import-order
24 | import { AuthPage } from "pages/auth"; // import-boundaries
25 | import { IssueDetails } from "widgets/issue-details/ui/details"; // import-order, publicAPI
26 |
27 | interface IConfig {
28 | path: string;
29 | };
30 |
31 | const configs: Array = [];
32 | `, {
33 | filePath: "src/widgets/mock/index.ts",
34 | });
35 |
36 | assert.strictEqual(report[0].errorCount, 11);
37 | });
38 |
39 | it("Global config with TS should lint without errors", async () => {
40 | const report = await eslint.lintText(`
41 | import { getRoute } from "pages/auth";
42 | import { Header } from "widgets/header";
43 | import { LoginForm } from "features/login-form";
44 | import { Phone } from "features/login-form/phone";
45 | import { Article } from "entities/article";
46 | import { LoginAPI } from "shared/api";
47 | import { Button } from "shared/ui/button";
48 | import { model } from "../model";
49 | import { styles } from "./styles.module.scss";
50 |
51 | interface IConfig {
52 | path: string;
53 | };
54 |
55 | const configs: Array = [];
56 | `, { filePath: "src/app/ui/index.ts" });
57 |
58 | assert.strictEqual(report[0].errorCount, 0);
59 | });
60 |
61 | it("Global config with TS should lint only with import-order error", async () => {
62 | const report = await eslint.lintText(`
63 | import { LoginAPI } from "shared/api";
64 | import { getRoute } from "pages/auth";
65 | const configs: Array = [];
66 | `, { filePath: "src/app/ui/index.ts" });
67 |
68 | assert.strictEqual(report[0].errorCount, 1);
69 | });
70 |
71 | it("Global config with TS should lint only with layer error", async () => {
72 | const report = await eslint.lintText(`
73 | import { LoginForm } from "features/login-form";
74 | const configs: Array = [];
75 | `, { filePath: "src/entities/ui/index.ts" });
76 |
77 | assert.strictEqual(report[0].errorCount, 1);
78 | });
79 |
80 | it("Global config with TS should lint only with slice error", async () => {
81 | const report = await eslint.lintText(`
82 | import { Article } from "entities/article";
83 | const configs: Array = [];
84 | `, { filePath: "src/entities/avatar/ui/index.ts" });
85 |
86 | assert.strictEqual(report[0].errorCount, 1);
87 | });
88 |
89 | it("Global config with TS should lint only with PublicAPI error", async () => {
90 | const report = await eslint.lintText(`
91 | import { orderModel } from "entities/order/model";
92 | const configs: Array = [];
93 | `, { filePath: "src/features/profile/ui/index.ts" });
94 |
95 | assert.strictEqual(report[0].errorCount, 1);
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/rules/layers-slices/README.md:
--------------------------------------------------------------------------------
1 | # @feature-sliced/layers-slices
2 |
3 | #### Reference: [Cross-communication](https://feature-sliced.design/docs/concepts/cross-communication)
4 |
5 | ## Usage
6 |
7 | Add `"@feature-sliced/eslint-config/rules/layers-slices"` to your `extends` section in ESLint config.
8 |
9 | ```js
10 | // 👎 Fail
11 | // 🛣 features/auth-form/index.ts
12 | import { getRoute } from "pages/auth";
13 | import { getStore } from "app/store";
14 | import { getAuthCtx } from "features/logout";
15 | import { UserAvatar } from "features/viewer-picker";
16 |
17 | // 👍 Pass
18 | // 🛣 features/auth-form/index.ts
19 | import { sessionModel } from "entities/session";
20 | import { Form, Button } from "shared/ui";
21 | import { getAuthCtx } from "entities/session";
22 | import { UserAvatar } from "entities/user";
23 | ```
24 |
25 | ---
26 |
27 | > ⚠️ **DANGEROUS-mode**: Support service directories for slices by `_` prefix ([why?](https://github.com/feature-sliced/eslint-config/discussions/75#discussioncomment-2056223))
28 | >
29 | > Use carefully and at your own risk
30 | >
31 | > ```js
32 | > import { ... } from "../HomePage";
33 | > import { ... } from "../ProfilePage";
34 | >
35 | > // Imported into ...
36 | > @path "app/**" // 🟩 valid (upper layer)
37 | > @path "shared/router" // 🟥 not valid (lower layer)
38 | > @path "pages/CartPage" // 🟥 not valid (sibling slice)
39 | > @path "pages/router" // 🟥 not valid (sibling slice)
40 | > @path "pages/_router" // 🟩 again valid (as service directory/slice)
41 | > ```
42 | >
43 | > But still actual:
44 | >
45 | > ```js
46 | > @path "pages/_router"
47 | > import { ... } from "app" // 🟥 not valid (lower layer)
48 | >
49 | > @path "shared/lib"
50 | > import { ... } from "pages/_router" // 🟥 not valid (lower layer)
51 | > ```
52 | >
53 | > *Only for @^0.1.0-beta.6*
54 |
--------------------------------------------------------------------------------
/rules/layers-slices/index.js:
--------------------------------------------------------------------------------
1 | const { layersLib } = require("../../utils");
2 |
3 | const getNotSharedLayersRules = () =>
4 | layersLib.getUpperLayers("shared").map((layer) => ({
5 | from: layer,
6 | allow: layersLib.getLowerLayers(layer),
7 | }));
8 |
9 | const slicelessLayerRules = [
10 | {
11 | from: "shared",
12 | allow: "shared",
13 | },
14 | {
15 | from: "app",
16 | allow: "app",
17 | }
18 | ];
19 |
20 | const getLayersBoundariesElements = () =>
21 | layersLib.FS_LAYERS.map((layer) => ({
22 | type: layer,
23 | pattern: `${layer}/!(_*){,/*}`,
24 | mode: "folder",
25 | capture: ["slices"],
26 | }));
27 |
28 | const getGodModeRules = () =>
29 | layersLib.FS_LAYERS.map((layer) => ({
30 | from: `gm_${layer}`,
31 | allow: [layer, ...layersLib.getLowerLayers(layer)]
32 | }));
33 |
34 | const getGodModeElements = () =>
35 | layersLib.FS_LAYERS.map((layer) => ({
36 | type: `gm_${layer}`,
37 | pattern: `${layer}/_*`,
38 | mode: "folder",
39 | capture: ["slices"],
40 | }));
41 |
42 | module.exports = {
43 | plugins: ["boundaries"],
44 | extends: ["plugin:boundaries/recommended"],
45 | ignorePatterns: [".eslintrc.js"],
46 | settings: {
47 | "boundaries/elements": [...getLayersBoundariesElements(), ...getGodModeElements()],
48 | },
49 | rules: {
50 | "boundaries/element-types": [
51 | 2,
52 | {
53 | "default": "disallow",
54 | "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ",
55 | "rules": [...getNotSharedLayersRules(), ...slicelessLayerRules, ...getGodModeRules()],
56 | },
57 | ],
58 | },
59 | };
60 |
--------------------------------------------------------------------------------
/rules/layers-slices/layers.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require("./");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(
9 | configLib.mockImports(cfg)
10 | ),
11 | });
12 |
13 | describe("Import boundaries between layers", () => {
14 |
15 | describe("IDDQD boundaries", () => {
16 |
17 | it("should lint without errors in GodMode for _computed entities", async () => {
18 | const report = await eslint.lintText(`
19 | import { userModel } from "entities/user";
20 | import { getUser } from "shared/api/user-api";
21 | `,
22 | {filePath: "src/entities/_computed/UserPost/model.js"});
23 |
24 | assert.strictEqual(report[0].errorCount, 0);
25 | });
26 |
27 | it("should lint with errors for computed entities", async () => {
28 | const report = await eslint.lintText(`
29 | import { userModel } from "entities/user";
30 | `,
31 | {filePath: "src/entities/computed/UserPost/model.js"});
32 |
33 | assert.strictEqual(report[0].errorCount, 1);
34 | });
35 |
36 | it("should lint without errors in GodMode for pages", async () => {
37 | const report = await eslint.lintText(`
38 | import { FooPage } from "pages/foo";
39 | import { BagFeature } from "features/bag";
40 | import { format } from "shared/lib/format";
41 | import { BarPage } from "../bar";
42 | `,
43 | {filePath: "src/pages/_router/private.routes.js"});
44 |
45 | assert.strictEqual(report[0].errorCount, 0);
46 | });
47 |
48 | it("should lint with errors in GodMode for upper layers", async () => {
49 | const report = await eslint.lintText(`
50 | import { MainPage } from "pages/main";
51 | import { UserFeature } from "features/user";
52 | `,
53 | {filePath: "src/entities/_computed/UserPost/model.js"});
54 |
55 | assert.strictEqual(report[0].errorCount, 2);
56 | });
57 |
58 |
59 | it("should lint with errors without GodMode for pages", async () => {
60 | const report = await eslint.lintText(`
61 | import { FooPage } from "pages/foo";
62 | `,
63 | {filePath: "src/pages/router/private.routes.js"});
64 |
65 | assert.strictEqual(report[0].errorCount, 1);
66 | });
67 | })
68 |
69 | it("should lint with cross-import errors.", async () => {
70 | const wrongImports = [
71 | `import { getRoute } from "pages/auth";`,
72 | `import { getStore } from "app/store";`,
73 | ];
74 |
75 | const report = await eslint.lintText(wrongImports.join("\n"), {
76 | filePath: "src/shared/lib/index.js",
77 | });
78 | assert.strictEqual(report[0].errorCount, wrongImports.length);
79 | });
80 |
81 | it("should lint without errors.", async () => {
82 | const validCodeSnippet = [
83 | `import { sessionModel } from "entities/session";`,
84 | `import { Form, Button } from "shared/ui";`,
85 | ].join("\n");
86 |
87 | const report = await eslint.lintText(validCodeSnippet, {
88 | filePath: "src/app/ui/app.js",
89 | });
90 | assert.strictEqual(report[0].errorCount, 0);
91 | });
92 |
93 | it("should lint without errors when import from shared.", async () => {
94 | const validCodeSnippet = [
95 | `import { API_TOKEN } from "shared/config"`,
96 | `import { Form } from "shared/ui";`,
97 | ].join("\n");
98 |
99 | const report = await eslint.lintText(validCodeSnippet, {
100 | filePath: "src/shared/ui/button.js",
101 | });
102 | assert.strictEqual(report[0].errorCount, 0);
103 | });
104 |
105 | it("should lint without errors when import from app.", async () => {
106 | const validCodeSnippet = [
107 | `import "app/styles/styles.css"`,
108 | `import { withProviders } from "app/providers"`,
109 | ].join("\n");
110 |
111 | const report = await eslint.lintText(validCodeSnippet, {
112 | filePath: "src/app/ui/app.tsx",
113 | });
114 | assert.strictEqual(report[0].errorCount, 0);
115 | });
116 |
117 | it("should lint with errors when import from app.", async () => {
118 | const wrongImports = [
119 | `import { withProviders } from "app/providers"`,
120 | ];
121 |
122 | const report = await eslint.lintText(wrongImports.join("\n"), {
123 | filePath: "src/features/add-user/model.tsx",
124 | });
125 | assert.strictEqual(report[0].errorCount, wrongImports.length);
126 | });
127 | });
128 |
--------------------------------------------------------------------------------
/rules/layers-slices/slices.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require(".");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(
9 | configLib.mockImports(cfg)
10 | ),
11 | });
12 |
13 | describe("Import boundaries between slices and layers", () => {
14 |
15 | it("should lint with cross-import errors between pages.", async () => {
16 | const wrongImports = [
17 | `import { getAuthCtx } from "pages/logout";`,
18 | `import { UserAvatar } from "pages/auth";`,
19 | ];
20 |
21 | const report = await eslint.lintText(wrongImports.join("\n"), {
22 | filePath: "src/pages/map/index.js",
23 | });
24 |
25 | assert.strictEqual(report[0].errorCount, wrongImports.length);
26 | });
27 |
28 | it("should lint with cross-import errors between widgets.", async () => {
29 | const wrongImports = [
30 | `import { HeaderTitle } from "widgets/header";`,
31 | `import { Links } from "widgets/footer";`,
32 | ];
33 |
34 | const report = await eslint.lintText(wrongImports.join("\n"), {
35 | filePath: "src/widgets/mock/index.js",
36 | });
37 |
38 | assert.strictEqual(report[0].errorCount, wrongImports.length);
39 | });
40 |
41 | it("should lint with cross-import errors between features.", async () => {
42 | const wrongImports = [
43 | `import { getAuthCtx } from "features/logout";`,
44 | `import { UserAvatar } from "features/viewer-picker";`,
45 | ];
46 |
47 | const report = await eslint.lintText(wrongImports.join("\n"), {
48 | filePath: "features/auth-form/index.js",
49 | });
50 |
51 | assert.strictEqual(report[0].errorCount, wrongImports.length);
52 | });
53 |
54 | it("should lint with cross-import errors between entities.", async () => {
55 | const wrongImports = [
56 | `import { LoginForm } from "features/login-form";`,
57 | `import { Avatar } from "features/avatar";`,
58 | ];
59 |
60 | const report = await eslint.lintText(wrongImports.join("\n"), {
61 | filePath: "src/entities/mock/index.js",
62 | });
63 |
64 | assert.strictEqual(report[0].errorCount, wrongImports.length);
65 | });
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/rules/public-api/README.md:
--------------------------------------------------------------------------------
1 | # @feature-sliced/public-api
2 |
3 | #### Reference: [PublicAPI](https://feature-sliced.design/docs/concepts/public-api)
4 |
5 | ## Usage
6 |
7 | Add `"@feature-sliced/eslint-config/rules/public-api"` to your `extends` section in ESLint config.
8 |
9 | #### Slices PublicAPI
10 |
11 | ```js
12 | // 👎 Fail
13 | import { Issues } from "pages/issues/ui";
14 | import { IssueDetails } from "widgets/issue-details/ui/details"
15 | import { AuthForm } from "features/auth-form/ui/form"
16 | import { Button } from "shared/ui/button/button";
17 | import { saveOrder } from "entities/order/model/actions";
18 | import { orderModel } from "entities/order/model";
19 | import { TicketCard } from "@/entities/ticket/ui";
20 |
21 | // 👍 Pass
22 | import { Issues } from "pages/issues";
23 | import { IssueDetails } from "widgets/issue-details"
24 | import { AuthForm } from "features/auth-form"
25 | import { Button } from "shared/ui/button";
26 | import { orderModel } from "entities/order";
27 | import { TicketCard } from "@/entities/ticket";
28 | import { AuthForm } from "features/auth/form"
29 | import { Button } from "shared/ui";
30 | ```
31 |
32 | #### Segments PublicAPI
33 |
34 | ```js
35 | // 👍 Pass
36 | /** @path features/smth/index.ts */
37 | export { SubmitButton, SmthForm } from "./ui";
38 | export * from "./model";
39 | export * as smthModel from "./model";
40 | export { selectSmthById, ... } from "./model";
41 |
42 | // 👎 Fail
43 | /** @path features/smth/index.ts */
44 | export { SubmitButton } from "./ui/button";
45 | export { SmthForm } from "./ui/form";
46 | export * from "./model/actions";
47 | export { selectSmthById } from "./model/selectors";
48 | ```
49 |
50 | ## Lite
51 |
52 | **Without SegmentsAPI / InnerAPI restrictions** [(why experimental?)](https://github.com/feature-sliced/eslint-config/issues/90)
53 |
54 | Add `"@feature-sliced/eslint-config/rules/public-api/lite"` to your `extends` section in ESLint config.
55 |
56 | *Only for @^0.1.0-beta.5*
57 |
58 | #### Slices PublicAPI
59 |
60 | Without changes
61 |
62 | ```js
63 | // 👍 Pass
64 | import { orderModel } from "entities/order";
65 | // 👎 Fail
66 | import { orderModel } from "entities/order/model";
67 | ```
68 |
69 | #### Segments PublicAPI
70 |
71 | Less restricted with segments
72 |
73 | ```js
74 | // 👍 Pass
75 | /** @path features/smth/index.ts */
76 | export { SubmitButton, SmthForm } from "./ui";
77 | export * from "./model";
78 | export * as smthModel from "./model";
79 | export { selectSmthById, ... } from "./model";
80 |
81 | // 👍 Also Pass
82 | /** @path features/smth/index.ts */
83 | export { SubmitButton } from "./ui/button";
84 | export { SmthForm } from "./ui/form";
85 | export * from "./model/actions";
86 | export { selectSmthById } from "./model/selectors";
87 | ```
88 |
89 | ---
90 |
91 | > ⚠️ **DANGEROUS-mode**: Support custom segments at shared by `_` prefix ([why?](https://github.com/feature-sliced/eslint-config/discussions/75#discussioncomment-1972319))
92 | >
93 | > Use carefully and at your own risk
94 | >
95 | > ```js
96 | > import { ... } from "shared/lib" // 🟩 valid
97 | > import { ... } from "shared/library" // 🟥 not valid
98 | >
99 | > import { ... } from "shared/_library" // 🟩 again valid
100 | > import { ... } from "shared/_library/fp" // 🟩 still valid
101 | > import { ... } from "shared/_library/fp/compose" // 🟥 don't be brash :)
102 | > ```
103 | >
104 | > *Only for @^0.1.0-beta.6*
105 |
--------------------------------------------------------------------------------
/rules/public-api/index.js:
--------------------------------------------------------------------------------
1 | const { layersLib } = require("../../utils");
2 |
3 | const FS_SLICED_LAYERS_REG = layersLib.getUpperLayers("shared").join("|");
4 | const FS_SEGMENTS_REG = [
5 | ...layersLib.FS_SEGMENTS,
6 | ...layersLib.FS_SEGMENTS.map((seg) => `${seg}.*`),
7 | ].join('|');
8 |
9 | module.exports = {
10 | plugins: ["import"],
11 | rules: {
12 | "import/no-internal-modules": [
13 | "error", {
14 | "allow": [
15 | /**
16 | * Allow not segments import from slices
17 | * @example
18 | * 'entities/form/ui' // Fail
19 | * 'entities/form' // Pass
20 | */
21 | `**/*(${FS_SLICED_LAYERS_REG})/!(${FS_SEGMENTS_REG})`,
22 |
23 | /**
24 | * Allow slices with structure grouping
25 | * @example
26 | * 'features/auth/form' // Pass
27 | */
28 | `**/*(${FS_SLICED_LAYERS_REG})/!(${FS_SEGMENTS_REG})/!(${FS_SEGMENTS_REG})`,
29 |
30 | /**
31 | * Allow not segments import in shared segments
32 | * @example
33 | * 'shared/ui/button' // Pass
34 | */
35 | `**/*shared/*(${FS_SEGMENTS_REG})/!(${FS_SEGMENTS_REG})`,
36 |
37 | /**
38 | * Allow import from segments in shared
39 | * @example
40 | * 'shared/ui' // Pass
41 | */
42 | `**/*shared/*(${FS_SEGMENTS_REG})`,
43 |
44 | /** allow global modules */
45 | `**/node_modules/**`,
46 |
47 | /**
48 | * allow custom shared segments with _prefix
49 | */
50 | `**/*shared/_*`,
51 | `**/*shared/_*/*`
52 | ],
53 | }],
54 | },
55 | };
56 |
--------------------------------------------------------------------------------
/rules/public-api/index.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require(".");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(
9 | configLib.mockImports(cfg),
10 | ),
11 | });
12 |
13 | describe("Allow publicAPI for shared segments with _prefix:", () => {
14 | it("with _prefix should lint without errors", async () => {
15 | const report = await eslint.lintText(`
16 | import { One } from "shared/_route/one";
17 | import { Two } from "@shared/_note/two";
18 | import { Route } from "shared/_route";
19 | `,
20 | {filePath: "src/app/ui/index.js"});
21 | assert.strictEqual(report[0].errorCount, 0);
22 | });
23 |
24 | it("import inner module with _prefix should lint with errors", async () => {
25 | const report = await eslint.lintText(`
26 | import { Five } from "@shared/_note/two/five";
27 | import { Four } from "shared/_note/three/four";
28 | import { Route } from "shared/route";
29 | `,
30 | {filePath: "src/app/ui/index.js"});
31 | assert.strictEqual(report[0].errorCount, 3);
32 | });
33 |
34 | it("without prefix should lint with errors", async () => {
35 | const report = await eslint.lintText(`
36 | import { One } from "shared/route/one";
37 | import { Two } from "@shared/note/two";
38 | `,
39 | {filePath: "src/app/ui/index.js"});
40 | assert.strictEqual(report[0].errorCount, 2);
41 | });
42 | });
43 |
44 | describe("PublicAPI import boundaries:", () => {
45 | it("Should lint PublicAPI boundaries with errors.", async () => {
46 | const report = await eslint.lintText(`
47 | import { Issues } from "pages/issues/ui";
48 | import { IssueDetails } from "widgets/issue-details/ui/details";
49 | import { AuthForm } from "features/auth-form/ui/form";
50 | import { Button } from "shared/ui/button/button";
51 | import { saveOrder } from "entities/order/model/actions";
52 | import { orderModel } from "entities/order/model";
53 | import { TicketCard } from "@src/entities/ticket/ui";
54 | import { Ticket } from "@src/entities/ticket/ui.tsx";
55 | `,
56 | { filePath: "src/app/ui/index.js" });
57 | assert.strictEqual(report[0].errorCount, 8);
58 | });
59 |
60 | it("Should lint PublicAPI boundaries without errors.", async () => {
61 | const report = await eslint.lintText(`
62 | import { Issues } from "pages/issues";
63 | import { GoodIssues } from "@src/pages/issues";
64 | import { IssueDetails } from "widgets/issue-details";
65 | import { AuthForm } from "features/auth-form";
66 | import { Button } from "shared/ui/button";
67 | import { orderModel } from "entities/order";
68 | import { TicketCard } from "@/entities/ticket";
69 | import { FixButton } from "@src/shared/ui/fix-button";
70 | `, { filePath: "src/app/ui/index.js" });
71 | assert.strictEqual(report[0].errorCount, 0);
72 | });
73 |
74 | it("Should lint extra PublicAPI boundaries cases without errors.", async () => {
75 | const report = await eslint.lintText(`
76 | import { AuthForm } from "features/auth/form";
77 | import { Button } from "shared/ui";
78 | `, { filePath: "src/app/ui/index.js" });
79 |
80 | assert.strictEqual(report[0].errorCount, 0);
81 | });
82 |
83 | describe("Allow not segments import from slices:", () => {
84 | it("should lint without errors", async () => {
85 | const report = await eslint.lintText(`
86 | import { AuthForm } from "entities/auth";
87 | import { model } from "../model";
88 | import { styles } from "./styles.module.scss";
89 | `, { filePath: "src/features/form/ui/index.js" });
90 |
91 | assert.strictEqual(report[0].errorCount, 0);
92 | });
93 |
94 | it("should lint with errors", async () => {
95 | const report = await eslint.lintText(`
96 | import { AuthForm } from "entities/auth/ui";
97 | import { Button } from "shared/button";
98 | `, { filePath: "src/features/form/ui/index.js" });
99 |
100 | assert.strictEqual(report[0].errorCount, 2);
101 | });
102 | });
103 |
104 | describe("Allow slices with structure grouping:", () => {
105 | it("should lint with errors", async () => {
106 | const report = await eslint.lintText(`
107 | import { AuthForm } from "entities/auth/form";
108 | `, { filePath: "src/features/form/ui/index.js" });
109 |
110 | assert.strictEqual(report[0].errorCount, 0);
111 | });
112 |
113 | it("should lint without errors", async () => {
114 | const report = await eslint.lintText(`
115 | import { AuthForm } from "entities/auth/ui";
116 | import { Form } from "shared/button/form";
117 | `, { filePath: "src/features/form/ui/index.js" });
118 |
119 | assert.strictEqual(report[0].errorCount, 2);
120 | });
121 | });
122 |
123 | describe("Allow not segments import in shared segments:", () => {
124 | it("should lint without errors", async () => {
125 | const report = await eslint.lintText(`
126 | import { Form } from "shared/ui/form";
127 | import { AuthAPI } from "shared/api/auth";
128 | import { useGeo } from "shared/lib/hooks";
129 | import { styles } from "shared/ui/styles";
130 | import { lockSound } from "shared/assets";
131 | import { CONNECT_ATTEMPTS } from "shared/config";
132 | `, { filePath: "src/features/form/ui/index.js" });
133 |
134 | assert.strictEqual(report[0].errorCount, 0);
135 | });
136 |
137 | it("should lint with errors", async () => {
138 | const report = await eslint.lintText(`
139 | import { Hex } from "shared/api/ui";
140 | import { Form } from "shared/ui/lib";
141 | import { AuthForm } from "shared/api/ui";
142 | import { lockSound } from "shared/assets/ui";
143 | import { model } from "shared/ui/model";
144 | `, { filePath: "src/features/form/ui/index.js" });
145 |
146 | assert.strictEqual(report[0].errorCount, 5);
147 | });
148 |
149 | it("should lint without errors", async () => {
150 | const report = await eslint.lintText(`
151 | import { FancyLabel } from "../../label";
152 | import { model } from "../model";
153 | import { lockSound } from "../assets";
154 | `, { filePath: "src/shared/ui/button/index.js" });
155 |
156 | assert.strictEqual(report[0].errorCount, 0);
157 | });
158 |
159 | it("should lint aliases without errors", async () => {
160 | const report = await eslint.lintText(`
161 | import { routeNames } from '@/entities/one';
162 | import { fetchRules } from '@entities/two';
163 | import { Three } from '@features/three';
164 | import { Four } from '@/features/four';
165 | `, { filePath: "src/pages/main/ui/index.js" });
166 |
167 | assert.strictEqual(report[0].errorCount, 0);
168 | });
169 | });
170 |
171 | describe("Import from segments in shared:", () => {
172 | it("should lint without errors", async () => {
173 | const report = await eslint.lintText(`
174 | import { AuthAPI } from "shared/api";
175 | import { FancyLabel } from 'shared/ui';
176 | import { convertToken } from 'shared/lib';
177 | import { CONNECT_ATTEMPTS } from "shared/config";
178 | import { lockSound } from "shared/assets";
179 | `, { filePath: "src/pages/main/ui/index.js" });
180 |
181 | assert.strictEqual(report[0].errorCount, 0);
182 | });
183 |
184 | it("should lint with errors", async () => {
185 | const report = await eslint.lintText(`
186 | import { AuthAPI } from "shared/auth";
187 | import { FancyLabel } from 'shared/label';
188 | import { convertToken } from 'shared/token';
189 | import { CONNECT_ATTEMPTS } from "shared/const";
190 | `, { filePath: "src/pages/main/ui/index.js" });
191 |
192 | assert.strictEqual(report[0].errorCount, 4);
193 | });
194 |
195 | it("should lint shared aliases without errors", async () => {
196 | const report = await eslint.lintText(`
197 | import { routeNames } from '@/shared/api/router';
198 | import { fetchRules } from '@shared/api/rules';
199 | import { lockSound } from "shared/assets/sounds";
200 | `, { filePath: "src/pages/main/ui/index.js" });
201 |
202 | assert.strictEqual(report[0].errorCount, 0);
203 | });
204 | });
205 | });
206 |
--------------------------------------------------------------------------------
/rules/public-api/lite.js:
--------------------------------------------------------------------------------
1 | const { layersLib } = require("../../utils");
2 |
3 | const FS_SLICED_LAYERS_REG = layersLib.getUpperLayers("shared").join("|");
4 | const FS_SEGMENTS_REG = [
5 | ...layersLib.FS_SEGMENTS,
6 | ...layersLib.FS_SEGMENTS.map((seg) => `${seg}.*`),
7 | ].join('|');
8 |
9 | module.exports = {
10 | plugins: ["import"],
11 | rules: {
12 | "import/no-internal-modules": [
13 | "error", {
14 | "allow": [
15 | /**
16 | * Allow not segments import from slices
17 | * @example
18 | * 'entities/form/ui' // Fail
19 | * 'entities/form' // Pass
20 | */
21 | `**/*(${FS_SLICED_LAYERS_REG})/!(${FS_SEGMENTS_REG})`,
22 |
23 | /**
24 | * Allow slices with structure grouping
25 | * @example
26 | * 'features/auth/form' // Pass
27 | */
28 | `**/*(${FS_SLICED_LAYERS_REG})/!(${FS_SEGMENTS_REG})/!(${FS_SEGMENTS_REG})`,
29 |
30 | /**
31 | * Allow not segments import in shared segments
32 | * @example
33 | * 'shared/ui/button' // Pass
34 | */
35 | `**/*shared/*(${FS_SEGMENTS_REG})/!(${FS_SEGMENTS_REG})`,
36 |
37 | /**
38 | * Allow import from segments in shared
39 | * @example
40 | * 'shared/ui' // Pass
41 | */
42 | `**/*shared/*(${FS_SEGMENTS_REG})`,
43 |
44 | /** allow global modules */
45 | `**/node_modules/**`,
46 |
47 | /**
48 | * allow custom shared segments with _prefix
49 | */
50 | `**/*shared/_*`,
51 | `**/*shared/_*/*`,
52 |
53 | /**
54 | * Used for allow import local modules
55 | * @example
56 | * './model/something' // Pass
57 | */
58 | `./**`
59 |
60 | ],
61 | }],
62 | },
63 | };
64 |
--------------------------------------------------------------------------------
/rules/public-api/lite.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require("./lite");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(
9 | configLib.mockImports(cfg),
10 | ),
11 | });
12 |
13 | describe("Lite PublicAPI:", () => {
14 | it("local segments should be ignored in Lite config.", async () => {
15 | const report = await eslint.lintText(`
16 | export { SubmitButton } from "./ui/button";
17 | export { SmthForm } from "./ui/form";
18 | export * from "./model/actions";
19 | export { selectSmthById } from "./model/selectors";
20 | `,
21 | { filePath: "src/features/smth/index.ts" });
22 | assert.strictEqual(report[0].errorCount, 0);
23 | });
24 |
25 | it("should lint with errors.", async () => {
26 | const report = await eslint.lintText(`
27 | import { Button } from "../shared/ui/button/button";
28 | import { Date } from "../shared/lib/date/date";
29 | `,
30 | {filePath: "src/features/index.ts"});
31 | assert.strictEqual(report[0].errorCount, 2);
32 | });
33 |
34 | it("should lint without errors when local import in shared layer.", async () => {
35 | const report = await eslint.lintText(`
36 | import { Button } from "./button/button";
37 | `,
38 | {filePath: "src/shared/ui/index.ts"});
39 | assert.strictEqual(report[0].errorCount, 0);
40 | });
41 |
42 | it("should lint with error when layer import in shared layer.", async () => {
43 | const report = await eslint.lintText(`
44 | import { Button } from "shared/ui/button/button";
45 | `,
46 | {filePath: "src/shared/ui/index.ts"});
47 | assert.strictEqual(report[0].errorCount, 1);
48 | });
49 | })
50 |
51 | describe("Allow publicAPI for shared segments with _prefix:", () => {
52 | it("with _prefix should lint without errors", async () => {
53 | const report = await eslint.lintText(`
54 | import { One } from "shared/_route/one";
55 | import { Two } from "@shared/_note/two";
56 | import { Route } from "shared/_route";
57 | `,
58 | {filePath: "src/app/ui/index.js"});
59 | assert.strictEqual(report[0].errorCount, 0);
60 | });
61 |
62 | it("import inner module with _prefix should lint with errors", async () => {
63 | const report = await eslint.lintText(`
64 | import { Five } from "@shared/_note/two/five";
65 | import { Four } from "shared/_note/three/four";
66 | import { Route } from "shared/route";
67 | `,
68 | {filePath: "src/app/ui/index.js"});
69 | assert.strictEqual(report[0].errorCount, 3);
70 | });
71 |
72 | it("without prefix should lint with errors", async () => {
73 | const report = await eslint.lintText(`
74 | import { One } from "shared/route/one";
75 | import { Two } from "@shared/note/two";
76 | `,
77 | {filePath: "src/app/ui/index.js"});
78 | assert.strictEqual(report[0].errorCount, 2);
79 | });
80 | });
81 |
82 | describe("PublicAPI import boundaries:", () => {
83 | it("Should lint PublicAPI boundaries with errors.", async () => {
84 | const report = await eslint.lintText(`
85 | import { Issues } from "pages/issues/ui";
86 | import { IssueDetails } from "widgets/issue-details/ui/details";
87 | import { AuthForm } from "features/auth-form/ui/form";
88 | import { Button } from "shared/ui/button/button";
89 | import { saveOrder } from "entities/order/model/actions";
90 | import { orderModel } from "entities/order/model";
91 | import { TicketCard } from "@src/entities/ticket/ui";
92 | import { Ticket } from "@src/entities/ticket/ui.tsx";
93 | `,
94 | { filePath: "src/app/ui/index.js" });
95 | assert.strictEqual(report[0].errorCount, 8);
96 | });
97 |
98 | it("Should lint PublicAPI boundaries without errors.", async () => {
99 | const report = await eslint.lintText(`
100 | import { Issues } from "pages/issues";
101 | import { GoodIssues } from "@src/pages/issues";
102 | import { IssueDetails } from "widgets/issue-details";
103 | import { AuthForm } from "features/auth-form";
104 | import { Button } from "shared/ui/button";
105 | import { orderModel } from "entities/order";
106 | import { TicketCard } from "@/entities/ticket";
107 | import { FixButton } from "@src/shared/ui/fix-button";
108 | `, { filePath: "src/app/ui/index.js" });
109 | assert.strictEqual(report[0].errorCount, 0);
110 | });
111 |
112 | it("Should lint extra PublicAPI boundaries cases without errors.", async () => {
113 | const report = await eslint.lintText(`
114 | import { AuthForm } from "features/auth/form";
115 | import { Button } from "shared/ui";
116 | `, { filePath: "src/app/ui/index.js" });
117 |
118 | assert.strictEqual(report[0].errorCount, 0);
119 | });
120 |
121 | describe("Allow not segments import from slices:", () => {
122 | it("should lint without errors", async () => {
123 | const report = await eslint.lintText(`
124 | import { AuthForm } from "entities/auth";
125 | import { model } from "../model";
126 | import { styles } from "./styles.module.scss";
127 | `, { filePath: "src/features/form/ui/index.js" });
128 |
129 | assert.strictEqual(report[0].errorCount, 0);
130 | });
131 |
132 | it("should lint with errors", async () => {
133 | const report = await eslint.lintText(`
134 | import { AuthForm } from "entities/auth/ui";
135 | import { Button } from "shared/button";
136 | `, { filePath: "src/features/form/ui/index.js" });
137 |
138 | assert.strictEqual(report[0].errorCount, 2);
139 | });
140 | });
141 |
142 | describe("Allow slices with structure grouping:", () => {
143 | it("should lint with errors", async () => {
144 | const report = await eslint.lintText(`
145 | import { AuthForm } from "entities/auth/form";
146 | `, { filePath: "src/features/form/ui/index.js" });
147 |
148 | assert.strictEqual(report[0].errorCount, 0);
149 | });
150 |
151 | it("should lint without errors", async () => {
152 | const report = await eslint.lintText(`
153 | import { AuthForm } from "entities/auth/ui";
154 | import { Form } from "shared/button/form";
155 | `, { filePath: "src/features/form/ui/index.js" });
156 |
157 | assert.strictEqual(report[0].errorCount, 2);
158 | });
159 | });
160 |
161 | describe("Allow not segments import in shared segments:", () => {
162 | it("should lint without errors", async () => {
163 | const report = await eslint.lintText(`
164 | import { Form } from "shared/ui/form";
165 | import { AuthAPI } from "shared/api/auth";
166 | import { useGeo } from "shared/lib/hooks";
167 | import { styles } from "shared/ui/styles";
168 | import { CONNECT_ATTEMPTS } from "shared/config";
169 | `, { filePath: "src/features/form/ui/index.js" });
170 |
171 | assert.strictEqual(report[0].errorCount, 0);
172 | });
173 |
174 | it("should lint with errors", async () => {
175 | const report = await eslint.lintText(`
176 | import { Hex } from "shared/api/ui";
177 | import { Form } from "shared/ui/lib";
178 | import { AuthForm } from "shared/api/ui";
179 | import { model } from "shared/ui/model";
180 | `, { filePath: "src/features/form/ui/index.js" });
181 |
182 | assert.strictEqual(report[0].errorCount, 4);
183 | });
184 |
185 | it("should lint without errors", async () => {
186 | const report = await eslint.lintText(`
187 | import { FancyLabel } from "../../label";
188 | import { model } from "../model";
189 | `, { filePath: "src/shared/ui/button/index.js" });
190 |
191 | assert.strictEqual(report[0].errorCount, 0);
192 | });
193 |
194 | it("should lint aliases without errors", async () => {
195 | const report = await eslint.lintText(`
196 | import { routeNames } from '@/entities/one';
197 | import { fetchRules } from '@entities/two';
198 | import { Three } from '@features/three';
199 | import { Four } from '@/features/four';
200 | `, { filePath: "src/pages/main/ui/index.js" });
201 |
202 | assert.strictEqual(report[0].errorCount, 0);
203 | });
204 | });
205 |
206 | describe("Import from segments in shared:", () => {
207 | it("should lint without errors", async () => {
208 | const report = await eslint.lintText(`
209 | import { AuthAPI } from "shared/api";
210 | import { FancyLabel } from 'shared/ui';
211 | import { convertToken } from 'shared/lib';
212 | import { CONNECT_ATTEMPTS } from "shared/config";
213 | `, { filePath: "src/pages/main/ui/index.js" });
214 |
215 | assert.strictEqual(report[0].errorCount, 0);
216 | });
217 |
218 | it("should lint with errors", async () => {
219 | const report = await eslint.lintText(`
220 | import { AuthAPI } from "shared/auth";
221 | import { FancyLabel } from 'shared/label';
222 | import { convertToken } from 'shared/token';
223 | import { CONNECT_ATTEMPTS } from "shared/const";
224 | `, { filePath: "src/pages/main/ui/index.js" });
225 |
226 | assert.strictEqual(report[0].errorCount, 4);
227 | });
228 |
229 | it("should lint shared aliases without errors", async () => {
230 | const report = await eslint.lintText(`
231 | import { routeNames } from '@/shared/api/router';
232 | import { fetchRules } from '@shared/api/rules';
233 | `, { filePath: "src/pages/main/ui/index.js" });
234 |
235 | assert.strictEqual(report[0].errorCount, 0);
236 | });
237 | });
238 |
239 | describe("Segment PublicAPI:", () => {
240 | it("Should lint Segment PublicAPI boundaries without errors.", async () => {
241 | const report = await eslint.lintText(`
242 | export { SubmitButton, SmthForm } from "./ui";
243 | export * from "./model";
244 | export { selectSmthById, selectSmthByName } from "./model";
245 | `, { filePath: "src/features/smth/index.ts" });
246 |
247 | assert.strictEqual(report[0].errorCount, 0);
248 | });
249 |
250 | describe("Exclusive Segments Public API for shared layer:", () => {
251 | it("Should lint with errors.", async () => {
252 | const report = await eslint.lintText(`
253 | import { Button } from "shared/ui/button/button";
254 | import { Date } from "shared/lib/date/date";
255 | `,
256 | { filePath: "src/features/smth/index.ts" });
257 | assert.strictEqual(report[0].errorCount, 2);
258 | });
259 |
260 | it("Should lint without errors.", async () => {
261 | const report = await eslint.lintText(`
262 | import { Button } from "shared/ui/button";
263 | import { SexyButton } from "shared/ui";
264 | import { Parser } from "shared/lib";
265 | import { Date } from "shared/lib/date";
266 | `, { filePath: "src/features/smth/index.ts" });
267 |
268 | assert.strictEqual(report[0].errorCount, 0);
269 | });
270 | });
271 |
272 | });
273 |
274 | });
275 |
--------------------------------------------------------------------------------
/rules/public-api/segment-public-api.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const { configLib } = require("../../utils");
4 | const cfg = require(".");
5 |
6 | const eslint = new ESLint({
7 | useEslintrc: false,
8 | baseConfig: configLib.setParser(
9 | configLib.mockImports(cfg),
10 | ),
11 | });
12 |
13 | describe("Segment PublicAPI:", () => {
14 | it("Should lint Segment PublicAPI boundaries with errors.", async () => {
15 | const report = await eslint.lintText(`
16 | export { SubmitButton } from "./ui/button";
17 | export { SmthForm } from "./ui/form";
18 | export * from "./model/actions";
19 | export { selectSmthById } from "./model/selectors";
20 | `,
21 | { filePath: "src/features/smth/index.ts" });
22 | assert.strictEqual(report[0].errorCount, 4);
23 | });
24 |
25 | it("Should lint Segment PublicAPI boundaries without errors.", async () => {
26 | const report = await eslint.lintText(`
27 | export { SubmitButton, SmthForm } from "./ui";
28 | export * from "./model";
29 | export { selectSmthById, selectSmthByName } from "./model";
30 | `, { filePath: "src/features/smth/index.ts" });
31 |
32 | assert.strictEqual(report[0].errorCount, 0);
33 | });
34 |
35 | describe("Exclusive Segments Public API for shared layer:", () => {
36 | it("Should lint with errors.", async () => {
37 | const report = await eslint.lintText(`
38 | import { Button } from "shared/ui/button/button";
39 | import { Date } from "shared/lib/date/date";
40 | `,
41 | { filePath: "src/features/smth/index.ts" });
42 | assert.strictEqual(report[0].errorCount, 2);
43 | });
44 |
45 | it("Should lint without errors.", async () => {
46 | const report = await eslint.lintText(`
47 | import { Button } from "shared/ui/button";
48 | import { SexyButton } from "shared/ui";
49 | import { Parser } from "shared/lib";
50 | import { Date } from "shared/lib/date";
51 | `, { filePath: "src/features/smth/index.ts" });
52 |
53 | assert.strictEqual(report[0].errorCount, 0);
54 | });
55 | });
56 |
57 | });
58 |
--------------------------------------------------------------------------------
/test/config.test.js:
--------------------------------------------------------------------------------
1 | const assert = require("assert");
2 | const { typesLib } = require("../utils");
3 | const cfg = require("../");
4 |
5 | describe("config is valid", () => {
6 | it("Valid parerOptions should presented in global config.", () => {
7 | assert.ok(typesLib.isObj(cfg.parserOptions));
8 | Object.entries(cfg.parserOptions).forEach(([key, value]) => {
9 | assert.ok(typesLib.isString(key));
10 | assert.ok(typesLib.isString(value));
11 | });
12 | });
13 |
14 | it("Global config should extends other config.", () => {
15 | assert.ok(typesLib.isArray(cfg.extends));
16 | });
17 |
18 | it("All extended configs should be presented.", () => {
19 | cfg.extends.forEach((configPath) => {
20 | const config = require(configPath);
21 | assert.ok(config);
22 | });
23 | });
24 |
25 | it("All extended configs plugins should be presented as Array's", () => {
26 | cfg.extends.forEach((configPath) => {
27 | const config = require(configPath);
28 | assert.ok(typesLib.isArray(config.plugins));
29 | });
30 | });
31 |
32 | it("All extended configs rules should be with name and value", () => {
33 | cfg.extends.forEach((configPath) => {
34 | const config = require(configPath);
35 | assert.ok(typesLib.isObj(config.rules));
36 | Object.entries(config.rules).forEach(([ruleName, ruleOptions]) => {
37 | assert.ok(typesLib.isString(ruleName));
38 | assert.ok(
39 | typesLib.isNumber(ruleOptions) ||
40 | typesLib.isArray(ruleOptions) ||
41 | typesLib.isObj(ruleOptions),
42 | );
43 | });
44 | });
45 | });
46 | // TODO: eslint.isValid
47 | // NOTE: maybe will need... in future...
48 | // t.ok(isObject(config.parserOptions))
49 | // t.ok(isObject(config.env))
50 | // t.ok(isObject(config.globals))
51 | // t.ok(isObject(config.rules))
52 | });
53 |
--------------------------------------------------------------------------------
/test/lint.test.js:
--------------------------------------------------------------------------------
1 | const { ESLint } = require("eslint");
2 | const assert = require("assert");
3 | const cfg = require("..");
4 |
5 | const eslint = new ESLint({
6 | useEslintrc: false,
7 | baseConfig: cfg,
8 | });
9 |
10 | // Should be actualized (https://github.com/feature-sliced/eslint-config/issues/17)
11 | describe.skip("restrict imports", () => {
12 | it("invalid", async () => {
13 | const report = await eslint.lintText(`
14 | import { Issues } from "pages/issues";
15 | import { IssueDetails } from "features/issue-details"
16 | import { Button } from "shared/components/button";
17 | `);
18 | assert.strictEqual(report[0].errorCount, 3);
19 | });
20 | it("valid", async () => {
21 | const report = await eslint.lintText(`
22 | import Routing from "pages"; // specific pages shouldn't be reexported
23 | import { IssueDetails } from "features" // all features should be reexported, for usage
24 | import { Button } from "shared/components"; // all components should be reexported, for usage
25 | `);
26 | assert.strictEqual(report[0].errorCount, 0);
27 | });
28 | });
29 |
30 | // Should be actualized (https://github.com/feature-sliced/eslint-config/issues/17)
31 | describe.skip("absolute imports", () => {
32 | it("invalid", async () => {
33 | const report = await eslint.lintText(`
34 | import Routing from "../../pages"
35 | import { IssueDetails } from "../features";
36 | import { Button } from "../shared/components";
37 | `);
38 | assert.strictEqual(report[0].errorCount, 3);
39 | });
40 | it("valid", async () => {
41 | const report = await eslint.lintText(`
42 | import Routing from "pages"
43 | import { IssueDetails } from "features";
44 | import { Button } from "shared/components";
45 | `);
46 | assert.strictEqual(report[0].errorCount, 0);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/utils/config/index.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const mockImports = (config, extension = "js") => {
4 | return {
5 | ...config,
6 | settings: {
7 | ...config.settings,
8 | "import/resolver": {
9 | [path.resolve(__dirname, "./mock-resolver.js")]: {
10 | extension,
11 | },
12 | },
13 | }
14 | }
15 | }
16 |
17 | function setParser (config, version = "2015") {
18 | return {
19 | ...config,
20 | parserOptions: {
21 | "ecmaVersion": version,
22 | "sourceType": "module",
23 | },
24 | };
25 | }
26 |
27 | function setTSParser (config) {
28 | return {
29 | ...config,
30 | parser: "@typescript-eslint/parser",
31 | };
32 | }
33 |
34 | module.exports.configLib = { mockImports, setParser, setTSParser };
35 |
--------------------------------------------------------------------------------
/utils/config/mock-resolver.js:
--------------------------------------------------------------------------------
1 | /* Used by import/boundaries plugin for configure parser version */
2 | const interfaceVersion = 2;
3 |
4 | function resolve (source, file, settings) {
5 | return { found: true, path: `${source}/index.${settings.extension}` };
6 | }
7 |
8 | module.exports = { interfaceVersion, resolve };
9 |
--------------------------------------------------------------------------------
/utils/index.js:
--------------------------------------------------------------------------------
1 | const { configLib } = require("./config");
2 | const { layersLib } = require("./layers");
3 | const { typesLib } = require("./types");
4 |
5 | module.exports = {
6 | configLib,
7 | layersLib,
8 | typesLib,
9 | };
10 |
--------------------------------------------------------------------------------
/utils/layers.js:
--------------------------------------------------------------------------------
1 | const FS_LAYERS = [
2 | "app",
3 | "processes",
4 | "pages",
5 | "widgets",
6 | "features",
7 | "entities",
8 | "shared",
9 | ];
10 |
11 | const FS_SEGMENTS = [
12 | "ui",
13 | "model",
14 | "lib",
15 | "api",
16 | "config",
17 | "assets"
18 | ];
19 |
20 | const getLowerLayers = (layer) => FS_LAYERS.slice(FS_LAYERS.indexOf(layer) + 1);
21 | const getUpperLayers = (layer) => FS_LAYERS.slice(0, FS_LAYERS.indexOf(layer));
22 |
23 | module.exports.layersLib = { FS_LAYERS, FS_SEGMENTS, getLowerLayers, getUpperLayers } ;
24 |
--------------------------------------------------------------------------------
/utils/types.js:
--------------------------------------------------------------------------------
1 | const isArray = (val) => Array.isArray(val);
2 | const isObj = (val) => typeof val === "object" && val !== null;
3 | const isString = (val) => typeof val === "string";
4 | const isNumber = (val) => typeof val === "number";
5 | const isOptional = (val) => val === undefined;
6 |
7 | module.exports.typesLib = {
8 | isArray,
9 | isObj,
10 | isString,
11 | isNumber,
12 | isOptional,
13 | };
14 |
--------------------------------------------------------------------------------