From 0b3e38174b4728b873a756097e0f19716ab839f0 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sat, 18 Dec 2021 17:47:04 +0300 Subject: [PATCH 01/15] feat: Prettier added. --- .prettierignore | 8 ++++++++ .prettierrc.js | 11 +++++++++++ package.json | 2 ++ yarn.lock | 5 +++++ 4 files changed, 26 insertions(+) create mode 100755 .prettierignore create mode 100755 .prettierrc.js diff --git a/.prettierignore b/.prettierignore new file mode 100755 index 0000000..f71c4be --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +node_modules/** +dist/** +build/** +public/ +.github +.workflows +*.md +*.mdx diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100755 index 0000000..e3bd92f --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,11 @@ +module.exports = { + printWidth: 100, + semi: true, + singleQuote: false, + tabWidth: 4, + quoteProps: "consistent", + endOfLine: "lf", + importOrder: ["^[./]"], + trailingComma: "all", + arrowParens: "always", +}; diff --git a/package.json b/package.json index cae7c24..584921c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", + "prettier:fix": "prettier --write **/*.js", "clean": "git clean -fxd", "test": "mocha \"*(test|rules)/**/*.test.js\"" }, @@ -42,6 +43,7 @@ "eslint-plugin-boundaries": "^2.6.0", "eslint-plugin-import": "^2.25.3", "mocha": "^8.2.1", + "prettier": "^2.5.1", "typescript": "^4.5.3" }, "dependencies": {} diff --git a/yarn.lock b/yarn.lock index 5a645cf..fc62963 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1250,6 +1250,11 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== + progress@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" From 11d8f395e6c43958bdd6a7877bca5fdc5b6ca65a Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sat, 18 Dec 2021 17:49:15 +0300 Subject: [PATCH 02/15] refactor: split slices-layers. --- index.js | 9 +++-- .../index.js | 14 ++++--- .../index.md | 0 .../layers.test.js | 0 .../index.test.js | 12 ++++++ rules/slices-boundaries/index.js | 37 +++++++++++++++++++ rules/slices-boundaries/index.md | 19 ++++++++++ .../slices.test.js | 2 +- 8 files changed, 82 insertions(+), 11 deletions(-) rename rules/{layers-slices-boundaries => layers-boundaries}/index.js (73%) rename rules/{layers-slices-boundaries => layers-boundaries}/index.md (100%) rename rules/{layers-slices-boundaries => layers-boundaries}/layers.test.js (100%) create mode 100644 rules/slices-and-layers-boundaries/index.test.js create mode 100644 rules/slices-boundaries/index.js create mode 100644 rules/slices-boundaries/index.md rename rules/{layers-slices-boundaries => slices-boundaries}/slices.test.js (97%) diff --git a/index.js b/index.js index a971bd6..f618929 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,9 @@ module.exports = { "sourceType": "module", }, extends: [ - path.resolve(__dirname, "./rules/public-api-boundaries"), - path.resolve(__dirname, "./rules/layers-slices-boundaries"), - path.resolve(__dirname, "./rules/import-order") - ], + "./rules/public-api-boundaries", + "./rules/layers-boundaries", + "./rules/slices-boundaries", + "./rules/import-order", + ].map(require.resolve), }; diff --git a/rules/layers-slices-boundaries/index.js b/rules/layers-boundaries/index.js similarity index 73% rename from rules/layers-slices-boundaries/index.js rename to rules/layers-boundaries/index.js index 67ada13..1dff73e 100644 --- a/rules/layers-slices-boundaries/index.js +++ b/rules/layers-boundaries/index.js @@ -1,18 +1,20 @@ const { layersLib } = require("../../utils"); +const withLayerPrefix = (layer) => `layer:${layer}` + const getLayersRules = () => layersLib.FS_LAYERS.map((layer) => ({ - from: layer, - allow: layersLib.getLowerLayers(layer), + from: withLayerPrefix(layer), + disallow: layersLib.getUpperLayers(withLayerPrefix(layer)), })); const getLayersBoundariesElements = () => layersLib.FS_LAYERS.map((layer) => ({ - type: layer, - pattern: `${layer}/*`, + type: withLayerPrefix(layer), + pattern: `${layer}/**`, mode: "folder", - capture: ["slices"], + capture: ["layers"], })); module.exports = { @@ -26,7 +28,7 @@ module.exports = { "boundaries/element-types": [ 2, { - "default": "disallow", + "default": "allow", "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ", "rules": getLayersRules(), }, diff --git a/rules/layers-slices-boundaries/index.md b/rules/layers-boundaries/index.md similarity index 100% rename from rules/layers-slices-boundaries/index.md rename to rules/layers-boundaries/index.md diff --git a/rules/layers-slices-boundaries/layers.test.js b/rules/layers-boundaries/layers.test.js similarity index 100% rename from rules/layers-slices-boundaries/layers.test.js rename to rules/layers-boundaries/layers.test.js diff --git a/rules/slices-and-layers-boundaries/index.test.js b/rules/slices-and-layers-boundaries/index.test.js new file mode 100644 index 0000000..ee5fff5 --- /dev/null +++ b/rules/slices-and-layers-boundaries/index.test.js @@ -0,0 +1,12 @@ +const { configLib } = require("../../utils"); +const slicesBoundaries = require("../slices-boundaries"); +const layersBoundaries = require("../layers-boundaries"); + +describe("Slices and Layers config:", () => { + + it("slices and layers should be correct combined", () => { + console.log(JSON.stringify( + configLib.combineConfigs([slicesBoundaries, layersBoundaries]), + null, 4)); + }); +}); \ No newline at end of file diff --git a/rules/slices-boundaries/index.js b/rules/slices-boundaries/index.js new file mode 100644 index 0000000..ccfb3fb --- /dev/null +++ b/rules/slices-boundaries/index.js @@ -0,0 +1,37 @@ +const { layersLib } = require("../../utils"); + +const withSlicePrefix = (slice) => `slice:${slice}`; + +const getSlicesRules = () => + layersLib.FS_LAYERS.map((layer) => ({ + from: withSlicePrefix(layer), + allow: layersLib.FS_LAYERS.filter(item => item !== layer). + map(item => withSlicePrefix(item)), + })); + +const getSlicesBoundariesElements = () => + layersLib.FS_LAYERS.map((layer) => ({ + type: withSlicePrefix(layer), + pattern: `${layer}`, + mode: "folder", + capture: ["slices"], + })); + +module.exports = { + plugins: ["boundaries"], + extends: ["plugin:boundaries/recommended"], + ignorePatterns: [".eslintrc.js"], + settings: { + "boundaries/elements": getSlicesBoundariesElements(), + }, + rules: { + "boundaries/element-types": [ + 2, + { + "default": "disallow", + "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/concepts/app-splitting#group-slices ", + "rules": getSlicesRules(), + }, + ], + }, +}; diff --git a/rules/slices-boundaries/index.md b/rules/slices-boundaries/index.md new file mode 100644 index 0000000..96569e5 --- /dev/null +++ b/rules/slices-boundaries/index.md @@ -0,0 +1,19 @@ +# @feature-sliced/layers-slices-boundaries + +Reference: [Cross-communication](https://feature-sliced.design/docs/concepts/cross-communication) + +```js +// 👎 Fail +// 🛣 features/auth-form/index.ts +import { getRoute } from "pages/auth"; +import { getStore } from "app/store"; +import { getAuthCtx } from "features/logout"; +import { UserAvatar } from "features/viewer-picker"; + +// 👍 Pass +// 🛣 features/auth-form/index.ts +import { sessionModel } from "entities/session"; +import { Form, Button } from "shared/ui"; +import { getAuthCtx } from "entities/session"; +import { UserAvatar } from "entities/user"; +``` diff --git a/rules/layers-slices-boundaries/slices.test.js b/rules/slices-boundaries/slices.test.js similarity index 97% rename from rules/layers-slices-boundaries/slices.test.js rename to rules/slices-boundaries/slices.test.js index eaa155c..94a79c1 100644 --- a/rules/layers-slices-boundaries/slices.test.js +++ b/rules/slices-boundaries/slices.test.js @@ -45,7 +45,7 @@ describe("Import boundaries between slices and layers", () => { ]; const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "features/auth-form/index.js", + filePath: "src/features/auth-form/index.js", }); assert.strictEqual(report[0].errorCount, wrongImports.length); From 920660d2fd830834c1f2930290efd2215737c9c3 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sat, 18 Dec 2021 17:49:53 +0300 Subject: [PATCH 03/15] feta: slice-and-layers config. --- rules/integration.test.js | 1 + rules/slices-and-layers-boundaries/index.js | 5 +++ utils/config/index.js | 34 ++++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 rules/slices-and-layers-boundaries/index.js diff --git a/rules/integration.test.js b/rules/integration.test.js index 60f2c66..0857423 100644 --- a/rules/integration.test.js +++ b/rules/integration.test.js @@ -24,6 +24,7 @@ describe("Integration tests:", () => { `, { filePath: "src/widgets/mock/index.js", }); + console.log(report[0]); assert.strictEqual(report[0].errorCount, 11); }); diff --git a/rules/slices-and-layers-boundaries/index.js b/rules/slices-and-layers-boundaries/index.js new file mode 100644 index 0000000..6d87085 --- /dev/null +++ b/rules/slices-and-layers-boundaries/index.js @@ -0,0 +1,5 @@ +const slicesBoundaries = require("../slices-boundaries"); +const layersBoundaries = require("../layers-boundaries"); +const { configLib } = require("../../utils"); + +module.exports = configLib.mergeConfigs(layersBoundaries, slicesBoundaries); \ No newline at end of file diff --git a/utils/config/index.js b/utils/config/index.js index 86e6979..08d0649 100644 --- a/utils/config/index.js +++ b/utils/config/index.js @@ -1,4 +1,6 @@ const path = require("path"); +const { typesLib } = require("../types"); +const { isArray } = require("eslint-plugin-boundaries/src/helpers/utils"); const mockImports = (config, extension = "js") => { return { @@ -10,9 +12,9 @@ const mockImports = (config, extension = "js") => { extension, }, }, - } - } -} + }, + }; +}; function setParser (config, version = "2015") { return { @@ -31,4 +33,28 @@ function setTSParser (config) { }; } -module.exports.configLib = { mockImports, setParser, setTSParser }; +function mergeConfigs (...configs) { + return configs.reduce((prev, cur) => { + Object.keys(cur).forEach(key => { + const previous = prev[key]; + const current = cur[key]; + + if (typesLib.isArray(previous) && typesLib.isArray(current)) { + prev[key] = Array.from(new Set([...previous, ...current])); + } else if (typesLib.isObj(previous) && typesLib.isObj(current)) { + prev[key] = mergeConfigs(previous, current); + } else { + prev[key] = current; + } + }); + + return prev; + }, {}); +} + +module.exports.configLib = { + mockImports, + setParser, + setTSParser, + mergeConfigs, +}; From 667f6b947117c46b6d20144766acbe45fc4fe0bb Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sat, 18 Dec 2021 18:37:57 +0300 Subject: [PATCH 04/15] revert: merge config. --- index.js | 3 +- rules/layers-boundaries/index.js | 2 +- rules/slices-and-layers-boundaries/index.js | 37 ++++++- .../index.test.js | 98 +++++++++++++++++-- rules/slices-boundaries/index.js | 2 +- utils/config/index.js | 22 ----- 6 files changed, 128 insertions(+), 36 deletions(-) diff --git a/index.js b/index.js index f618929..c23c1a5 100644 --- a/index.js +++ b/index.js @@ -7,8 +7,7 @@ module.exports = { }, extends: [ "./rules/public-api-boundaries", - "./rules/layers-boundaries", - "./rules/slices-boundaries", + "./rules/slices-and-layers-boundaries", "./rules/import-order", ].map(require.resolve), }; diff --git a/rules/layers-boundaries/index.js b/rules/layers-boundaries/index.js index 1dff73e..68f80e8 100644 --- a/rules/layers-boundaries/index.js +++ b/rules/layers-boundaries/index.js @@ -28,7 +28,7 @@ module.exports = { "boundaries/element-types": [ 2, { - "default": "allow", + "default": "disallow", "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ", "rules": getLayersRules(), }, diff --git a/rules/slices-and-layers-boundaries/index.js b/rules/slices-and-layers-boundaries/index.js index 6d87085..9643566 100644 --- a/rules/slices-and-layers-boundaries/index.js +++ b/rules/slices-and-layers-boundaries/index.js @@ -1,5 +1,34 @@ -const slicesBoundaries = require("../slices-boundaries"); -const layersBoundaries = require("../layers-boundaries"); -const { configLib } = require("../../utils"); +const { layersLib } = require("../../utils"); -module.exports = configLib.mergeConfigs(layersBoundaries, slicesBoundaries); \ No newline at end of file +const getLayersRules = () => + layersLib.FS_LAYERS.map((layer) => ({ + from: layer, + allow: layersLib.getLowerLayers(layer), + })); + +const getAllBoundariesElements = () => + layersLib.FS_LAYERS.map((layer) => ({ + type: layer, + pattern: `${layer}/*`, + mode: "folder", + capture: ["slices"], + })); + +module.exports = { + plugins: ["boundaries"], + extends: ["plugin:boundaries/recommended"], + ignorePatterns: [".eslintrc.js"], + settings: { + "boundaries/elements": getAllBoundariesElements(), + }, + rules: { + "boundaries/element-types": [ + 2, + { + "default": "disallow", + "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ", + "rules": getLayersRules(), + }, + ], + }, +}; \ No newline at end of file diff --git a/rules/slices-and-layers-boundaries/index.test.js b/rules/slices-and-layers-boundaries/index.test.js index ee5fff5..1c55fc0 100644 --- a/rules/slices-and-layers-boundaries/index.test.js +++ b/rules/slices-and-layers-boundaries/index.test.js @@ -1,12 +1,98 @@ +const { ESLint } = require("eslint"); +const assert = require("assert"); const { configLib } = require("../../utils"); -const slicesBoundaries = require("../slices-boundaries"); -const layersBoundaries = require("../layers-boundaries"); +const cfg = require("./"); + +const eslint = new ESLint({ + useEslintrc: false, + baseConfig: configLib.setParser( + configLib.mockImports(cfg), + ), +}); describe("Slices and Layers config:", () => { - it("slices and layers should be correct combined", () => { - console.log(JSON.stringify( - configLib.combineConfigs([slicesBoundaries, layersBoundaries]), - null, 4)); + describe("Layers:", () => { + it("should lint with cross-import errors.", async () => { + const wrongImports = [ + `import { getRoute } from "pages/auth";`, + `import { getStore } from "app/store";`, + ]; + + const report = await eslint.lintText(wrongImports.join("\n"), { + filePath: "src/shared/lib/index.js", + }); + assert.strictEqual(report[0].errorCount, wrongImports.length); + }); + + it("should lint without errors.", async () => { + const validCodeSnippet = [ + `import { sessionModel } from "entities/session";`, + `import { Form, Button } from "shared/ui";`, + ].join("\n"); + + const report = await eslint.lintText(validCodeSnippet, { + filePath: "src/app/ui/app.js", + }); + assert.strictEqual(report[0].errorCount, 0); + }); + }); + + describe("Slices:", () => { + it("should lint with cross-import errors between pages.", async () => { + const wrongImports = [ + `import { getAuthCtx } from "pages/logout";`, + `import { UserAvatar } from "pages/auth";`, + ]; + + const report = await eslint.lintText(wrongImports.join("\n"), { + filePath: "src/pages/map/index.js", + }); + + assert.strictEqual(report[0].errorCount, wrongImports.length); + }); + + it("should lint with cross-import errors between widgets.", + async () => { + const wrongImports = [ + `import { HeaderTitle } from "widgets/header";`, + `import { Links } from "widgets/footer";`, + ]; + + const report = await eslint.lintText(wrongImports.join("\n"), { + filePath: "src/widgets/mock/index.js", + }); + + assert.strictEqual(report[0].errorCount, wrongImports.length); + }); + + it("should lint with cross-import errors between features.", + async () => { + const wrongImports = [ + `import { getAuthCtx } from "features/logout";`, + `import { UserAvatar } from "features/viewer-picker";`, + ]; + + const report = await eslint.lintText(wrongImports.join("\n"), { + filePath: "src/features/auth-form/index.js", + }); + + assert.strictEqual(report[0].errorCount, wrongImports.length); + }); + + it("should lint with cross-import errors between entities.", + async () => { + const wrongImports = [ + `import { LoginForm } from "features/login-form";`, + `import { Avatar } from "features/avatar";`, + ]; + + const report = await eslint.lintText(wrongImports.join("\n"), { + filePath: "src/entities/mock/index.js", + }); + + assert.strictEqual(report[0].errorCount, wrongImports.length); + }); + }); }); \ No newline at end of file diff --git a/rules/slices-boundaries/index.js b/rules/slices-boundaries/index.js index ccfb3fb..712fa5e 100644 --- a/rules/slices-boundaries/index.js +++ b/rules/slices-boundaries/index.js @@ -29,7 +29,7 @@ module.exports = { 2, { "default": "disallow", - "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/concepts/app-splitting#group-slices ", + "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ", "rules": getSlicesRules(), }, ], diff --git a/utils/config/index.js b/utils/config/index.js index 08d0649..7532875 100644 --- a/utils/config/index.js +++ b/utils/config/index.js @@ -1,6 +1,4 @@ const path = require("path"); -const { typesLib } = require("../types"); -const { isArray } = require("eslint-plugin-boundaries/src/helpers/utils"); const mockImports = (config, extension = "js") => { return { @@ -33,28 +31,8 @@ function setTSParser (config) { }; } -function mergeConfigs (...configs) { - return configs.reduce((prev, cur) => { - Object.keys(cur).forEach(key => { - const previous = prev[key]; - const current = cur[key]; - - if (typesLib.isArray(previous) && typesLib.isArray(current)) { - prev[key] = Array.from(new Set([...previous, ...current])); - } else if (typesLib.isObj(previous) && typesLib.isObj(current)) { - prev[key] = mergeConfigs(previous, current); - } else { - prev[key] = current; - } - }); - - return prev; - }, {}); -} - module.exports.configLib = { mockImports, setParser, setTSParser, - mergeConfigs, }; From 0d3967d62e6167daa1c184a9e7d95d981bf47cc5 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sun, 19 Dec 2021 12:20:34 +0300 Subject: [PATCH 05/15] fix: slices rules. remove redundant helpers. --- rules/integration.test.js | 2 - rules/layers-boundaries/index.js | 10 ++-- rules/slices-boundaries/index.js | 13 ++--- rules/slices-boundaries/slices.test.js | 68 ++++++++++++-------------- 4 files changed, 40 insertions(+), 53 deletions(-) diff --git a/rules/integration.test.js b/rules/integration.test.js index 0857423..f47a356 100644 --- a/rules/integration.test.js +++ b/rules/integration.test.js @@ -24,8 +24,6 @@ describe("Integration tests:", () => { `, { filePath: "src/widgets/mock/index.js", }); - console.log(report[0]); - assert.strictEqual(report[0].errorCount, 11); }); diff --git a/rules/layers-boundaries/index.js b/rules/layers-boundaries/index.js index 68f80e8..6f23dc8 100644 --- a/rules/layers-boundaries/index.js +++ b/rules/layers-boundaries/index.js @@ -1,17 +1,15 @@ const { layersLib } = require("../../utils"); -const withLayerPrefix = (layer) => `layer:${layer}` - const getLayersRules = () => layersLib.FS_LAYERS.map((layer) => ({ - from: withLayerPrefix(layer), - disallow: layersLib.getUpperLayers(withLayerPrefix(layer)), + from: layer, + disallow: layersLib.getUpperLayers(layer), })); const getLayersBoundariesElements = () => layersLib.FS_LAYERS.map((layer) => ({ - type: withLayerPrefix(layer), + type: layer, pattern: `${layer}/**`, mode: "folder", capture: ["layers"], @@ -28,7 +26,7 @@ module.exports = { "boundaries/element-types": [ 2, { - "default": "disallow", + "default": "allow", "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ", "rules": getLayersRules(), }, diff --git a/rules/slices-boundaries/index.js b/rules/slices-boundaries/index.js index 712fa5e..e95fc68 100644 --- a/rules/slices-boundaries/index.js +++ b/rules/slices-boundaries/index.js @@ -1,17 +1,14 @@ const { layersLib } = require("../../utils"); -const withSlicePrefix = (slice) => `slice:${slice}`; - const getSlicesRules = () => layersLib.FS_LAYERS.map((layer) => ({ - from: withSlicePrefix(layer), - allow: layersLib.FS_LAYERS.filter(item => item !== layer). - map(item => withSlicePrefix(item)), + from: layer, + disallow: layer, })); const getSlicesBoundariesElements = () => layersLib.FS_LAYERS.map((layer) => ({ - type: withSlicePrefix(layer), + type: layer, pattern: `${layer}`, mode: "folder", capture: ["slices"], @@ -28,8 +25,8 @@ module.exports = { "boundaries/element-types": [ 2, { - "default": "disallow", - "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/reference/layers/overview ", + "default": "allow", + "message": "\"${file.type}\" is not allowed to import \"${dependency.type}\" | See rules: https://feature-sliced.design/docs/concepts/app-splitting#group-slices ", "rules": getSlicesRules(), }, ], diff --git a/rules/slices-boundaries/slices.test.js b/rules/slices-boundaries/slices.test.js index 94a79c1..7926cde 100644 --- a/rules/slices-boundaries/slices.test.js +++ b/rules/slices-boundaries/slices.test.js @@ -6,62 +6,56 @@ const cfg = require("./"); const eslint = new ESLint({ useEslintrc: false, baseConfig: configLib.setParser( - configLib.mockImports(cfg) + configLib.mockImports(cfg), ), }); -describe("Import boundaries between slices and layers", () => { +describe("Import boundaries between slices", () => { it("should lint with cross-import errors between pages.", async () => { - const wrongImports = [ - `import { getAuthCtx } from "pages/logout";`, - `import { UserAvatar } from "pages/auth";`, - ]; + const report = await eslint.lintText(` + import { getAuthCtx } from "pages/logout"; + import { UserAvatar } from "pages/auth";`, + { filePath: "src/pages/map/index.js" }); - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/pages/map/index.js", - }); - - assert.strictEqual(report[0].errorCount, wrongImports.length); + assert.strictEqual(report[0].errorCount, 2); }); it("should lint with cross-import errors between widgets.", async () => { - const wrongImports = [ - `import { HeaderTitle } from "widgets/header";`, - `import { Links } from "widgets/footer";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/widgets/mock/index.js", - }); + const report = await eslint.lintText(` + import { HeaderTitle } from "widgets/header"; + import { Links } from "widgets/footer";`, + { filePath: "src/widgets/mock/index.js" }); - assert.strictEqual(report[0].errorCount, wrongImports.length); + assert.strictEqual(report[0].errorCount, 2); }); it("should lint with cross-import errors between features.", async () => { - const wrongImports = [ - `import { getAuthCtx } from "features/logout";`, - `import { UserAvatar } from "features/viewer-picker";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/features/auth-form/index.js", - }); + const report = await eslint.lintText(` + import { getAuthCtx } from "features/logout"; + import { UserAvatar } from "features/viewer-picker";`, + { filePath: "src/features/auth-form/index.js" }); - assert.strictEqual(report[0].errorCount, wrongImports.length); + assert.strictEqual(report[0].errorCount, 2); }); it("should lint with cross-import errors between entities.", async () => { - const wrongImports = [ - `import { LoginForm } from "features/login-form";`, - `import { Avatar } from "features/avatar";`, - ]; + const report = await eslint.lintText(` + import { LoginForm } from "entities/login-form"; + import { Avatar } from "entities/avatar";`, + { filePath: "src/entities/mock/index.js" }); + + assert.strictEqual(report[0].errorCount, 2); + }); - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/entities/mock/index.js", - }); + it("should lint without error", async () => { + const report = await eslint.lintText(` + import { model } from "../model"; + import { styles } from "./style.js"; + import { utils } from "../lib/utils.js";`, + { filePath: "src/entities/mock/index.js" }); - assert.strictEqual(report[0].errorCount, wrongImports.length); + assert.strictEqual(report[0].errorCount, 0); }); }); From acce6cc2885b146866e642a848a6f16b52aaf0ce Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sun, 19 Dec 2021 12:34:46 +0300 Subject: [PATCH 06/15] fix(doc): wrong examples in readme. --- rules/layers-boundaries/index.js | 1 - rules/layers-boundaries/index.md | 5 +---- rules/slices-boundaries/index.md | 4 ---- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/rules/layers-boundaries/index.js b/rules/layers-boundaries/index.js index 6f23dc8..d70d783 100644 --- a/rules/layers-boundaries/index.js +++ b/rules/layers-boundaries/index.js @@ -1,7 +1,6 @@ const { layersLib } = require("../../utils"); const getLayersRules = () => - layersLib.FS_LAYERS.map((layer) => ({ from: layer, disallow: layersLib.getUpperLayers(layer), diff --git a/rules/layers-boundaries/index.md b/rules/layers-boundaries/index.md index 96569e5..19e6be4 100644 --- a/rules/layers-boundaries/index.md +++ b/rules/layers-boundaries/index.md @@ -7,13 +7,10 @@ Reference: [Cross-communication](https://feature-sliced.design/docs/concepts/cro // 🛣 features/auth-form/index.ts import { getRoute } from "pages/auth"; import { getStore } from "app/store"; -import { getAuthCtx } from "features/logout"; -import { UserAvatar } from "features/viewer-picker"; + // 👍 Pass // 🛣 features/auth-form/index.ts -import { sessionModel } from "entities/session"; import { Form, Button } from "shared/ui"; import { getAuthCtx } from "entities/session"; -import { UserAvatar } from "entities/user"; ``` diff --git a/rules/slices-boundaries/index.md b/rules/slices-boundaries/index.md index 96569e5..94c8838 100644 --- a/rules/slices-boundaries/index.md +++ b/rules/slices-boundaries/index.md @@ -5,8 +5,6 @@ Reference: [Cross-communication](https://feature-sliced.design/docs/concepts/cro ```js // 👎 Fail // 🛣 features/auth-form/index.ts -import { getRoute } from "pages/auth"; -import { getStore } from "app/store"; import { getAuthCtx } from "features/logout"; import { UserAvatar } from "features/viewer-picker"; @@ -14,6 +12,4 @@ import { UserAvatar } from "features/viewer-picker"; // 🛣 features/auth-form/index.ts import { sessionModel } from "entities/session"; import { Form, Button } from "shared/ui"; -import { getAuthCtx } from "entities/session"; -import { UserAvatar } from "entities/user"; ``` From 156ea0a414845557bf4063d499541c72ade3be64 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Sun, 19 Dec 2021 12:37:48 +0300 Subject: [PATCH 07/15] refactor(test): simplify some test cases. --- .../index.test.js | 77 ++++++++----------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/rules/slices-and-layers-boundaries/index.test.js b/rules/slices-and-layers-boundaries/index.test.js index 1c55fc0..9fb2228 100644 --- a/rules/slices-and-layers-boundaries/index.test.js +++ b/rules/slices-and-layers-boundaries/index.test.js @@ -40,59 +40,50 @@ describe("Slices and Layers config:", () => { describe("Slices:", () => { it("should lint with cross-import errors between pages.", async () => { - const wrongImports = [ - `import { getAuthCtx } from "pages/logout";`, - `import { UserAvatar } from "pages/auth";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/pages/map/index.js", - }); + const report = await eslint.lintText(` + import { getAuthCtx } from "pages/logout"; + import { UserAvatar } from "pages/auth";`, + { filePath: "src/pages/map/index.js" }); - assert.strictEqual(report[0].errorCount, wrongImports.length); + assert.strictEqual(report[0].errorCount, 2); }); - it("should lint with cross-import errors between widgets.", - async () => { - const wrongImports = [ - `import { HeaderTitle } from "widgets/header";`, - `import { Links } from "widgets/footer";`, - ]; + it("should lint with cross-import errors between widgets.", async () => { + const report = await eslint.lintText(` + import { HeaderTitle } from "widgets/header"; + import { Links } from "widgets/footer";`, + { filePath: "src/widgets/mock/index.js" }); - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/widgets/mock/index.js", - }); - - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); + assert.strictEqual(report[0].errorCount, 2); + }); - it("should lint with cross-import errors between features.", - async () => { - const wrongImports = [ - `import { getAuthCtx } from "features/logout";`, - `import { UserAvatar } from "features/viewer-picker";`, - ]; + it("should lint with cross-import errors between features.", async () => { + const report = await eslint.lintText(` + import { getAuthCtx } from "features/logout"; + import { UserAvatar } from "features/viewer-picker";`, + { filePath: "src/features/auth-form/index.js" }); - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/features/auth-form/index.js", - }); + assert.strictEqual(report[0].errorCount, 2); + }); - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); + it("should lint with cross-import errors between entities.", async () => { + const report = await eslint.lintText(` + import { LoginForm } from "entities/login-form"; + import { Avatar } from "entities/avatar";`, + { filePath: "src/entities/mock/index.js" }); - it("should lint with cross-import errors between entities.", - async () => { - const wrongImports = [ - `import { LoginForm } from "features/login-form";`, - `import { Avatar } from "features/avatar";`, - ]; + assert.strictEqual(report[0].errorCount, 2); + }); - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/entities/mock/index.js", - }); + it("should lint without error", async () => { + const report = await eslint.lintText(` + import { model } from "../model"; + import { styles } from "./style.js"; + import { utils } from "../lib/utils.js";`, + { filePath: "src/entities/mock/index.js" }); - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); + assert.strictEqual(report[0].errorCount, 0); + }); }); }); \ No newline at end of file From 36ba453d6a56e085b6f5c024c6478aff3a13b5b0 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Mon, 20 Dec 2021 11:52:53 +0300 Subject: [PATCH 08/15] revert: prettier --- .prettierignore | 8 -------- .prettierrc.js | 11 ----------- package.json | 2 -- yarn.lock | 5 ----- 4 files changed, 26 deletions(-) delete mode 100755 .prettierignore delete mode 100755 .prettierrc.js diff --git a/.prettierignore b/.prettierignore deleted file mode 100755 index f71c4be..0000000 --- a/.prettierignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules/** -dist/** -build/** -public/ -.github -.workflows -*.md -*.mdx diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100755 index e3bd92f..0000000 --- a/.prettierrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - printWidth: 100, - semi: true, - singleQuote: false, - tabWidth: 4, - quoteProps: "consistent", - endOfLine: "lf", - importOrder: ["^[./]"], - trailingComma: "all", - arrowParens: "always", -}; diff --git a/package.json b/package.json index 584921c..cae7c24 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "publish:patch": "npm version patch && npm publish", "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", - "prettier:fix": "prettier --write **/*.js", "clean": "git clean -fxd", "test": "mocha \"*(test|rules)/**/*.test.js\"" }, @@ -43,7 +42,6 @@ "eslint-plugin-boundaries": "^2.6.0", "eslint-plugin-import": "^2.25.3", "mocha": "^8.2.1", - "prettier": "^2.5.1", "typescript": "^4.5.3" }, "dependencies": {} diff --git a/yarn.lock b/yarn.lock index fc62963..5a645cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1250,11 +1250,6 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== - progress@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" From 68756d98409f1766366895bc656f00220030ad63 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Mon, 20 Dec 2021 12:31:42 +0300 Subject: [PATCH 09/15] revert: use old rule naming --- .../index.js | 0 .../index.test.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename rules/{slices-and-layers-boundaries => layers-slices-boundaries}/index.js (100%) rename rules/{slices-and-layers-boundaries => layers-slices-boundaries}/index.test.js (100%) diff --git a/rules/slices-and-layers-boundaries/index.js b/rules/layers-slices-boundaries/index.js similarity index 100% rename from rules/slices-and-layers-boundaries/index.js rename to rules/layers-slices-boundaries/index.js diff --git a/rules/slices-and-layers-boundaries/index.test.js b/rules/layers-slices-boundaries/index.test.js similarity index 100% rename from rules/slices-and-layers-boundaries/index.test.js rename to rules/layers-slices-boundaries/index.test.js From 54493383e2a25289496c647a0fd2c9c4744a2d4f Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Mon, 20 Dec 2021 12:36:38 +0300 Subject: [PATCH 10/15] fix: rule name in main config --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index c23c1a5..4e4fc8d 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ module.exports = { }, extends: [ "./rules/public-api-boundaries", - "./rules/slices-and-layers-boundaries", + "./rules/layers-slices-boundaries", "./rules/import-order", ].map(require.resolve), }; From 3028d6b6b92130e1500103f7d93e6f79f2b62523 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Thu, 23 Dec 2021 07:34:29 +0300 Subject: [PATCH 11/15] test: integration tests for custom config variants. --- rules/integration.custom.js | 330 +++++++++++++++++++++++++++++ rules/layers-slices/layers.test.js | 39 ---- rules/layers-slices/slices.test.js | 67 ------ rules/public-api/index.js | 2 +- 4 files changed, 331 insertions(+), 107 deletions(-) create mode 100644 rules/integration.custom.js delete mode 100644 rules/layers-slices/layers.test.js delete mode 100644 rules/layers-slices/slices.test.js diff --git a/rules/integration.custom.js b/rules/integration.custom.js new file mode 100644 index 0000000..9405882 --- /dev/null +++ b/rules/integration.custom.js @@ -0,0 +1,330 @@ +const { ESLint } = require("eslint"); +const assert = require("assert"); +const { configLib } = require("../utils"); + +describe("Integration Custom configs tests:", () => { + + describe("With custom recommended rules: ", () => { + + const cfg = { + parserOptions: { + "ecmaVersion": "2015", + "sourceType": "module", + }, + extends: [ + "./layers-slices", + "./public-api", + "./import-order", + ].map(require.resolve), + }; + + const eslint = new ESLint({ + useEslintrc: false, + baseConfig: configLib.mockImports(cfg), + }); + + it("Custom config should lint with errors", async () => { + const report = await eslint.lintText(` + import { getSmth } from "./lib"; // import-order + import axios from "axios"; + import { data } from "../fixtures"; // import-order + import { authModel } from "entities/auth"; // import-order + import { Button } from "shared/ui"; // import-order + import { LoginForm } from "features/login-form"; // import-order + import { Header } from "widgets/header"; // import-order, import-boundaries + import { debounce } from "shared/lib/fp"; // import-order + import { AuthPage } from "pages/auth"; // import-boundaries + import { IssueDetails } from "widgets/issue-details/ui/details"; // import-order, publicAPI + `, { + filePath: "src/widgets/mock/index.js", + }); + assert.strictEqual(report[0].errorCount, 11); + }); + + it("Custom config should lint without errors", async () => { + const report = await eslint.lintText(` + import { getRoute } from "pages/auth"; + import { Header } from "widgets/header"; + import { LoginForm } from "features/login-form"; + import { Phone } from "features/login-form/phone"; + import { Article } from "entities/article"; + import { Button } from "shared/ui/button"; + import { LoginAPI } from "shared/api"; + import { model } from "../model"; + import { styles } from "./styles.module.scss"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 0); + }); + + it("Custom config should lint only with import-order error", async () => { + const report = await eslint.lintText(` + import { LoginAPI } from "shared/api"; + import { getRoute } from "pages/auth"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with layer error", async () => { + const report = await eslint.lintText(` + import { LoginForm } from "features/login-form"; + `, { filePath: "src/entities/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with slice error", async () => { + const report = await eslint.lintText(` + import { Article } from "entities/article"; + `, { filePath: "src/entities/avatar/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with PublicAPI error", async () => { + const report = await eslint.lintText(` + import { orderModel } from "entities/order/model"; + `, { filePath: "src/features/profile/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + }) + + describe("Without publicAPI: ", () => { + + const cfg = { + parserOptions: { + "ecmaVersion": "2015", + "sourceType": "module", + }, + extends: [ + "./layers-slices", + "./import-order", + ].map(require.resolve), + }; + + const eslint = new ESLint({ + useEslintrc: false, + baseConfig: configLib.mockImports(cfg), + }); + + it("Custom config should lint with errors", async () => { + const report = await eslint.lintText(` + import { getSmth } from "./lib"; // import-order + import axios from "axios"; + import { data } from "../fixtures"; // import-order + import { authModel } from "entities/auth"; // import-order + import { Button } from "shared/ui"; // import-order + import { LoginForm } from "features/login-form"; // import-order + import { Header } from "widgets/header"; // import-order, import-boundaries + import { debounce } from "shared/lib/fp"; // import-order + import { AuthPage } from "pages/auth"; // import-boundaries + `, { + filePath: "src/widgets/mock/index.js", + }); + assert.strictEqual(report[0].errorCount, 9); + }); + + it("Custom config should lint without errors", async () => { + const report = await eslint.lintText(` + import { getRoute } from "pages/auth"; + import { Header } from "widgets/header"; + import { LoginForm } from "features/login-form"; + import { Phone } from "features/login-form/phone"; + import { Article } from "entities/article"; + import { Button } from "shared/ui/button"; + import { LoginAPI } from "shared/api"; + import { model } from "../model"; + import { styles } from "./styles.module.scss"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 0); + }); + + it("Custom config should lint only with import-order error", async () => { + const report = await eslint.lintText(` + import { LoginAPI } from "shared/api"; + import { getRoute } from "pages/auth"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with layer error", async () => { + const report = await eslint.lintText(` + import { LoginForm } from "features/login-form"; + `, { filePath: "src/entities/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with slice error", async () => { + const report = await eslint.lintText(` + import { Article } from "entities/article"; + `, { filePath: "src/entities/avatar/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + }) + + describe("Without import-order: ", () => { + + const cfg = { + parserOptions: { + "ecmaVersion": "2015", + "sourceType": "module", + }, + extends: [ + "./public-api", + "./layers-slices", + ].map(require.resolve), + }; + + const eslint = new ESLint({ + useEslintrc: false, + baseConfig: configLib.mockImports(cfg), + }); + + it("Custom config should lint with errors", async () => { + const report = await eslint.lintText(` + import { getSmth } from "./lib"; + import axios from "axios"; + import { data } from "../fixtures"; + import { authModel } from "entities/auth"; + import { Button } from "shared/ui"; + import { LoginForm } from "features/login-form"; + import { Header } from "widgets/header"; // import-boundaries + import { debounce } from "shared/lib/fp"; + import { AuthPage } from "pages/auth"; // import-boundaries + import { IssueDetails } from "widgets/issue-details/ui/details"; // import-boundaries, publicAPI + `, { + filePath: "src/widgets/mock/index.js", + }); + + assert.strictEqual(report[0].errorCount, 4); + }); + + it("Custom config should lint without errors", async () => { + const report = await eslint.lintText(` + import { getRoute } from "pages/auth"; + import { Header } from "widgets/header"; + import { LoginForm } from "features/login-form"; + import { Phone } from "features/login-form/phone"; + import { Article } from "entities/article"; + import { Button } from "shared/ui/button"; + import { LoginAPI } from "shared/api"; + import { model } from "../model"; + import { styles } from "./styles.module.scss"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 0); + }); + + it("Custom config should lint only with layer error", async () => { + const report = await eslint.lintText(` + import { LoginForm } from "features/login-form"; + `, { filePath: "src/entities/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with slice error", async () => { + const report = await eslint.lintText(` + import { Article } from "entities/article"; + `, { filePath: "src/entities/avatar/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with PublicAPI error", async () => { + const report = await eslint.lintText(` + import { orderModel } from "entities/order/model"; + `, { filePath: "src/features/profile/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + }) + + describe("Without layers-slice, but with layers: ", () => { + + const cfg = { + parserOptions: { + "ecmaVersion": "2015", + "sourceType": "module", + }, + extends: [ + "./public-api", + "./import-order", + "./layers", + ].map(require.resolve), + }; + + const eslint = new ESLint({ + useEslintrc: false, + baseConfig: configLib.mockImports(cfg), + }); + + it("Custom config should lint with errors", async () => { + const report = await eslint.lintText(` + import { getSmth } from "./lib"; // import-order + import axios from "axios"; + import { authProcess } from "processes/auth-process"; // layers + import { data } from "../fixtures"; // import-order + import { authModel } from "entities/auth"; // import-order + import { Button } from "shared/ui"; // import-order + import { LoginForm } from "features/login-form"; // import-order + import { Header } from "widgets/header"; // import-order + import { debounce } from "shared/lib/fp"; // import-order + import { AuthPage } from "pages/auth"; // layers + import { IssueDetails } from "widgets/issue-details/ui/details"; // publicAPI + `, { + filePath: "src/widgets/mock/index.js", + }); + + assert.strictEqual(report[0].errorCount, 10); + }); + + it("Custom config should lint without errors", async () => { + const report = await eslint.lintText(` + import { getRoute } from "pages/auth"; + import { Header } from "widgets/header"; + import { LoginForm } from "features/login-form"; + import { Phone } from "features/login-form/phone"; + import { Article } from "entities/article"; + import { Button } from "shared/ui/button"; + import { LoginAPI } from "shared/api"; + import { model } from "../model"; + import { styles } from "./styles.module.scss"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 0); + }); + + it("Custom config should lint only with import-order error", async () => { + const report = await eslint.lintText(` + import { LoginAPI } from "shared/api"; + import { getRoute } from "pages/auth"; + `, { filePath: "src/app/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with layer error", async () => { + const report = await eslint.lintText(` + import { LoginForm } from "features/login-form"; + `, { filePath: "src/entities/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + + it("Custom config should lint only with PublicAPI error", async () => { + const report = await eslint.lintText(` + import { orderModel } from "entities/order/model"; + `, { filePath: "src/features/profile/ui/index.js" }); + + assert.strictEqual(report[0].errorCount, 1); + }); + }) +}); diff --git a/rules/layers-slices/layers.test.js b/rules/layers-slices/layers.test.js deleted file mode 100644 index 444551b..0000000 --- a/rules/layers-slices/layers.test.js +++ /dev/null @@ -1,39 +0,0 @@ -const { ESLint } = require("eslint"); -const assert = require("assert"); -const { configLib } = require("../../utils"); -const cfg = require("./"); - -const eslint = new ESLint({ - useEslintrc: false, - baseConfig: configLib.setParser( - configLib.mockImports(cfg) - ), -}); - -describe("Import boundaries between layers", () => { - - it("should lint with cross-import errors.", async () => { - const wrongImports = [ - `import { getRoute } from "pages/auth";`, - `import { getStore } from "app/store";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/shared/lib/index.js", - }); - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); - - it("should lint without errors.", async () => { - const validCodeSnippet = [ - `import { sessionModel } from "entities/session";`, - `import { Form, Button } from "shared/ui";`, - ].join("\n"); - - const report = await eslint.lintText(validCodeSnippet, { - filePath: "src/app/ui/app.js", - }); - assert.strictEqual(report[0].errorCount, 0); - }); - -}); diff --git a/rules/layers-slices/slices.test.js b/rules/layers-slices/slices.test.js deleted file mode 100644 index 88b4b98..0000000 --- a/rules/layers-slices/slices.test.js +++ /dev/null @@ -1,67 +0,0 @@ -const { ESLint } = require("eslint"); -const assert = require("assert"); -const { configLib } = require("../../utils"); -const cfg = require("."); - -const eslint = new ESLint({ - useEslintrc: false, - baseConfig: configLib.setParser( - configLib.mockImports(cfg) - ), -}); - -describe("Import boundaries between slices and layers", () => { - - it("should lint with cross-import errors between pages.", async () => { - const wrongImports = [ - `import { getAuthCtx } from "pages/logout";`, - `import { UserAvatar } from "pages/auth";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/pages/map/index.js", - }); - - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); - - it("should lint with cross-import errors between widgets.", async () => { - const wrongImports = [ - `import { HeaderTitle } from "widgets/header";`, - `import { Links } from "widgets/footer";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/widgets/mock/index.js", - }); - - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); - - it("should lint with cross-import errors between features.", async () => { - const wrongImports = [ - `import { getAuthCtx } from "features/logout";`, - `import { UserAvatar } from "features/viewer-picker";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "features/auth-form/index.js", - }); - - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); - - it("should lint with cross-import errors between entities.", async () => { - const wrongImports = [ - `import { LoginForm } from "features/login-form";`, - `import { Avatar } from "features/avatar";`, - ]; - - const report = await eslint.lintText(wrongImports.join("\n"), { - filePath: "src/entities/mock/index.js", - }); - - assert.strictEqual(report[0].errorCount, wrongImports.length); - }); - -}); diff --git a/rules/public-api/index.js b/rules/public-api/index.js index 4137e4b..b021b56 100644 --- a/rules/public-api/index.js +++ b/rules/public-api/index.js @@ -1,6 +1,6 @@ const { layersLib } = require("../../utils"); -const FS_SEGMENTS_REG = layersLib.FS_SEGMENTS.join("|"); +const FS_SEGMENTS_REG = layersLib.FS_SEGMENTS.map(seg => `${seg}*`).join("|"); const FS_SLICED_LAYERS_REG = layersLib.getUpperLayers("shared").join("|"); module.exports = { From 3c0a8f3fc5a82bd55e0d70e61eae9cae7cb69507 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Thu, 23 Dec 2021 07:43:19 +0300 Subject: [PATCH 12/15] revert: publicAPI and doc changes. --- rules/layers-slices/README.md | 6 +++++- rules/public-api/index.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rules/layers-slices/README.md b/rules/layers-slices/README.md index 7c29308..f75fef1 100644 --- a/rules/layers-slices/README.md +++ b/rules/layers-slices/README.md @@ -8,6 +8,8 @@ Add `"@feature-sliced/eslint-config/rules/layers-slices"` to you `extends` secti ```js // 👎 Fail // 🛣 features/auth-form/index.ts +import { getRoute } from "pages/auth"; +import { getStore } from "app/store"; import { getAuthCtx } from "features/logout"; import { UserAvatar } from "features/viewer-picker"; @@ -15,4 +17,6 @@ import { UserAvatar } from "features/viewer-picker"; // 🛣 features/auth-form/index.ts import { sessionModel } from "entities/session"; import { Form, Button } from "shared/ui"; -``` +import { getAuthCtx } from "entities/session"; +import { UserAvatar } from "entities/user"; +``` \ No newline at end of file diff --git a/rules/public-api/index.js b/rules/public-api/index.js index b021b56..4137e4b 100644 --- a/rules/public-api/index.js +++ b/rules/public-api/index.js @@ -1,6 +1,6 @@ const { layersLib } = require("../../utils"); -const FS_SEGMENTS_REG = layersLib.FS_SEGMENTS.map(seg => `${seg}*`).join("|"); +const FS_SEGMENTS_REG = layersLib.FS_SEGMENTS.join("|"); const FS_SLICED_LAYERS_REG = layersLib.getUpperLayers("shared").join("|"); module.exports = { From a6a2df3aff1da15e2e44e163d2b22a111e4b6e83 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Thu, 23 Dec 2021 07:55:03 +0300 Subject: [PATCH 13/15] fix(doc): usage layers and slices custom rules. --- rules/layers/index.md | 7 +++++-- rules/slices/index.md | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rules/layers/index.md b/rules/layers/index.md index 19e6be4..743f84f 100644 --- a/rules/layers/index.md +++ b/rules/layers/index.md @@ -1,6 +1,9 @@ -# @feature-sliced/layers-slices-boundaries +# @feature-sliced/layers -Reference: [Cross-communication](https://feature-sliced.design/docs/concepts/cross-communication) +#### Reference: [Layers](https://feature-sliced.design/docs/reference/layers/overview) + +#### Usage: +Add `"@feature-sliced/eslint-config/rules/layers"` to you `extends` section in ESLint config. ```js // 👎 Fail diff --git a/rules/slices/index.md b/rules/slices/index.md index 983ee93..3315c40 100644 --- a/rules/slices/index.md +++ b/rules/slices/index.md @@ -3,7 +3,7 @@ #### Reference: [Cross-communication](https://feature-sliced.design/docs/concepts/cross-communication) #### Usage: -Add `"@feature-sliced/eslint-config/rules/layers-slices"` to you `extends` section in ESLint config. +Add `"@feature-sliced/eslint-config/rules/slices"` to you `extends` section in ESLint config. ```js // 👎 Fail From e69a7af25b916e20cd057be9bb230b3a7435a51c Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Thu, 23 Dec 2021 08:01:24 +0300 Subject: [PATCH 14/15] chore: rename to index.test.js --- rules/layers/{layers.test.js => index.test.js} | 0 rules/slices/{slices.test.js => index.test.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename rules/layers/{layers.test.js => index.test.js} (100%) rename rules/slices/{slices.test.js => index.test.js} (100%) diff --git a/rules/layers/layers.test.js b/rules/layers/index.test.js similarity index 100% rename from rules/layers/layers.test.js rename to rules/layers/index.test.js diff --git a/rules/slices/slices.test.js b/rules/slices/index.test.js similarity index 100% rename from rules/slices/slices.test.js rename to rules/slices/index.test.js From 8ed302975b6754c5348041061d0f61c1045e4986 Mon Sep 17 00:00:00 2001 From: Krakazybik Date: Thu, 23 Dec 2021 08:08:18 +0300 Subject: [PATCH 15/15] doc(readme): actualize for new rules. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c853eda..b271414 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Uncomment if will be needed - [`import-order`](./rules/import-order) - [`public-api`](./rules/public-api) - [`layers-slices`](./rules/layers-slices) +- [`layers`](./rules/layers) +- [`slices`](./rules/slices) ## Get Started