diff --git a/documentation/migration-to-2.1.0.md b/documentation/migration-to-2.1.0.md
index af394ca..54db920 100644
--- a/documentation/migration-to-2.1.0.md
+++ b/documentation/migration-to-2.1.0.md
@@ -6,9 +6,12 @@ A minor configuration fix will be required for version <= 1.4.7.
### General changes:
-- The entire documentation has been rewritten for ESLint's new config system. Examples with the old ESLint configuration can be found in the [**playground**](https://github.com/Igorkowalski94/eslint-plugin-project-structure-playground) for eslint-plugin-project-structure rules.
+- A shorter notation option for [structure](https://github.com/Igorkowalski94/eslint-plugin-project-structure/blob/main/documentation/project-structure-folder-structure.md#structure).
+- New build-in {SNAKE_CASE} regexParameter.
+- Improvements for {PascalCase} and {camelCase} regexParameters.
+- The entire documentation has been rewritten for ESLint's new config system. Examples with the old ESLint configuration can be found in the [playground](https://github.com/Igorkowalski94/eslint-plugin-project-structure-playground) for eslint-plugin-project-structure rules.
- New option for creating a configuration file in an .mjs file with TypeScript support.
-- Enforcing the existence of a file/folder when a specific file/folder exists. For example, if `src/Component.tsx` exists, then `src/Component.test.tsx` and `src/stories/Component.stories.tsx` must also exist.
+- [Enforcing the existence](https://github.com/Igorkowalski94/eslint-plugin-project-structure/blob/main/documentation/project-structure-folder-structure.md#enforce-existence) of a files/folders when a specific file/folder exists. For example, if `src/Component.tsx` exists, then `src/Component.test.tsx` and `src/stories/Component.stories.tsx` must also exist.
- You can now use comments in folderStructure.json and independentModules.json files.
- Improved error messages for folder-structure.
- Easier configuration of folder-structure. The "extension" key has been removed, now the file extension will be part of the "name". You don't need to add /^$/ to your regex, they will be added automatically and other improvements.
@@ -122,17 +125,13 @@ The following improvements are automatically added to the regex:
From: ${{key}}
```jsonc
-{
- "name": "/^${{parentName}}$/",
-}
+{ "name": "/^${{parentName}}$/" }
```
To: {key}
```jsonc
-{
- "name": "{parentName}",
-}
+{ "name": "{parentName}" }
```
### Changes for build-in PascalCase
@@ -169,9 +168,7 @@ Add **`SNAKE_CASE`** validation to your regex.
The added regex is **`((([A-Z]|\d)+_)*([A-Z]|\d)+)`**.
```jsonc
-{
- "name": "{SNAKE_CASE}",
-}
+{ "name": "{SNAKE_CASE}" }
```
### New rules:
diff --git a/documentation/project-structure-folder-structure.md b/documentation/project-structure-folder-structure.md
index 28da06f..9843212 100644
--- a/documentation/project-structure-folder-structure.md
+++ b/documentation/project-structure-folder-structure.md
@@ -8,7 +8,7 @@ Enforce rules on folder structure to keep your project consistent, orderly and w
✅ File/Folder name regex validation with features like wildcard `*` and treating `.` as a character, along with other conveniences.
✅ Build in case validation.
✅ Inheriting the folder's name. The file/folder inherits the name of the folder in which it is located. Option of adding your own prefixes/suffixes or changing the case.
-✅ Enforcing the existence of a file/folder when a specific file/folder exists. For example, if `./src/Component.tsx` exists, then `./src/Component.test.tsx` and `./src/stories/Component.stories.tsx` must also exist.
+✅ Enforcing the existence of a files/folders when a specific file/folder exists. For example, if `./src/Component.tsx` exists, then `./src/Component.test.tsx` and `./src/stories/Component.stories.tsx` must also exist.
✅ Reusable rules for folder structures.
✅ An option to create a separate configuration file with TypeScript support.
✅ Forcing a nested/flat structure for a given folder.
@@ -164,20 +164,18 @@ Create a **`folderStructure.mjs`** in the root of your project.
import { createFolderStructure } from "eslint-plugin-project-structure";
export const folderStructureConfig = createFolderStructure({
- structure: {
- children: [
- // Allow any files in the root of your project, like package.json, eslint.config.mjs, etc. You can add rules for them separately.
- // You can also add exceptions like this: "(?!folderStructure)*"
- { name: "*" },
- {
- name: "src",
- children: [
- { name: "index.tsx" },
- { name: "components", children: [{ name: "{PascalCase}.tsx" }] },
- ],
- },
- ],
- },
+ structure: [
+ // Allow any files in the root of your project, like package.json, eslint.config.mjs, etc. You can add rules for them separately.
+ // You can also add exceptions like this: "(?!folderStructure)*"
+ { name: "*" },
+ {
+ name: "src",
+ children: [
+ { name: "index.tsx" },
+ { name: "components", children: [{ name: "{PascalCase}.tsx" }] },
+ ],
+ },
+ ],
});
```
@@ -239,17 +237,15 @@ import { createFolderStructure } from "eslint-plugin-project-structure";
export const folderStructureConfig = createFolderStructure({
ignorePatterns: ["src/legacy/**"],
- structure: {
- children: [
- // Allow any files in the root of your project, like package.json, eslint.config.mjs, etc. You can add rules for them separately.
- // You can also add exceptions like this: "(?!folderStructure)*"
- { name: "*" },
- {
- name: "src",
- children: [{ ruleId: "hooks_folder" }, { ruleId: "components_folder" }],
- },
- ],
- },
+ structure: [
+ // Allow any files in the root of your project, like package.json, eslint.config.mjs, etc. You can add rules for them separately.
+ // You can also add exceptions like this: "(?!folderStructure)*"
+ { name: "*" },
+ {
+ name: "src",
+ children: [{ ruleId: "hooks_folder" }, { ruleId: "components_folder" }],
+ },
+ ],
rules: {
hooks_folder: {
name: "hooks",
@@ -441,7 +437,13 @@ In `enforceExistence`, two references are available for use:
```jsonc
{
"structure": {
+ // If root directory exists.
+ "enforceExistence": [
+ "src", // ./src must exist.
+ "src/components", // ./src/components must exist.
+ ],
"children": [
+ { "name": "*" },
{
"name": "src",
"children": [
@@ -459,20 +461,12 @@ In `enforceExistence`, two references are available for use:
},
],
},
- {
- "name": "*",
- // If any file exists in the root directory of the project.
- "enforceExistence": [
- "src", // ./src must exist.
- "src/components", // ./src/components must exist.
- ],
- },
],
},
}
```
-### **`structure`**: ``
+### **`structure`**: ` | []`
The structure of your project and its rules.
@@ -490,9 +484,25 @@ The structure of your project and its rules.
└── 📄 ...
```
+```jsonc
+{
+ "structure": [
+ { "name": "libs", "children": [] },
+ { "name": "src", "children": [] },
+ { "name": "yourCoolFolderName", "children": [] },
+ // Allow any files in the root of your project, like package.json, eslint.config.mjs, etc. You can add rules for them separately.
+ // You can also add exceptions like this: "(?!folderStructure)*"
+ { "name": "*" },
+ ],
+}
+```
+
+or
+
```jsonc
{
"structure": {
+ "enforceExistence": ["src"],
"children": [
{ "name": "libs", "children": [] },
{ "name": "src", "children": [] },
diff --git a/folderStructure.schema.json b/folderStructure.schema.json
index 64171ec..01cd27a 100644
--- a/folderStructure.schema.json
+++ b/folderStructure.schema.json
@@ -48,7 +48,17 @@
}
},
"structure": {
- "$ref": "#/definitions/Rule"
+ "oneOf": [
+ {
+ "$ref": "#/definitions/Rule"
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Rule"
+ }
+ }
+ ]
},
"rules": {
"type": "object",
diff --git a/package.json b/package.json
index 5e6bd7e..74b880c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"author": "Igor Kowalski (Igorkowalski94)",
"name": "eslint-plugin-project-structure",
- "version": "2.1.17",
+ "version": "2.1.18",
"license": "MIT",
"description": "Eslint plugin with rules that will help you achieve a project structure that is scalable, consistent, and well thought out. Whether you're working alone or with a small or large team, save time by automating the reviews of key principles for a healthy project!",
"keywords": [
diff --git a/src/rules/folderStructure/folderStructure.consts.ts b/src/rules/folderStructure/folderStructure.consts.ts
index e10c251..870f17c 100644
--- a/src/rules/folderStructure/folderStructure.consts.ts
+++ b/src/rules/folderStructure/folderStructure.consts.ts
@@ -1,75 +1,6 @@
-import { JSONSchema4 } from "@typescript-eslint/utils/dist/json-schema";
-
export const REFERENCES = {
parentName: "{parentName}",
ParentName: "{ParentName}",
name: "{name}",
Name: "{Name}",
};
-
-export const FOLDER_STRUCTURE_SCHEMA: JSONSchema4 = {
- $schema: "http://json-schema.org/draft-07/schema#",
- definitions: {
- Rule: {
- type: "object",
- default: { name: "" },
- properties: {
- ruleId: {
- type: "string",
- default: "",
- },
- name: {
- type: "string",
- default: "",
- },
- children: {
- type: "array",
- default: [],
- items: {
- $ref: "#/definitions/Rule",
- },
- },
- enforceExistence: {
- type: "array",
- default: [],
- items: {
- type: "string",
- },
- },
- },
- additionalProperties: false,
- },
- RegexParameters: {
- type: "object",
- default: {},
- additionalProperties: {
- type: "string",
- },
- },
- },
- type: "object",
- properties: {
- ignorePatterns: {
- type: "array",
- default: [],
- items: {
- type: "string",
- },
- },
- structure: {
- $ref: "#/definitions/Rule",
- },
- rules: {
- type: "object",
- default: {},
- additionalProperties: {
- $ref: "#/definitions/Rule",
- },
- },
- regexParameters: {
- $ref: "#/definitions/RegexParameters",
- },
- },
- required: ["structure"],
- additionalProperties: false,
-};
diff --git a/src/rules/folderStructure/folderStructure.types.ts b/src/rules/folderStructure/folderStructure.types.ts
index 489b9a0..06713d5 100644
--- a/src/rules/folderStructure/folderStructure.types.ts
+++ b/src/rules/folderStructure/folderStructure.types.ts
@@ -13,7 +13,7 @@ export type RegexParameters = Record;
export interface FolderStructureConfig {
ignorePatterns?: string[];
- structure: Rule;
+ structure: Rule | Rule[];
rules?: Record>;
regexParameters?: RegexParameters;
}
diff --git a/src/rules/folderStructure/helpers/createFolderStructure.ts b/src/rules/folderStructure/helpers/createFolderStructure.ts
index eb2c3ef..f106b1a 100644
--- a/src/rules/folderStructure/helpers/createFolderStructure.ts
+++ b/src/rules/folderStructure/helpers/createFolderStructure.ts
@@ -7,7 +7,7 @@ import {
export const createFolderStructure = <
R extends Record>,
>(config: {
- structure: Rule;
+ structure: Rule | Rule[];
rules?: R;
ignorePatterns?: string[];
regexParameters?: RegexParameters;
diff --git a/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getPaths.ts b/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getPaths.ts
index c5c8ada..6e86ee4 100644
--- a/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getPaths.ts
+++ b/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getPaths.ts
@@ -3,6 +3,7 @@ import path, { sep } from "path";
interface GetPathsProps {
cwd: string;
filename: string;
+ rootFolderName: string;
}
interface GetPathsReturn {
@@ -10,11 +11,15 @@ interface GetPathsReturn {
filenameWithoutCwd: string;
}
-export const getPaths = ({ cwd, filename }: GetPathsProps): GetPathsReturn => {
+export const getPaths = ({
+ cwd,
+ filename,
+ rootFolderName,
+}: GetPathsProps): GetPathsReturn => {
const filenameWithoutCwd = path.relative(cwd, filename).replaceAll(sep, "/");
const pathname = path
- .join("structure", filenameWithoutCwd)
+ .join(rootFolderName, filenameWithoutCwd)
.replaceAll(sep, "/");
return {
diff --git a/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule.test.ts b/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule.test.ts
new file mode 100644
index 0000000..059c33e
--- /dev/null
+++ b/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule.test.ts
@@ -0,0 +1,20 @@
+import {
+ FolderStructureConfig,
+ Rule,
+} from "rules/folderStructure/folderStructure.types";
+import { getRootRule } from "rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule";
+
+describe("getRootRule", () => {
+ it.each<{ structure: FolderStructureConfig["structure"]; expected: Rule }>([
+ {
+ structure: [{ children: [] }, { name: "index.ts" }],
+ expected: { children: [{ children: [] }, { name: "index.ts" }] },
+ },
+ {
+ structure: { enforceExistence: [], children: [] },
+ expected: { enforceExistence: [], children: [] },
+ },
+ ])("Should return correct value for %o", ({ structure, expected }) => {
+ expect(getRootRule(structure)).toEqual(expected);
+ });
+});
diff --git a/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule.ts b/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule.ts
new file mode 100644
index 0000000..d1381df
--- /dev/null
+++ b/src/rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule.ts
@@ -0,0 +1,15 @@
+import {
+ FolderStructureConfig,
+ Rule,
+} from "rules/folderStructure/folderStructure.types";
+
+export const getRootRule = (
+ structure: FolderStructureConfig["structure"],
+): Rule => {
+ if (Array.isArray(structure))
+ return {
+ children: structure,
+ };
+
+ return structure;
+};
diff --git a/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.consts.ts b/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.consts.ts
new file mode 100644
index 0000000..080508f
--- /dev/null
+++ b/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.consts.ts
@@ -0,0 +1,78 @@
+import { JSONSchema4 } from "@typescript-eslint/utils/dist/json-schema";
+
+export const FOLDER_STRUCTURE_SCHEMA: JSONSchema4 = {
+ $schema: "http://json-schema.org/draft-07/schema#",
+ definitions: {
+ Rule: {
+ type: "object",
+ default: { name: "" },
+ properties: {
+ ruleId: {
+ type: "string",
+ default: "",
+ },
+ name: {
+ type: "string",
+ default: "",
+ },
+ children: {
+ type: "array",
+ default: [],
+ items: {
+ $ref: "#/definitions/Rule",
+ },
+ },
+ enforceExistence: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ },
+ },
+ },
+ additionalProperties: false,
+ },
+ RegexParameters: {
+ type: "object",
+ default: {},
+ additionalProperties: {
+ type: "string",
+ },
+ },
+ },
+ type: "object",
+ properties: {
+ ignorePatterns: {
+ type: "array",
+ default: [],
+ items: {
+ type: "string",
+ },
+ },
+ structure: {
+ oneOf: [
+ {
+ $ref: "#/definitions/Rule",
+ },
+ {
+ type: "array",
+ items: {
+ $ref: "#/definitions/Rule",
+ },
+ },
+ ],
+ },
+ rules: {
+ type: "object",
+ default: {},
+ additionalProperties: {
+ $ref: "#/definitions/Rule",
+ },
+ },
+ regexParameters: {
+ $ref: "#/definitions/RegexParameters",
+ },
+ },
+ required: ["structure"],
+ additionalProperties: false,
+};
diff --git a/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.test.ts b/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.test.ts
index e86c6c8..361dad0 100644
--- a/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.test.ts
+++ b/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.test.ts
@@ -1,3 +1,5 @@
+import path from "path";
+
import { validateConfig } from "helpers/validateConfig";
import { isIgnoredPathname } from "rules/folderStructure/helpers/validateFolderStructure/helpers/isIgnoredPathname";
@@ -25,9 +27,15 @@ describe("validateFolderStructure", () => {
expect(
validateFolderStructure({
- cwd: "",
+ cwd: path.join("C:", "rootFolderName"),
config: { structure: {} },
- filename: "src/features/ComponentName.tsx",
+ filename: path.join(
+ "C:",
+ "rootFolderName",
+ "src",
+ "features",
+ "ComponentName.tsx",
+ ),
}),
).toEqual(undefined);
});
@@ -40,16 +48,22 @@ describe("validateFolderStructure", () => {
(validateConfig as jest.Mock).mockImplementation();
validateFolderStructure({
- cwd: "",
+ cwd: path.join("C:", "rootFolderName"),
config: { structure: {} },
- filename: "src/features/ComponentName.tsx",
+ filename: path.join(
+ "C:",
+ "rootFolderName",
+ "src",
+ "features",
+ "ComponentName.tsx",
+ ),
});
expect(validatePathMock).toHaveBeenCalledWith({
- pathname: "structure/src/features/ComponentName.tsx",
+ pathname: "rootFolderName/src/features/ComponentName.tsx",
filenameWithoutCwd: "src/features/ComponentName.tsx",
- cwd: "",
- parentName: "structure",
+ cwd: path.join("C:", "rootFolderName"),
+ parentName: "rootFolderName",
rule: {},
config: { structure: {} },
});
diff --git a/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.ts b/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.ts
index 8507797..f704f9e 100644
--- a/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.ts
+++ b/src/rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.ts
@@ -1,9 +1,12 @@
+import { sep } from "path";
+
import { validateConfig } from "helpers/validateConfig";
-import { FOLDER_STRUCTURE_SCHEMA } from "rules/folderStructure/folderStructure.consts";
import { FolderStructureConfig } from "rules/folderStructure/folderStructure.types";
import { getPaths } from "rules/folderStructure/helpers/validateFolderStructure/helpers/getPaths";
+import { getRootRule } from "rules/folderStructure/helpers/validateFolderStructure/helpers/getRootRule";
import { isIgnoredPathname } from "rules/folderStructure/helpers/validateFolderStructure/helpers/isIgnoredPathname";
+import { FOLDER_STRUCTURE_SCHEMA } from "rules/folderStructure/helpers/validateFolderStructure/validateFolderStructure.consts";
import { validatePath } from "rules/folderStructure/helpers/validatePath/validatePath";
interface ValidateFolderStructureProps {
@@ -21,7 +24,14 @@ export const validateFolderStructure = ({
const { structure, ignorePatterns } = config;
- const { filenameWithoutCwd, pathname } = getPaths({ cwd, filename });
+ const rootRule = getRootRule(structure);
+ const rootFolderName = cwd.split(sep).reverse()[0];
+
+ const { filenameWithoutCwd, pathname } = getPaths({
+ cwd,
+ filename,
+ rootFolderName,
+ });
if (isIgnoredPathname({ pathname: filenameWithoutCwd, ignorePatterns }))
return;
@@ -30,8 +40,8 @@ export const validateFolderStructure = ({
pathname,
filenameWithoutCwd,
cwd,
- parentName: "structure",
- rule: structure,
+ parentName: rootFolderName,
+ rule: rootRule,
config,
});
};