diff --git a/package.json b/package.json index 2609b46..554fa1c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Igor Kowalski (Igorkowalski94)", "name": "eslint-plugin-project-structure", - "version": "3.4.0", + "version": "3.5.0", "license": "MIT", "description": "Powerful ESLint plugin with rules to help you achieve a scalable, consistent, and well-structured project. Create your own framework! Define your folder structure, file composition, advanced naming conventions, and create independent modules. Take your project to the next level and save time by automating the review of key principles of a healthy project! react folder structure react file structure react project structure react conventions architecture react next.js angular node solid vue svelte", "keywords": [ diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts index fccce37..95ea293 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts @@ -43,6 +43,7 @@ export const handlePositionIndex = ({ name, positionIndex, positionIndexRules, + selectorType, }); validatePositionIndex({ diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types.ts index 2893e61..cb6c4bd 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types.ts @@ -1,4 +1,8 @@ +import { Selector } from "rules/fileComposition/fileComposition.types"; + export interface PositionIndexRule { positionIndex: number; format: string[]; + selector: Selector | Selector[]; + expressionName?: string; } diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.test.ts index bd90b62..4e6f80a 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.test.ts @@ -1,4 +1,4 @@ -import { PositionIndexRule } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types"; +import { SelectorType } from "rules/fileComposition/fileComposition.types"; import { getPositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex"; import { getSelectorNamesFromBody } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody"; @@ -12,39 +12,51 @@ jest.mock( describe("getPositionIndex", () => { test.each<{ name: string; - positionIndexRules: PositionIndexRule[]; + selectorType: SelectorType; expected: number; }>([ { - name: "Return", - positionIndexRules: [ - { format: ["Props"], positionIndex: 0 }, - { format: ["Return"], positionIndex: 1 }, - { format: ["Name"], positionIndex: 2 }, - ], + name: "Props", + selectorType: "interface", expected: 0, }, + { + name: "Return", + selectorType: "interface", + expected: 1, + }, { name: "Name", - positionIndexRules: [ - { format: ["variable"], positionIndex: 0 }, - { format: ["Return"], positionIndex: 1 }, - { format: ["Name"], positionIndex: 2 }, - ], + selectorType: "arrowFunction", expected: 2, }, { - name: "Name", - positionIndexRules: [], + name: "Last2", + selectorType: "variable", + expected: 6, + }, + { + name: "Last1", + selectorType: "variable", + expected: 7, + }, + { + name: "Random", + selectorType: "variable", expected: 1, }, ])( "Should return correct value for = %o", - ({ name, positionIndexRules, expected }) => { + ({ name, selectorType, expected }) => { (getSelectorNamesFromBody as jest.Mock).mockReturnValue([ - "variable", - "Return", - "Name", + { selector: "interface", name: "Return" }, + { selector: "variable", name: "Last2" }, + { selector: "variable", name: "variable3" }, + { selector: "arrowFunction", name: "Name" }, + { selector: "interface", name: "Props" }, + { selector: "variable", name: "variable1" }, + { selector: "variable", name: "Last1" }, + { selector: "variable", name: "variable2" }, ]); expect( @@ -52,7 +64,14 @@ describe("getPositionIndex", () => { bodyWithoutImports: [], name, positionIndex: 1, - positionIndexRules, + positionIndexRules: [ + { format: ["Props"], selector: "interface", positionIndex: 0 }, + { format: ["Return"], selector: "interface", positionIndex: 1 }, + { format: ["Name"], selector: "arrowFunction", positionIndex: 2 }, + { format: ["Last2"], selector: "variable", positionIndex: -2 }, + { format: ["Last1"], selector: "variable", positionIndex: -100 }, + ], + selectorType, }), ).toEqual(expected); }, diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts index e453f86..cf28a13 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts @@ -1,5 +1,7 @@ import { TSESTree } from "@typescript-eslint/utils"; +import { SelectorType } from "rules/fileComposition/fileComposition.types"; +import { isCorrectSelector } from "rules/fileComposition/helpers/validateFile/helpers/isCorrectSelector"; import { PositionIndexRule } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types"; import { getSelectorNamesFromBody } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody"; @@ -8,6 +10,7 @@ interface GetPositionIndexProps { positionIndexRules: PositionIndexRule[]; bodyWithoutImports: TSESTree.ProgramStatement[]; name: string; + selectorType: SelectorType; } export const getPositionIndex = ({ @@ -15,26 +18,63 @@ export const getPositionIndex = ({ positionIndex, bodyWithoutImports, name, + selectorType, }: GetPositionIndexProps): number => { const selectorNamesFromBody = getSelectorNamesFromBody(bodyWithoutImports); const positionIndexRulesBody = selectorNamesFromBody - .map((name) => - positionIndexRules.find(({ format }) => format.includes(name)), + .map((body) => + positionIndexRules.find( + ({ format, selector }) => + isCorrectSelector({ + selector, + selectorType: body.selector, + expressionName: body.expressionName, + }) && format.includes(body.name), + ), ) + .map((rule) => { + if (!rule) return; + + const positionIndexNegativeDefault = selectorNamesFromBody.length - 1; + const positionIndexNegative = + selectorNamesFromBody.length + rule.positionIndex; + const currentPositionIndexNegative = + positionIndexNegative < 0 + ? positionIndexNegativeDefault + : positionIndexNegative; + + return { + ...rule, + positionIndex: + rule.positionIndex < 0 + ? currentPositionIndexNegative + : rule.positionIndex, + }; + }) .filter((v): v is PositionIndexRule => v !== undefined) .sort((a, b) => a.positionIndex - b.positionIndex); + let sortedIndex = 0; + const positionIndexRulesNewOrder = positionIndexRulesBody.map( - ({ format }, index) => ({ + ({ format, selector, expressionName }) => ({ format, - positionIndex: index, + selector, + expressionName, + positionIndex: positionIndexRulesBody[sortedIndex++], }), ); - const newPositionIndex = positionIndexRulesNewOrder.find(({ format }) => - format.includes(name), - )?.positionIndex; + const newPositionIndex = positionIndexRulesNewOrder.find( + ({ format, expressionName, selector }) => + format.includes(name) && + isCorrectSelector({ + selector, + selectorType, + expressionName, + }), + )?.positionIndex.positionIndex; return newPositionIndex ?? positionIndex; }; diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.test.ts index 8100a71..d26153a 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.test.ts @@ -10,6 +10,8 @@ describe("validateRules", () => { { selector: "variable" }, ], }), - ).toEqual([{ format: ["Props"], positionIndex: 1 }]); + ).toEqual([ + { format: ["Props"], selector: "arrowFunction", positionIndex: 1 }, + ]); }); }); diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts index d85619e..5ef8c81 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts @@ -17,7 +17,7 @@ export const getPositionIndexRules = ({ filenamePath, }: GetPositionIndexRulesProps): PositionIndexRule[] => rules - .map(({ format, filenamePartsToRemove, positionIndex }) => { + .map(({ format, selector, filenamePartsToRemove, positionIndex }) => { if (positionIndex === undefined) return; const filenameWithoutParts = getFilenameWithoutParts({ @@ -27,6 +27,7 @@ export const getPositionIndexRules = ({ return { positionIndex, + selector, format: prepareFormat({ format, filenameWithoutParts, diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.test.ts index 1c948f0..ae69b78 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.test.ts @@ -382,25 +382,108 @@ describe("getSelectorNamesFromBody", () => { range: [397, 434], exportKind: "value", }, + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "VariableExpression", + range: [9, 27], + }, + init: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "fn", + range: [30, 32], + }, + arguments: [], + optional: false, + range: [30, 34], + }, + range: [9, 34], + }, + ], + kind: "const", + range: [3, 34], + }, ] as unknown as TSESTree.ProgramStatement[]), ).toEqual([ - "variable", - "arrowFunction", - "Function", - "Class", - "Type", - "Interface", - "Enum", - "variable", - "arrowFunction", - "Function", - "Class", - "Type", - "Interface", - "Enum", - "Function", - "Class", - "Interface", + { + name: "variable", + selector: "variable", + }, + { + name: "arrowFunction", + selector: "variable", + }, + { + name: "Function", + selector: "function", + }, + { + name: "Class", + selector: "class", + }, + { + name: "Type", + selector: "type", + }, + { + name: "Interface", + selector: "interface", + }, + { + name: "Enum", + selector: "enum", + }, + { + name: "variable", + selector: "variable", + }, + { + name: "arrowFunction", + selector: "variable", + }, + { + name: "Function", + selector: "function", + }, + { + name: "Class", + selector: "class", + }, + { + name: "Type", + selector: "type", + }, + { + name: "Interface", + selector: "interface", + }, + { + name: "Enum", + selector: "enum", + }, + { + name: "Function", + selector: "function", + }, + { + name: "Class", + selector: "class", + }, + { + name: "Interface", + selector: "interface", + }, + { + expressionName: "fn", + name: "VariableExpression", + selector: "variableExpression", + }, ]); }); }); diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts index abac431..f23efe5 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts @@ -1,11 +1,21 @@ /* eslint-disable complexity */ import { TSESTree } from "@typescript-eslint/utils"; +import { SelectorType } from "rules/fileComposition/fileComposition.types"; +import { getIdentifierFromExpression } from "rules/fileComposition/helpers/getIdentifierFromExpression"; +import { SELECTORS } from "rules/fileComposition/helpers/validateFile/validateFile.consts"; + +interface GetSelectorNamesFromBodyReturn { + name: string; + selector: SelectorType; + expressionName?: string; +} + export const getSelectorNamesFromBody = ( body: TSESTree.ProgramStatement[], -): string[] => +): GetSelectorNamesFromBodyReturn[] => body - .map((node) => { + .map((node): GetSelectorNamesFromBodyReturn | undefined => { const currentNode = node.type === TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration || node.type === TSESTree.AST_NODE_TYPES.ExportNamedDeclaration @@ -17,19 +27,39 @@ export const getSelectorNamesFromBody = ( currentNode.declarations[0].id.type === TSESTree.AST_NODE_TYPES.Identifier ) { - return currentNode.declarations[0].id.name; + const expressionName = getIdentifierFromExpression( + currentNode.declarations[0].init, + ); + + if (expressionName) { + return { + selector: "variableExpression", + name: currentNode.declarations[0].id.name, + expressionName, + }; + } + + return { + selector: "variable", + name: currentNode.declarations[0].id.name, + }; } if ( - currentNode?.type === TSESTree.AST_NODE_TYPES.FunctionDeclaration || - currentNode?.type === TSESTree.AST_NODE_TYPES.ClassDeclaration || - currentNode?.type === TSESTree.AST_NODE_TYPES.TSInterfaceDeclaration || - currentNode?.type === TSESTree.AST_NODE_TYPES.TSTypeAliasDeclaration || - currentNode?.type === TSESTree.AST_NODE_TYPES.TSEnumDeclaration + (currentNode?.type === TSESTree.AST_NODE_TYPES.FunctionDeclaration || + currentNode?.type === TSESTree.AST_NODE_TYPES.ClassDeclaration || + currentNode?.type === + TSESTree.AST_NODE_TYPES.TSInterfaceDeclaration || + currentNode?.type === + TSESTree.AST_NODE_TYPES.TSTypeAliasDeclaration || + currentNode?.type === TSESTree.AST_NODE_TYPES.TSEnumDeclaration) && + currentNode.id?.name ) { - return currentNode.id?.name; + const selector = SELECTORS[currentNode.type]; + + return { selector, name: currentNode.id.name }; } return undefined; }) - .filter((v): v is string => v !== undefined); + .filter((v): v is GetSelectorNamesFromBodyReturn => v !== undefined);