From 343a34b5c96db7534104e8b39bc62e6ade3a3ba0 Mon Sep 17 00:00:00 2001 From: Evgeniy Date: Sat, 11 Dec 2021 17:02:43 +0300 Subject: [PATCH 1/4] refactor: move rule-configs close to test. move helpers and utils to ./utils directory. update mocha package.json run for new test locations. --- index.js | 4 +- package.json | 2 +- .../layers-slices-boundaries/index.js | 2 +- .../layers-slices-boundaries/index.test.js | 32 ++++++++++++++-- .../public-api-boundaries/index.js | 2 +- .../public-api-boundaries/index.test.js | 4 +- test/layers-boundaries.test.js | 38 ------------------- helpers.js => utils/helpers.js | 0 {test/utils => utils}/mock-import/index.js | 0 .../mock-import/mock-resolver.js | 0 10 files changed, 35 insertions(+), 49 deletions(-) rename layers-slices-boundaries.js => rules/layers-slices-boundaries/index.js (93%) rename test/slices-boundaries.test.js => rules/layers-slices-boundaries/index.test.js (70%) rename public-api-boundaries.js => rules/public-api-boundaries/index.js (94%) rename test/public-api-boundaries.test.js => rules/public-api-boundaries/index.test.js (95%) delete mode 100644 test/layers-boundaries.test.js rename helpers.js => utils/helpers.js (100%) rename {test/utils => utils}/mock-import/index.js (100%) rename {test/utils => utils}/mock-import/mock-resolver.js (100%) diff --git a/index.js b/index.js index f455770..20b2165 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,7 @@ module.exports = { "sourceType": "module", }, extends: [ - path.resolve(__dirname, "./public-api-boundaries.js"), - path.resolve(__dirname, "./layers-slices-boundaries.js") + path.resolve(__dirname, "./rules/public-api-boundaries"), + path.resolve(__dirname, "./rules/layers-slices-boundaries") ], }; diff --git a/package.json b/package.json index 3eefcb8..e8d8c17 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "clean": "git clean -fxd", - "test": "mocha test/**.test.js" + "test": "mocha **/**.test.js" }, "peerDependencies": { "eslint": ">=6", diff --git a/layers-slices-boundaries.js b/rules/layers-slices-boundaries/index.js similarity index 93% rename from layers-slices-boundaries.js rename to rules/layers-slices-boundaries/index.js index afc6b54..94a97b8 100644 --- a/layers-slices-boundaries.js +++ b/rules/layers-slices-boundaries/index.js @@ -1,4 +1,4 @@ -const { getLowerLayers, FS_LAYERS } = require("./helpers"); +const { getLowerLayers, FS_LAYERS } = require("../../utils/helpers"); const getLayersRules = () => FS_LAYERS.map((layer) => ({ diff --git a/test/slices-boundaries.test.js b/rules/layers-slices-boundaries/index.test.js similarity index 70% rename from test/slices-boundaries.test.js rename to rules/layers-slices-boundaries/index.test.js index cebfd05..4547e30 100644 --- a/test/slices-boundaries.test.js +++ b/rules/layers-slices-boundaries/index.test.js @@ -1,15 +1,14 @@ const { ESLint } = require("eslint"); const assert = require("assert"); -const { getRandomImportByLayerName } = require("./utils/tools"); -const { mockImports } = require("./utils/mock-import"); -const cfg = require(".."); +const { mockImports } = require("../../utils/mock-import"); +const cfg = require("./"); const eslint = new ESLint({ useEslintrc: false, baseConfig: mockImports(cfg), }); -describe("Import boundaries between slices", () => { +describe("Import boundaries between slices and layers", () => { it("should lint with cross-import errors between pages.", async () => { const wrongImports = [ @@ -75,4 +74,29 @@ describe("Import boundaries between slices", () => { assert.strictEqual(report[0].errorCount, 0); }); + + 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/public-api-boundaries.js b/rules/public-api-boundaries/index.js similarity index 94% rename from public-api-boundaries.js rename to rules/public-api-boundaries/index.js index 85842c7..cc2d1a6 100644 --- a/public-api-boundaries.js +++ b/rules/public-api-boundaries/index.js @@ -1,4 +1,4 @@ -const { getUpperLayers, FS_SEGMENTS, FS_LAYERS } = require("./helpers"); +const { getUpperLayers, FS_SEGMENTS, FS_LAYERS } = require("../../utils/helpers"); const FS_SEGMENTS_REG = FS_SEGMENTS.join("|"); const FS_LAYERS_REG = FS_LAYERS.join("|"); diff --git a/test/public-api-boundaries.test.js b/rules/public-api-boundaries/index.test.js similarity index 95% rename from test/public-api-boundaries.test.js rename to rules/public-api-boundaries/index.test.js index 81e5738..4c6d880 100644 --- a/test/public-api-boundaries.test.js +++ b/rules/public-api-boundaries/index.test.js @@ -1,7 +1,7 @@ const { ESLint } = require("eslint"); const assert = require("assert"); -const { mockImports } = require("./utils/mock-import"); -const cfg = require("../public-api-boundaries"); +const { mockImports } = require("../../utils/mock-import"); +const cfg = require("./"); const eslint = new ESLint({ useEslintrc: false, diff --git a/test/layers-boundaries.test.js b/test/layers-boundaries.test.js deleted file mode 100644 index fc46ad9..0000000 --- a/test/layers-boundaries.test.js +++ /dev/null @@ -1,38 +0,0 @@ -const { ESLint } = require("eslint"); -const assert = require("assert"); -const { getRandomImportByLayerName } = require("./utils/tools"); -const { mockImports } = require("./utils/mock-import"); -const cfg = require(".."); - -const eslint = new ESLint({ - useEslintrc: false, - baseConfig: 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/helpers.js b/utils/helpers.js similarity index 100% rename from helpers.js rename to utils/helpers.js diff --git a/test/utils/mock-import/index.js b/utils/mock-import/index.js similarity index 100% rename from test/utils/mock-import/index.js rename to utils/mock-import/index.js diff --git a/test/utils/mock-import/mock-resolver.js b/utils/mock-import/mock-resolver.js similarity index 100% rename from test/utils/mock-import/mock-resolver.js rename to utils/mock-import/mock-resolver.js From 6713e221d85bd01103802a6fdf57bc6b27b4a34b Mon Sep 17 00:00:00 2001 From: Evgeniy Date: Sat, 11 Dec 2021 17:03:24 +0300 Subject: [PATCH 2/4] docs(rules): added some doc's for layers/slices and publicAPI rules. --- rules/layers-slices-boundaries/index.md | 19 +++++++++++++++++++ rules/public-api-boundaries/index.md | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 rules/layers-slices-boundaries/index.md create mode 100644 rules/public-api-boundaries/index.md diff --git a/rules/layers-slices-boundaries/index.md b/rules/layers-slices-boundaries/index.md new file mode 100644 index 0000000..96569e5 --- /dev/null +++ b/rules/layers-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/public-api-boundaries/index.md b/rules/public-api-boundaries/index.md new file mode 100644 index 0000000..ea36cef --- /dev/null +++ b/rules/public-api-boundaries/index.md @@ -0,0 +1,24 @@ +# @feature-sliced/public-api-boundaries + +Reference: [PublicAPI](https://feature-sliced.design/docs/concepts/public-api) + +```js +// 👎 Fail +import { Issues } from "pages/issues/ui"; +import { IssueDetails } from "widgets/issue-details/ui/details" +import { AuthForm } from "features/auth-form/ui/form" +import { Button } from "shared/ui/button/button"; +import { saveOrder } from "entities/order/model/actions"; +import { orderModel } from "entities/order/model"; +import { TicketCard } from "@/entities/ticket/ui"; + +// 👍 Pass +import { Issues } from "pages/issues"; +import { IssueDetails } from "widgets/issue-details" +import { AuthForm } from "features/auth-form" +import { Button } from "shared/ui/button"; +import { orderModel } from "entities/order"; +import { TicketCard } from "@/entities/ticket"; +import { AuthForm } from "features/auth/form" +import { Button } from "shared/ui"; +``` From 97cd03f5720dc92e3af6001bf55582dbe6f74561 Mon Sep 17 00:00:00 2001 From: Evgeniy Date: Sat, 11 Dec 2021 20:20:04 +0300 Subject: [PATCH 3/4] revert: layers and slices test. --- rules/layers-slices-boundaries/layers.test.js | 37 +++++++++ rules/layers-slices-boundaries/slices.test.js | 77 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 rules/layers-slices-boundaries/layers.test.js create mode 100644 rules/layers-slices-boundaries/slices.test.js diff --git a/rules/layers-slices-boundaries/layers.test.js b/rules/layers-slices-boundaries/layers.test.js new file mode 100644 index 0000000..9328a08 --- /dev/null +++ b/rules/layers-slices-boundaries/layers.test.js @@ -0,0 +1,37 @@ +const { ESLint } = require("eslint"); +const assert = require("assert"); +const { mockImports } = require("../../utils/mock-import"); +const cfg = require("./"); + +const eslint = new ESLint({ + useEslintrc: false, + baseConfig: 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-boundaries/slices.test.js b/rules/layers-slices-boundaries/slices.test.js new file mode 100644 index 0000000..c68f26a --- /dev/null +++ b/rules/layers-slices-boundaries/slices.test.js @@ -0,0 +1,77 @@ +const { ESLint } = require("eslint"); +const assert = require("assert"); +const { mockImports } = require("../../utils/mock-import"); +const cfg = require("./"); + +const eslint = new ESLint({ + useEslintrc: false, + baseConfig: mockImports(cfg), +}); + +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(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); + }); + + it("should lint without errors", async () => { + const codeSnippet = [ + `import { getAuthCtx } from "entities/session";`, + `import { UserAvatar } from "entities/user";`, + ].join("\n"); + + const report = await eslint.lintText(codeSnippet, { + filePath: "src/features/auth-form/index.js", + }); + + assert.strictEqual(report[0].errorCount, 0); + }); +}); From 7d51a70e98443fffcf547368619941241093565f Mon Sep 17 00:00:00 2001 From: Evgeniy Date: Sat, 11 Dec 2021 20:21:48 +0300 Subject: [PATCH 4/4] refactor: move tools. added layersLib re-export for all layers tools. --- rules/layers-slices-boundaries/index.js | 9 +- rules/layers-slices-boundaries/index.test.js | 102 ------------------- rules/public-api-boundaries/index.js | 8 +- test/config.test.js | 2 +- utils/{helpers.js => layers.js} | 2 +- {test/utils => utils}/tools.js | 0 6 files changed, 11 insertions(+), 112 deletions(-) delete mode 100644 rules/layers-slices-boundaries/index.test.js rename utils/{helpers.js => layers.js} (81%) rename {test/utils => utils}/tools.js (100%) diff --git a/rules/layers-slices-boundaries/index.js b/rules/layers-slices-boundaries/index.js index 94a97b8..71a9db3 100644 --- a/rules/layers-slices-boundaries/index.js +++ b/rules/layers-slices-boundaries/index.js @@ -1,13 +1,14 @@ -const { getLowerLayers, FS_LAYERS } = require("../../utils/helpers"); +const { layersLib } = require("../../utils/layers"); const getLayersRules = () => - FS_LAYERS.map((layer) => ({ + + layersLib.FS_LAYERS.map((layer) => ({ from: layer, - allow: getLowerLayers(layer), + allow: layersLib.getLowerLayers(layer), })); const getLayersBoundariesElements = () => - FS_LAYERS.map((layer) => ({ + layersLib.FS_LAYERS.map((layer) => ({ type: layer, pattern: `${layer}/*`, mode: "folder", diff --git a/rules/layers-slices-boundaries/index.test.js b/rules/layers-slices-boundaries/index.test.js deleted file mode 100644 index 4547e30..0000000 --- a/rules/layers-slices-boundaries/index.test.js +++ /dev/null @@ -1,102 +0,0 @@ -const { ESLint } = require("eslint"); -const assert = require("assert"); -const { mockImports } = require("../../utils/mock-import"); -const cfg = require("./"); - -const eslint = new ESLint({ - useEslintrc: false, - baseConfig: 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); - }); - - it("should lint without errors", async () => { - const codeSnippet = [ - `import { getAuthCtx } from "entities/session";`, - `import { UserAvatar } from "entities/user";`, - ].join("\n"); - - const report = await eslint.lintText(codeSnippet, { - filePath: "src/features/auth-form/index.js", - }); - - assert.strictEqual(report[0].errorCount, 0); - }); - - 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/public-api-boundaries/index.js b/rules/public-api-boundaries/index.js index cc2d1a6..618fe1e 100644 --- a/rules/public-api-boundaries/index.js +++ b/rules/public-api-boundaries/index.js @@ -1,7 +1,7 @@ -const { getUpperLayers, FS_SEGMENTS, FS_LAYERS } = require("../../utils/helpers"); +const { layersLib } = require("../../utils/layers"); -const FS_SEGMENTS_REG = FS_SEGMENTS.join("|"); -const FS_LAYERS_REG = FS_LAYERS.join("|"); +const FS_SEGMENTS_REG = layersLib.FS_SEGMENTS.join("|"); +const FS_LAYERS_REG = layersLib.FS_LAYERS.join("|"); module.exports = { parserOptions: { @@ -19,7 +19,7 @@ module.exports = { * 'entities/form/ui' // Fail * 'entities/form' // Pass */ - `**/*(${getUpperLayers("shared").join("|")})/!(${FS_SEGMENTS_REG})`, + `**/*(${layersLib.getUpperLayers("shared").join("|")})/!(${FS_SEGMENTS_REG})`, /** * Allow slices with structure grouping diff --git a/test/config.test.js b/test/config.test.js index aceda97..381e46f 100644 --- a/test/config.test.js +++ b/test/config.test.js @@ -1,6 +1,6 @@ const assert = require("assert"); const cfg = require("../"); -const utils = require("./utils/tools"); +const utils = require("../utils/tools"); describe("config is valid", () => { it("parserOptions ~ Record", () => { diff --git a/utils/helpers.js b/utils/layers.js similarity index 81% rename from utils/helpers.js rename to utils/layers.js index 8fc3287..0b0f37f 100644 --- a/utils/helpers.js +++ b/utils/layers.js @@ -20,4 +20,4 @@ const FS_SEGMENTS = [ const getLowerLayers = (layer) => FS_LAYERS.slice(FS_LAYERS.indexOf(layer) + 1); const getUpperLayers = (layer) => FS_LAYERS.slice(0, FS_LAYERS.indexOf(layer)); -module.exports = { FS_LAYERS, FS_SEGMENTS, getLowerLayers, getUpperLayers }; +module.exports.layersLib = { FS_LAYERS, FS_SEGMENTS, getLowerLayers, getUpperLayers } ; diff --git a/test/utils/tools.js b/utils/tools.js similarity index 100% rename from test/utils/tools.js rename to utils/tools.js