diff --git a/fileComposition.mjs b/fileComposition.mjs index c2e7681..0c2e414 100644 --- a/fileComposition.mjs +++ b/fileComposition.mjs @@ -34,7 +34,9 @@ export const fileCompositionConfig = createFileComposition({ { filePattern: "src/rules/*/*.ts", - allowOnlySpecifiedSelectors: true, + allowOnlySpecifiedSelectors: { + file: false, + }, rules: [ { selector: { type: "variableExpression", limitTo: "ESLintUtils" }, @@ -49,13 +51,13 @@ export const fileCompositionConfig = createFileComposition({ rootSelectorsLimits: [{ selector: ["arrowFunction", "class"], limit: 1 }], rules: [ { - selector: "interface", + selector: ["interface", "type"], positionIndex: 0, scope: "fileRoot", format: "{FileName}Props", }, { - selector: "interface", + selector: ["interface", "type"], positionIndex: 1, scope: "fileRoot", format: "{FileName}Return", diff --git a/src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts b/src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts index 3a7316e..0a3fa99 100644 --- a/src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts +++ b/src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts @@ -11,6 +11,7 @@ import { } from "rules/fileComposition/fileComposition.types"; import { FILE_COMPOSITION_SCHEMA } from "rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.consts"; +// eslint-disable-next-line project-structure/file-composition interface GetFileCompositionConfigReturn { config: FileCompositionConfig; fileConfig?: FileRules; diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts.ts new file mode 100644 index 0000000..4180f37 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts.ts @@ -0,0 +1,19 @@ +import { Rule } from "rules/fileComposition/fileComposition.types"; +import { getFileNameWithoutExtension } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/getFileNameWithoutExtension"; +import { removeFilenameParts } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts"; + +interface GetFilenameWithoutPartsProps { + filenamePartsToRemove: Rule["filenamePartsToRemove"]; + filenamePath: string; +} + +export const getFilenameWithoutParts = ({ + filenamePartsToRemove, + filenamePath, +}: GetFilenameWithoutPartsProps): string => { + const filenameWithoutExtension = getFileNameWithoutExtension(filenamePath); + return removeFilenameParts({ + filenameWithoutExtension, + filenamePartsToRemove, + }); +}; diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFileNameWithoutExtension.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/getFileNameWithoutExtension.ts similarity index 100% rename from src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFileNameWithoutExtension.ts rename to src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/getFileNameWithoutExtension.ts diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.test.ts similarity index 90% rename from src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts.test.ts rename to src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.test.ts index d76fb5d..d28c6ca 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.test.ts @@ -1,5 +1,5 @@ import { Rule } from "rules/fileComposition/fileComposition.types"; -import { removeFilenameParts } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts"; +import { removeFilenameParts } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts"; describe("removeFilenameParts", () => { test.each<{ diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.ts similarity index 100% rename from src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts.ts rename to src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.ts 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 new file mode 100644 index 0000000..fccce37 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts @@ -0,0 +1,55 @@ +import { RegexParameters } from "types"; + +import { + Context, + Node, + Rule, + SelectorType, +} from "rules/fileComposition/fileComposition.types"; +import { getBodyWithoutImports } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getBodyWithoutImports"; +import { getPositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex"; +import { getPositionIndexRules } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules"; +import { validatePositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex"; + +interface HandlePositionIndexProps { + positionIndex: number; + node: Node; + filenamePath: string; + rules: Rule[]; + regexParameters?: RegexParameters; + context: Context; + name: string; + selectorType: SelectorType; +} + +export const handlePositionIndex = ({ + filenamePath, + positionIndex, + node, + rules, + regexParameters, + context, + name, + selectorType, +}: HandlePositionIndexProps): void => { + const positionIndexRules = getPositionIndexRules({ + filenamePath, + rules, + regexParameters, + }); + const bodyWithoutImports = getBodyWithoutImports(node); + const newPositionIndex = getPositionIndex({ + bodyWithoutImports, + name, + positionIndex, + positionIndexRules, + }); + + validatePositionIndex({ + node, + positionIndex: newPositionIndex, + selectorType, + context, + bodyWithoutImports, + }); +}; 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 new file mode 100644 index 0000000..2893e61 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types.ts @@ -0,0 +1,4 @@ +export interface PositionIndexRule { + positionIndex: number; + format: string[]; +} diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getBodyWithoutImports.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getBodyWithoutImports.ts new file mode 100644 index 0000000..e72d503 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getBodyWithoutImports.ts @@ -0,0 +1,14 @@ +import { TSESTree } from "@typescript-eslint/utils"; + +import { Node } from "rules/fileComposition/fileComposition.types"; +import { getProgramFromNode } from "rules/fileComposition/helpers/validateFile/helpers/getProgramFromNode"; + +export const getBodyWithoutImports = ( + node: Node, +): TSESTree.ProgramStatement[] => { + const program = getProgramFromNode(node); + + return program.body.filter( + ({ type }) => type !== TSESTree.AST_NODE_TYPES.ImportDeclaration, + ); +}; 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 new file mode 100644 index 0000000..bd90b62 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.test.ts @@ -0,0 +1,60 @@ +import { PositionIndexRule } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.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"; + +jest.mock( + "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody", + () => ({ + getSelectorNamesFromBody: jest.fn(), + }), +); + +describe("getPositionIndex", () => { + test.each<{ + name: string; + positionIndexRules: PositionIndexRule[]; + expected: number; + }>([ + { + name: "Return", + positionIndexRules: [ + { format: ["Props"], positionIndex: 0 }, + { format: ["Return"], positionIndex: 1 }, + { format: ["Name"], positionIndex: 2 }, + ], + expected: 0, + }, + { + name: "Name", + positionIndexRules: [ + { format: ["variable"], positionIndex: 0 }, + { format: ["Return"], positionIndex: 1 }, + { format: ["Name"], positionIndex: 2 }, + ], + expected: 2, + }, + { + name: "Name", + positionIndexRules: [], + expected: 1, + }, + ])( + "Should return correct value for = %o", + ({ name, positionIndexRules, expected }) => { + (getSelectorNamesFromBody as jest.Mock).mockReturnValue([ + "variable", + "Return", + "Name", + ]); + + expect( + getPositionIndex({ + bodyWithoutImports: [], + name, + positionIndex: 1, + positionIndexRules, + }), + ).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 new file mode 100644 index 0000000..e453f86 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts @@ -0,0 +1,40 @@ +import { TSESTree } from "@typescript-eslint/utils"; + +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"; + +interface GetPositionIndexProps { + positionIndex: number; + positionIndexRules: PositionIndexRule[]; + bodyWithoutImports: TSESTree.ProgramStatement[]; + name: string; +} + +export const getPositionIndex = ({ + positionIndexRules, + positionIndex, + bodyWithoutImports, + name, +}: GetPositionIndexProps): number => { + const selectorNamesFromBody = getSelectorNamesFromBody(bodyWithoutImports); + + const positionIndexRulesBody = selectorNamesFromBody + .map((name) => + positionIndexRules.find(({ format }) => format.includes(name)), + ) + .filter((v): v is PositionIndexRule => v !== undefined) + .sort((a, b) => a.positionIndex - b.positionIndex); + + const positionIndexRulesNewOrder = positionIndexRulesBody.map( + ({ format }, index) => ({ + format, + positionIndex: index, + }), + ); + + const newPositionIndex = positionIndexRulesNewOrder.find(({ format }) => + format.includes(name), + )?.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 new file mode 100644 index 0000000..8100a71 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.test.ts @@ -0,0 +1,15 @@ +import { getPositionIndexRules } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules"; + +describe("validateRules", () => { + test("Should return correct values", () => { + expect( + getPositionIndexRules({ + filenamePath: "", + rules: [ + { selector: "arrowFunction", positionIndex: 1, format: "Props" }, + { selector: "variable" }, + ], + }), + ).toEqual([{ format: ["Props"], 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 new file mode 100644 index 0000000..d85619e --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts @@ -0,0 +1,37 @@ +import { RegexParameters } from "types"; + +import { Rule } from "rules/fileComposition/fileComposition.types"; +import { getFilenameWithoutParts } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts"; +import { PositionIndexRule } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.types"; +import { prepareFormat } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat"; + +interface GetPositionIndexRulesProps { + rules: Rule[]; + regexParameters?: RegexParameters; + filenamePath: string; +} + +export const getPositionIndexRules = ({ + rules, + regexParameters, + filenamePath, +}: GetPositionIndexRulesProps): PositionIndexRule[] => + rules + .map(({ format, filenamePartsToRemove, positionIndex }) => { + if (positionIndex === undefined) return; + + const filenameWithoutParts = getFilenameWithoutParts({ + filenamePartsToRemove, + filenamePath, + }); + + return { + positionIndex, + format: prepareFormat({ + format, + filenameWithoutParts, + regexParameters, + }).formatWithoutReferences, + }; + }) + .filter((v): v is PositionIndexRule => v !== undefined); 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 new file mode 100644 index 0000000..1c948f0 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.test.ts @@ -0,0 +1,406 @@ +import { TSESTree } from "@typescript-eslint/utils"; + +import { getSelectorNamesFromBody } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody"; + +describe("getSelectorNamesFromBody", () => { + test("Should return correct values", () => { + expect( + getSelectorNamesFromBody([ + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "variable", + range: [6, 14], + }, + init: { + type: "Literal", + value: "", + raw: '""', + range: [17, 19], + }, + range: [6, 19], + }, + ], + kind: "const", + range: [0, 19], + }, + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "arrowFunction", + range: [26, 39], + }, + init: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [], + body: { + type: "Literal", + value: "", + raw: '""', + range: [48, 50], + }, + async: false, + expression: true, + range: [42, 50], + }, + range: [26, 50], + }, + ], + kind: "const", + range: [20, 50], + }, + { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "Function", + range: [60, 68], + }, + generator: false, + expression: false, + async: false, + params: [], + body: { + type: "BlockStatement", + body: [], + range: [70, 72], + }, + range: [51, 72], + }, + { + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "Class", + range: [79, 84], + }, + body: { + type: "ClassBody", + body: [], + range: [85, 87], + }, + superClass: null, + range: [73, 87], + }, + { + type: "TSTypeAliasDeclaration", + id: { + type: "Identifier", + name: "Type", + range: [93, 97], + }, + typeAnnotation: { + type: "TSLiteralType", + literal: { + type: "Literal", + value: "", + raw: '""', + range: [100, 102], + }, + range: [100, 102], + }, + range: [88, 102], + }, + { + type: "TSInterfaceDeclaration", + body: { + type: "TSInterfaceBody", + body: [], + range: [123, 125], + }, + id: { + type: "Identifier", + name: "Interface", + range: [113, 122], + }, + range: [103, 125], + }, + { + type: "TSEnumDeclaration", + id: { + type: "Identifier", + name: "Enum", + range: [131, 135], + }, + members: [], + range: [126, 138], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "variable", + range: [154, 162], + }, + init: { + type: "Literal", + value: "", + raw: '""', + range: [165, 167], + }, + range: [154, 167], + }, + ], + kind: "const", + range: [148, 167], + }, + specifiers: [], + source: null, + exportKind: "value", + range: [141, 167], + assertions: [], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "arrowFunction", + range: [181, 194], + }, + init: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [], + body: { + type: "Literal", + value: "", + raw: '""', + range: [203, 205], + }, + async: false, + expression: true, + range: [197, 205], + }, + range: [181, 205], + }, + ], + kind: "const", + range: [175, 205], + }, + specifiers: [], + source: null, + exportKind: "value", + range: [168, 205], + assertions: [], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "Function", + range: [222, 230], + }, + generator: false, + expression: false, + async: false, + params: [], + body: { + type: "BlockStatement", + body: [], + range: [232, 234], + }, + range: [213, 234], + }, + specifiers: [], + source: null, + exportKind: "value", + range: [206, 234], + assertions: [], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "Class", + range: [248, 253], + }, + body: { + type: "ClassBody", + body: [], + range: [254, 256], + }, + superClass: null, + range: [242, 256], + }, + specifiers: [], + source: null, + exportKind: "value", + range: [235, 256], + assertions: [], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "TSTypeAliasDeclaration", + id: { + type: "Identifier", + name: "Type", + range: [269, 273], + }, + typeAnnotation: { + type: "TSLiteralType", + literal: { + type: "Literal", + value: "", + raw: '""', + range: [276, 278], + }, + range: [276, 278], + }, + range: [264, 278], + }, + specifiers: [], + source: null, + exportKind: "type", + range: [257, 278], + assertions: [], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "TSInterfaceDeclaration", + body: { + type: "TSInterfaceBody", + body: [], + range: [306, 308], + }, + id: { + type: "Identifier", + name: "Interface", + range: [296, 305], + }, + range: [286, 308], + }, + specifiers: [], + source: null, + exportKind: "type", + range: [279, 308], + assertions: [], + }, + { + type: "ExportNamedDeclaration", + declaration: { + type: "TSEnumDeclaration", + id: { + type: "Identifier", + name: "Enum", + range: [321, 325], + }, + members: [], + range: [316, 328], + }, + specifiers: [], + source: null, + exportKind: "value", + range: [309, 328], + assertions: [], + }, + { + type: "ExportDefaultDeclaration", + declaration: { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "Function", + range: [354, 362], + }, + generator: false, + expression: false, + async: false, + params: [], + body: { + type: "BlockStatement", + body: [], + range: [364, 366], + }, + range: [345, 366], + }, + range: [330, 366], + exportKind: "value", + }, + { + type: "ExportDefaultDeclaration", + declaration: { + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "Class", + range: [388, 393], + }, + body: { + type: "ClassBody", + body: [], + range: [394, 396], + }, + superClass: null, + range: [382, 396], + }, + range: [367, 396], + exportKind: "value", + }, + { + type: "ExportDefaultDeclaration", + declaration: { + type: "TSInterfaceDeclaration", + body: { + type: "TSInterfaceBody", + body: [], + range: [432, 434], + }, + id: { + type: "Identifier", + name: "Interface", + range: [422, 431], + }, + range: [412, 434], + }, + range: [397, 434], + exportKind: "value", + }, + ] as unknown as TSESTree.ProgramStatement[]), + ).toEqual([ + "variable", + "arrowFunction", + "Function", + "Class", + "Type", + "Interface", + "Enum", + "variable", + "arrowFunction", + "Function", + "Class", + "Type", + "Interface", + "Enum", + "Function", + "Class", + "Interface", + ]); + }); +}); 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 new file mode 100644 index 0000000..abac431 --- /dev/null +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts @@ -0,0 +1,35 @@ +/* eslint-disable complexity */ +import { TSESTree } from "@typescript-eslint/utils"; + +export const getSelectorNamesFromBody = ( + body: TSESTree.ProgramStatement[], +): string[] => + body + .map((node) => { + const currentNode = + node.type === TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration || + node.type === TSESTree.AST_NODE_TYPES.ExportNamedDeclaration + ? node.declaration + : node; + + if ( + currentNode?.type === TSESTree.AST_NODE_TYPES.VariableDeclaration && + currentNode.declarations[0].id.type === + TSESTree.AST_NODE_TYPES.Identifier + ) { + return 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 + ) { + return currentNode.id?.name; + } + + return undefined; + }) + .filter((v): v is string => v !== undefined); diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getDefaultRegexParameters.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/helpers/getDefaultRegexParameters.ts similarity index 100% rename from src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getDefaultRegexParameters.ts rename to src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/helpers/getDefaultRegexParameters.ts diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.ts index 102fa35..a435bab 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.ts @@ -4,7 +4,7 @@ import { RegexParameters } from "types"; import { getRegexWithoutReferences } from "helpers/getRegexWithoutReferences/getRegexWithoutReferences"; import { Rule } from "rules/fileComposition/fileComposition.types"; -import { getDefaultRegexParameters } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getDefaultRegexParameters"; +import { getDefaultRegexParameters } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/helpers/getDefaultRegexParameters"; import { DEFAULT_FORMAT } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.consts"; interface PrepareFormatProps { diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex.test.ts deleted file mode 100644 index 17fd715..0000000 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getConvertedPositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex"; - -describe("getConvertedPositionIndex", () => { - test.each<{ - positionIndex: number; - bodyWithoutImportsLength: number; - expected: number; - }>([ - { - positionIndex: -1, - bodyWithoutImportsLength: 1, - expected: 0, - }, - { - positionIndex: 2, - bodyWithoutImportsLength: 1, - expected: 0, - }, - { - positionIndex: 1, - bodyWithoutImportsLength: 2, - expected: 1, - }, - ])( - "Should return correct values for %o", - ({ bodyWithoutImportsLength, positionIndex, expected }) => { - expect( - getConvertedPositionIndex({ - bodyWithoutImportsLength, - positionIndex, - }), - ).toEqual(expected); - }, - ); -}); diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex.ts deleted file mode 100644 index 4e242ad..0000000 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex.ts +++ /dev/null @@ -1,14 +0,0 @@ -interface GetConvertedPositionIndexProps { - positionIndex: number; - bodyWithoutImportsLength: number; -} - -export const getConvertedPositionIndex = ({ - positionIndex, - bodyWithoutImportsLength, -}: GetConvertedPositionIndexProps): number => { - if (positionIndex < 0) return 0; - if (positionIndex > bodyWithoutImportsLength - 1) - return bodyWithoutImportsLength - 1; - return positionIndex; -}; diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.test.ts index 364ab07..21a7bd9 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.test.ts @@ -1,5 +1,4 @@ import { Context, Node } from "rules/fileComposition/fileComposition.types"; -import { getConvertedPositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex"; import { getNodePosition } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getNodePosition"; import { validatePositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex"; @@ -10,35 +9,17 @@ jest.mock( }), ); -jest.mock( - "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex", - () => ({ - getConvertedPositionIndex: jest.fn(), - }), -); - describe("validatePositionIndex", () => { - test("Should return undefined if positionIndex === undefined", () => { - expect( - validatePositionIndex({ - context: {} as Context, - node: {} as Node, - selectorType: "arrowFunction", - positionIndex: undefined, - }), - ).toEqual(undefined); - }); - - test("Should return undefined if nodePosition === convertedPositionIndex", () => { + test("Should return undefined if nodePosition === positionIndex", () => { (getNodePosition as jest.Mock).mockReturnValue(1); - (getConvertedPositionIndex as jest.Mock).mockReturnValue(1); expect( validatePositionIndex({ context: {} as Context, - node: { type: "Program", body: [] } as unknown as Node, + node: {} as Node, selectorType: "arrowFunction", positionIndex: 1, + bodyWithoutImports: [], }), ).toEqual(undefined); }); diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.ts index 08b0ba8..a382ed7 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex.ts @@ -5,40 +5,28 @@ import { Node, SelectorType, } from "rules/fileComposition/fileComposition.types"; -import { getProgramFromNode } from "rules/fileComposition/helpers/validateFile/helpers/getProgramFromNode"; -import { getConvertedPositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getConvertedPositionIndex"; import { getNodePosition } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/helpers/getNodePosition"; interface ValidatePositionIndexProps { node: Node; - positionIndex?: number; + positionIndex: number; selectorType: SelectorType; context: Context; + bodyWithoutImports: TSESTree.ProgramStatement[]; } export const validatePositionIndex = ({ node, - positionIndex, selectorType, context: { report, sourceCode }, + positionIndex, + bodyWithoutImports, }: ValidatePositionIndexProps): void => { - if (positionIndex === undefined) return; - - const program = getProgramFromNode(node); - const bodyWithoutImports = program.body.filter( - ({ type }) => type !== TSESTree.AST_NODE_TYPES.ImportDeclaration, - ); - const nodePosition = getNodePosition({ bodyWithoutImports, node }); - const convertedPositionIndex = getConvertedPositionIndex({ - positionIndex, - bodyWithoutImportsLength: bodyWithoutImports.length, - }); - - if (nodePosition === convertedPositionIndex) return; + if (nodePosition === positionIndex) return; - const nodeToReplace = bodyWithoutImports[convertedPositionIndex]; + const nodeToReplace = bodyWithoutImports[positionIndex]; const currentNodePosition = bodyWithoutImports[nodePosition]; report({ @@ -47,7 +35,7 @@ export const validatePositionIndex = ({ data: { selectorType, currentPosition: nodePosition, - positionIndex: convertedPositionIndex, + positionIndex, }, fix: (fixer) => [ fixer.replaceText(nodeToReplace, sourceCode.getText(currentNodePosition)), diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.test.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.test.ts index 38b1108..d34b5e3 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.test.ts @@ -1,8 +1,16 @@ import { TSESTree } from "@typescript-eslint/utils"; import { Context } from "rules/fileComposition/fileComposition.types"; +import { handlePositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex"; import { validateRules } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules"; +jest.mock( + "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex", + () => ({ + handlePositionIndex: jest.fn(), + }), +); + describe("validateRules", () => { test("Should return undefined if !isSelectorAllowed", () => { expect( @@ -15,6 +23,7 @@ describe("validateRules", () => { nodeType: "VariableDeclarator", errorMessageId: "prohibitedSelector", scope: "file", + allRules: [], }), ).toEqual(undefined); }); @@ -34,6 +43,7 @@ describe("validateRules", () => { errorMessageId: "prohibitedSelector", expressionName: "expressionName", scope: "file", + allRules: [], }), ).toEqual(undefined); }); @@ -50,11 +60,12 @@ describe("validateRules", () => { errorMessageId: "prohibitedSelector", scope: "file", allowOnlySpecifiedSelectors: true, + allRules: [], }), ).toEqual(undefined); }); - test("Should return undefined if isValidExport", () => { + test("Should return undefined if isValid && positionIndex === undefined", () => { expect( validateRules({ rules: [{ selector: "variable" }], @@ -71,11 +82,40 @@ describe("validateRules", () => { nodeType: "VariableDeclarator", errorMessageId: "prohibitedSelector", scope: "file", + allRules: [], }), ).toEqual(undefined); }); - test("Should call report if !isValidExport", () => { + test("Should call handlePositionIndex if isValid && positionIndex !== undefined", () => { + const handlePositionIndexMock = jest.fn(); + + (handlePositionIndex as jest.Mock).mockImplementation( + handlePositionIndexMock, + ); + + validateRules({ + rules: [{ selector: "variable", positionIndex: 0 }], + filenamePath: "C:/somePath/src/features/Feature1/Feature1.tsx", + context: { report: jest.fn() } as unknown as Context, + name: "functionName", + node: { + parent: { + type: TSESTree.AST_NODE_TYPES.Program, + body: [], + }, + type: TSESTree.AST_NODE_TYPES.VariableDeclarator, + } as unknown as TSESTree.VariableDeclarator, + nodeType: "VariableDeclarator", + errorMessageId: "prohibitedSelector", + scope: "file", + allRules: [], + }); + + expect(handlePositionIndex).toHaveBeenCalled(); + }); + + test("Should call report if !isValid", () => { const reportMock = jest.fn(); validateRules({ @@ -93,6 +133,7 @@ describe("validateRules", () => { nodeType: "VariableDeclarator", errorMessageId: "prohibitedSelector", scope: "file", + allRules: [], }); expect(reportMock).toHaveBeenCalledWith({ diff --git a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.ts b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.ts index 8c51984..3ee1e1b 100644 --- a/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.ts +++ b/src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.ts @@ -8,14 +8,13 @@ import { Scope, FileRules, } from "rules/fileComposition/fileComposition.types"; -import { getFileNameWithoutExtension } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFileNameWithoutExtension"; +import { getFilenameWithoutParts } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts"; import { getFormatWithFilenameReferences } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFormatWithFilenameReferences"; +import { handlePositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex"; import { isCorrectSelector } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/isCorrectSelector"; import { isNameValid } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/isNameValid"; import { isSelectorAllowed } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/isSelectorAllowed/isSelectorAllowed"; import { prepareFormat } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat"; -import { removeFilenameParts } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/removeFilenameParts"; -import { validatePositionIndex } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/validatePositionIndex/validatePositionIndex"; import { SELECTORS } from "rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.consts"; import { ValidateFileProps } from "rules/fileComposition/helpers/validateFile/validateFile"; @@ -32,6 +31,7 @@ interface ValidateRulesProps { allowOnlySpecifiedSelectors?: FileRules["allowOnlySpecifiedSelectors"]; scope: Scope; context: Context; + allRules: Rule[]; } export const validateRules = ({ @@ -48,6 +48,7 @@ export const validateRules = ({ nodeNotExported, context, context: { report }, + allRules, }: ValidateRulesProps): void => { const selectorType = SELECTORS[nodeType]; @@ -75,14 +76,10 @@ export const validateRules = ({ ); const formatWithoutReferences = selectorTypeRules - .map((rule) => { - const { format, filenamePartsToRemove, positionIndex } = rule; - - const filenameWithoutExtension = - getFileNameWithoutExtension(filenamePath); - const filenameWithoutParts = removeFilenameParts({ - filenameWithoutExtension, + .map(({ format, filenamePartsToRemove, positionIndex }) => { + const filenameWithoutParts = getFilenameWithoutParts({ filenamePartsToRemove, + filenamePath, }); const { formatWithReferences, formatWithoutReferences } = prepareFormat({ format, @@ -94,13 +91,20 @@ export const validateRules = ({ name, }); - if (isValid) - return validatePositionIndex({ + if (isValid) { + if (positionIndex === undefined) return; + + return handlePositionIndex({ + context, + filenamePath, + name, node: nodeNotExported ?? node, - positionIndex, + rules: allRules, selectorType, - context, + positionIndex, + regexParameters, }); + } return getFormatWithFilenameReferences({ formatWithReferences, diff --git a/src/rules/fileComposition/helpers/validateFile/validateFile.test.ts b/src/rules/fileComposition/helpers/validateFile/validateFile.test.ts index f282c44..2115ff0 100644 --- a/src/rules/fileComposition/helpers/validateFile/validateFile.test.ts +++ b/src/rules/fileComposition/helpers/validateFile/validateFile.test.ts @@ -51,6 +51,34 @@ describe("validateFile", () => { ).toEqual(undefined); }); + test("Should return undefined if !rules", () => { + const reportMock = jest.fn(); + + (isExportedName as jest.Mock).mockReturnValue({ + isExportName: true, + currentName: "componentNameExport", + currentNode: {}, + }); + + expect( + validateFile({ + context: { + settings: {}, + cwd: "C:/somePath", + filename: "C:/somePath/src/features/Feature1/Feature1.ts", + report: reportMock, + } as unknown as Context, + name: "componentName", + node: {} as TSESTree.VariableDeclarator, + nodeType: "VariableDeclarator", + config: { + filesRules: [{ filePattern: "**/*.ts" }], + }, + fileConfig: { filePattern: "**/*.ts" }, + }), + ).toEqual(undefined); + }); + test("Should call fileExportRules for fileExportRules", () => { const validateRulesMock = jest.fn(); const reportMock = jest.fn(); @@ -76,13 +104,19 @@ describe("validateFile", () => { filesRules: [ { filePattern: "**/*.ts", - rules: [{ scope: "fileExport", selector: "variable" }], + rules: [ + { scope: "fileExport", selector: "variable" }, + { selector: "arrowFunction" }, + ], }, ], }, fileConfig: { filePattern: "**/*.ts", - rules: [{ scope: "fileExport", selector: "variable" }], + rules: [ + { scope: "fileExport", selector: "variable" }, + { selector: "arrowFunction" }, + ], }, }); @@ -107,6 +141,10 @@ describe("validateFile", () => { expressionName: undefined, allowOnlySpecifiedSelectors: undefined, regexParameters: undefined, + allRules: [ + { scope: "fileExport", selector: "variable" }, + { selector: "arrowFunction" }, + ], }); }); @@ -136,13 +174,19 @@ describe("validateFile", () => { filesRules: [ { filePattern: "**/*.ts", - rules: [{ scope: "fileRoot", selector: "variable" }], + rules: [ + { scope: "fileRoot", selector: "variable" }, + { selector: "arrowFunction" }, + ], }, ], }, fileConfig: { filePattern: "**/*.ts", - rules: [{ scope: "fileRoot", selector: "variable" }], + rules: [ + { scope: "fileRoot", selector: "variable" }, + { selector: "arrowFunction" }, + ], }, }); @@ -166,6 +210,10 @@ describe("validateFile", () => { expressionName: undefined, allowOnlySpecifiedSelectors: undefined, regexParameters: undefined, + allRules: [ + { scope: "fileRoot", selector: "variable" }, + { selector: "arrowFunction" }, + ], }); }); @@ -197,6 +245,7 @@ describe("validateFile", () => { filePattern: "**/*.ts", rules: [ { scope: "file", selector: "variable" }, + { scope: "fileRoot", selector: "variable" }, { selector: "variable" }, ], }, @@ -206,6 +255,7 @@ describe("validateFile", () => { filePattern: "**/*.ts", rules: [ { scope: "file", selector: "variable" }, + { scope: "fileRoot", selector: "variable" }, { selector: "variable" }, ], }, @@ -234,6 +284,11 @@ describe("validateFile", () => { expressionName: undefined, allowOnlySpecifiedSelectors: undefined, regexParameters: undefined, + allRules: [ + { scope: "file", selector: "variable" }, + { scope: "fileRoot", selector: "variable" }, + { selector: "variable" }, + ], }); }); }); diff --git a/src/rules/fileComposition/helpers/validateFile/validateFile.ts b/src/rules/fileComposition/helpers/validateFile/validateFile.ts index 36ad286..d2c7185 100644 --- a/src/rules/fileComposition/helpers/validateFile/validateFile.ts +++ b/src/rules/fileComposition/helpers/validateFile/validateFile.ts @@ -33,10 +33,10 @@ export const validateFile = ({ }: ValidateFileProps): void => { if (!fileConfig) return; - const { rules, allowOnlySpecifiedSelectors } = fileConfig; - const fileExportRules = rules?.filter(({ scope }) => scope === "fileExport"); - const fileRootRules = rules?.filter(({ scope }) => scope === "fileRoot"); - const fileRules = rules?.filter(({ scope }) => scope === "file" || !scope); + const { rules = [], allowOnlySpecifiedSelectors } = fileConfig; + const fileExportRules = rules.filter(({ scope }) => scope === "fileExport"); + const fileRootRules = rules.filter(({ scope }) => scope === "fileRoot"); + const fileRules = rules.filter(({ scope }) => scope === "file" || !scope); const filenamePath = path.relative(cwd, filename); const regexParameters = config.regexParameters; @@ -46,7 +46,7 @@ export const validateFile = ({ name, }); - if (fileExportRules?.length && isExportName) { + if (fileExportRules.length && isExportName) { return validateRules({ rules: fileExportRules, name: currentName, @@ -60,6 +60,7 @@ export const validateFile = ({ expressionName, allowOnlySpecifiedSelectors, scope: "fileExport", + allRules: rules, }); } @@ -68,7 +69,7 @@ export const validateFile = ({ node, }); - if (fileRootRules?.length && isFileRootName) { + if (fileRootRules.length && isFileRootName) { return validateRules({ rules: fileRootRules, name, @@ -81,10 +82,11 @@ export const validateFile = ({ expressionName, allowOnlySpecifiedSelectors, scope: "fileRoot", + allRules: rules, }); } - if (fileRules?.length) { + if (fileRules.length) { return validateRules({ rules: fileRules, name, @@ -97,6 +99,7 @@ export const validateFile = ({ allowOnlySpecifiedSelectors, scope: "file", context, + allRules: rules, }); } }; diff --git a/src/rules/fileComposition/helpers/validateRootSelectorsLimits/helpers/getSelectorsCount.ts b/src/rules/fileComposition/helpers/validateRootSelectorsLimits/helpers/getSelectorsCount.ts index 4f9068d..ce728d2 100644 --- a/src/rules/fileComposition/helpers/validateRootSelectorsLimits/helpers/getSelectorsCount.ts +++ b/src/rules/fileComposition/helpers/validateRootSelectorsLimits/helpers/getSelectorsCount.ts @@ -4,6 +4,7 @@ import { TSESTree } from "@typescript-eslint/utils"; import { SelectorType } from "rules/fileComposition/fileComposition.types"; import { getIdentifierFromExpression } from "rules/fileComposition/helpers/getIdentifierFromExpression"; +// eslint-disable-next-line project-structure/file-composition type GetSelectorsCountReturn = Partial>; export const getSelectorsCount = (