UNPKG

eslint-plugin-project-structure

Version:

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 pr

1,561 lines (1,488 loc) 118 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createFileComposition: () => createFileComposition, createFolderStructure: () => createFolderStructure, createIndependentModules: () => createIndependentModules, projectStructureParser: () => parser, projectStructurePlugin: () => projectStructurePlugin }); module.exports = __toCommonJS(index_exports); // src/parser.ts var parser = { meta: { name: "projectStructureParser" }, parseForESLint: () => ({ ast: { type: "Program", start: 0, end: 0, loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }, tokens: [], comments: [], range: [0, 0], sourceType: "module", body: [] }, scopeManager: null, visitorKeys: null }) }; var parser_default = parser; module.exports = parser; // src/rules/fileComposition/fileComposition.ts var import_utils13 = require("@typescript-eslint/utils"); // src/rules/fileComposition/fileComposition.consts.ts var ESLINT_ERRORS = { invalidName: `\u{1F525} Invalid '{{selectorType}}' name, allowed formats = {{formatWithoutReferences}} \u{1F525}`, invalidPosition: `\u{1F525} Invalid '{{selectorType}}' position. It is located in line {{currentLine}} but should be in line {{correctLine}}. \u{1F525}`, prohibitedSelectorRoot: `\u{1F525} The use of '{{selectorType}}' is prohibited in the root of the file. \u{1F525}{{error}}`, prohibitedSelectorNested: `\u{1F525} The use of nested '{{selectorType}}' is prohibited in this file. \u{1F525}{{error}}`, prohibitedSelectorExport: `\u{1F525} Exporting '{{selectorType}}' is prohibited in this file. \u{1F525}{{error}}`, rootSelectorsLimits: "\u{1F525} The limit for the given selectors in the root of the file has been exceeded. \u{1F525}\n{{error}}\n\n" }; // src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts var import_path3 = __toESM(require("path")); // src/helpers/getProjectRoot.ts var import_path = __toESM(require("path")); var getProjectRoot = ({ cwd, projectRootConfig }) => { const dirnameSplit = (0, import_path.dirname)(__filename).split(import_path.sep); const indexOfNodeModules = dirnameSplit.indexOf("node_modules"); if (indexOfNodeModules === -1) return cwd; const rootPath = dirnameSplit.slice(0, indexOfNodeModules).join(import_path.sep); if (!projectRootConfig) return rootPath; return import_path.default.resolve(rootPath, projectRootConfig); }; // src/helpers/isCorrectPattern.ts var import_micromatch = __toESM(require("micromatch")); var isCorrectPattern = ({ input, pattern }) => { if (typeof pattern === "string") return import_micromatch.default.every(input, pattern); return pattern.some((p) => import_micromatch.default.every(input, p)); }; // src/helpers/readConfigFile/readConfigFile.ts var import_fs = require("fs"); var import_comment_json = require("comment-json"); var import_js_yaml = require("js-yaml"); // src/errors/getInvalidConfigFileError.ts var getInvalidConfigFileError = (configPath) => new Error( `\u{1F525} '${configPath}' file cannot be read or has an incorrect extension. \u{1F525}` ); // src/helpers/readConfigFile/helpers/getConfigPath.ts var import_path2 = __toESM(require("path")); // src/errors/getMissingConfigFileError.ts var getMissingConfigFileError = (key) => new Error(`\u{1F525} Missing configuration file '${key}'. \u{1F525}`); // src/helpers/readConfigFile/helpers/getConfigPath.ts var getConfigPath = ({ settings, key, cwd }) => { const configPath = settings[key]; if (!configPath) throw getMissingConfigFileError(key); return import_path2.default.resolve(getProjectRoot({ cwd }), configPath); }; // src/helpers/readConfigFile/readConfigFile.ts var readConfigFile = ({ key, settings, options, cwd }) => { if (options) return options; const configPath = getConfigPath({ key, settings, cwd }); let config = void 0; if (configPath.endsWith("json") || configPath.endsWith("jsonc")) { config = (0, import_comment_json.parse)((0, import_fs.readFileSync)(configPath, "utf-8")); } else { config = (0, import_js_yaml.load)((0, import_fs.readFileSync)(configPath, "utf8")); } if (!config) throw getInvalidConfigFileError(configPath); return config; }; // src/helpers/validateConfig.ts var import_jsonschema = require("jsonschema"); // src/errors/getInvalidConfigError.ts var getInvalidConfigError = (errors) => new Error( errors.reduce( (acc, stack) => acc + `\u{1F525} ${stack.replace("instance", "configuration")}. `, "\u{1F525} Invalid configuration file:\n" ) ); // src/helpers/validateConfig.ts var validateConfig = ({ config, schema }) => { const errors = (0, import_jsonschema.validate)(config, schema, { nestedErrors: true }).errors.filter(({ stack }) => !stack.includes("$schema")); if (!errors.length) return; throw getInvalidConfigError(errors.map(({ stack }) => stack)); }; // src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.consts.ts var FILE_COMPOSITION_SCHEMA = { $schema: "http://json-schema.org/draft-07/schema#", definitions: { SelectorType: { type: "string", default: "", enum: [ "class", "variable", "variableExpression", "propertyDefinition", "arrowFunction", "function", "type", "interface", "enum" ] }, Selector: { oneOf: [ { $ref: "#/definitions/SelectorType" }, { type: "object", default: { type: "variableExpression", limitTo: "" }, additionalProperties: false, properties: { type: { type: "string", default: "variableExpression", enum: ["variableExpression"] }, limitTo: { oneOf: [ { type: "string", default: "" }, { type: "array", default: [], items: { type: "string" } } ] } }, required: ["type", "limitTo"] } ] }, RootSelectorsLimits: { type: "array", default: [], items: { type: "object", additionalProperties: false, properties: { selector: { oneOf: [ { $ref: "#/definitions/SelectorType" }, { type: "array", default: [], items: { $ref: "#/definitions/SelectorType" } } ] }, limit: { oneOf: [ { type: "number" }, { type: "object", additionalProperties: false, properties: { min: { type: "number" }, max: { type: "number" } } } ] } }, required: ["limit", "selector"] } }, FileRule: { type: "object", default: {}, additionalProperties: false, properties: { selector: { oneOf: [ { $ref: "#/definitions/Selector" }, { type: "array", default: [], items: { $ref: "#/definitions/Selector" } } ] }, scope: { oneOf: [ { type: "string", default: "file", enum: ["file", "fileExport", "fileRoot", "nestedSelectors"] }, { type: "array", default: [], items: { type: "string", default: "file", enum: ["file", "fileExport", "fileRoot"] } } ] }, positionIndex: { oneOf: [ { type: "number", default: 0 }, { type: "object", default: { index: 0, sorting: "az" }, properties: { index: { type: "number", default: 0 }, sorting: { type: "string", default: "az", enum: ["az", "none"] } }, required: ["index"] } ] }, filenamePartsToRemove: { oneOf: [ { type: "string", default: "" }, { type: "array", default: [], items: { type: "string", default: "" } } ] }, format: { oneOf: [ { type: "string", default: "" }, { type: "array", default: [], items: { type: "string" } } ] } }, required: ["selector"] }, CustomErrors: { type: "object", default: {}, additionalProperties: false, properties: { class: { type: "string", default: "" }, variable: { type: "string", default: "" }, variableExpression: { type: "string", default: "" }, propertyDefinition: { type: "string", default: "" }, function: { type: "string", default: "" }, arrowFunction: { type: "string", default: "" }, type: { type: "string", default: "" }, interface: { type: "string", default: "" }, enum: { type: "string", default: "" } } }, FileRules: { type: "object", default: {}, additionalProperties: false, properties: { filePattern: { oneOf: [ { type: "string", default: "" }, { type: "array", default: [], items: { oneOf: [ { type: "string", default: "" }, { type: "array", default: [], items: { type: "string" } } ] } } ] }, allowOnlySpecifiedSelectors: { oneOf: [ { type: "boolean", default: true }, { type: "object", additionalProperties: false, default: { errors: {}, fileRoot: true, fileExport: true, file: true }, properties: { error: { $ref: "#/definitions/CustomErrors" }, fileRoot: { oneOf: [ { type: "boolean", default: true }, { $ref: "#/definitions/CustomErrors" } ] }, fileExport: { oneOf: [ { type: "boolean", default: true }, { $ref: "#/definitions/CustomErrors" } ] }, nestedSelectors: { oneOf: [ { type: "boolean", default: true }, { $ref: "#/definitions/CustomErrors" } ] } } } ] }, rootSelectorsLimits: { $ref: "#/definitions/RootSelectorsLimits" }, rules: { type: "array", default: [], items: { $ref: "#/definitions/FileRule" } } }, required: ["filePattern"] }, RegexParameters: { type: "object", default: {}, additionalProperties: { type: "string", default: "" } } }, type: "object", additionalProperties: false, properties: { projectRoot: { type: "string", default: "." }, filesRules: { type: "array", default: [], items: { $ref: "#/definitions/FileRules" } }, regexParameters: { $ref: "#/definitions/RegexParameters" } }, required: ["filesRules"] }; // src/rules/fileComposition/helpers/getFileCompositionConfig/getFileCompositionConfig.ts var getFileCompositionConfig = ({ filename, settings, options, cwd }) => { const config = readConfigFile({ key: "project-structure/file-composition-config-path", settings, options: options[0], cwd }); validateConfig({ config, schema: FILE_COMPOSITION_SCHEMA }); const filenamePath = import_path3.default.relative( getProjectRoot({ cwd, projectRootConfig: config.projectRoot }), filename ); const fileConfig = config.filesRules.find( ({ filePattern }) => isCorrectPattern({ input: filenamePath, pattern: filePattern }) ); return { config, fileConfig }; }; // src/rules/fileComposition/helpers/validateFile/validateFile.ts var import_path5 = __toESM(require("path")); // src/rules/fileComposition/helpers/validateFile/helpers/getCurrentScopeData.ts var getCurrentScopeData = ({ isFileExport, isFileRoot }) => { if (isFileExport) return { scope: "fileExport", errorMessageId: "prohibitedSelectorExport" }; if (isFileRoot) return { scope: "fileRoot", errorMessageId: "prohibitedSelectorRoot" }; return { scope: "nestedSelectors", errorMessageId: "prohibitedSelectorNested" }; }; // src/rules/fileComposition/helpers/validateFile/helpers/isCorrectScope.ts var isCorrectScope = ({ expect, scope }) => { if (scope === "file" || !scope) return true; if (typeof scope === "string") return scope === expect; return scope.some((s) => s === expect || s === "file"); }; // src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/isExportedName.ts var import_utils4 = require("@typescript-eslint/utils"); // src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/helpers/isExportDefault.ts var import_utils2 = require("@typescript-eslint/utils"); // src/rules/fileComposition/helpers/validateFile/helpers/getProgramFromNode.ts var import_utils = require("@typescript-eslint/utils"); var getProgramFromNode = (node) => { if (node.type !== import_utils.TSESTree.AST_NODE_TYPES.Program) return getProgramFromNode(node.parent); return node; }; // src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/helpers/isExportDefault.ts var isExportDefault = ({ name, node }) => { let currentNode = node; let currentName = name; let isExportDefault2 = false; getProgramFromNode(node).body.forEach((node2) => { if (node2.type !== import_utils2.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration) return; if (node2.declaration.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && node2.declaration.name === name) { isExportDefault2 = true; currentNode = node2.declaration; return; } if (node2.declaration.type === import_utils2.TSESTree.AST_NODE_TYPES.ObjectExpression) { node2.declaration.properties.forEach((property) => { if (property.type !== import_utils2.TSESTree.AST_NODE_TYPES.Property) return; if (property.key.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && property.value.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && property.value.name === name) { isExportDefault2 = true; currentNode = property.key; currentName = property.key.name; return; } }); } if (node2.declaration.type === import_utils2.TSESTree.AST_NODE_TYPES.ArrayExpression) { node2.declaration.elements.forEach((element) => { if (!element) return; if (element.type === import_utils2.TSESTree.AST_NODE_TYPES.Identifier && element.name === name) { isExportDefault2 = true; currentNode = element; return; } }); } }); return { isExportDefault: isExportDefault2, currentNode, currentName }; }; // src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/helpers/isNamedExport.ts var import_utils3 = require("@typescript-eslint/utils"); var isNamedExport = ({ name, node }) => { let isNamedExport2 = false; let currentNode = node; let currentName = name; getProgramFromNode(node).body.forEach((node2) => { if (node2.type !== import_utils3.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration) return; node2.specifiers.forEach((specifier) => { if (specifier.local.type !== import_utils3.TSESTree.AST_NODE_TYPES.Identifier || specifier.exported.type !== import_utils3.TSESTree.AST_NODE_TYPES.Identifier) return; if (specifier.local.name === name && specifier.exported.name !== name) { isNamedExport2 = true; currentName = specifier.exported.name; currentNode = specifier.exported; return; } if (specifier.local.name === name && specifier.exported.name === name) { isNamedExport2 = true; currentNode = specifier.local; return; } }); }); return { isNamedExport: isNamedExport2, currentName, currentNode }; }; // src/rules/fileComposition/helpers/validateFile/helpers/isExportedName/isExportedName.ts var isExportedName = ({ nodeType, node, name }) => { if ((nodeType === "ArrowFunctionExpression" || nodeType === "Expression" || nodeType === "VariableDeclarator") && node.parent.parent?.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.parent?.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration) return { isExportName: true, currentNode: node, currentName: name }; if (node.parent.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.type === import_utils4.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration) return { isExportName: true, currentNode: node, currentName: name }; const namedExport = isNamedExport({ name, node }); const exportDefault = isExportDefault({ name, node }); if (namedExport.isNamedExport) return { isExportName: true, currentNode: namedExport.currentNode, currentName: namedExport.currentName }; if (exportDefault.isExportDefault) return { isExportName: true, currentNode: exportDefault.currentNode, currentName: exportDefault.currentName }; return { isExportName: false, currentName: name, currentNode: node }; }; // src/rules/fileComposition/helpers/validateFile/helpers/isNameFromFileRoot.ts var import_utils5 = require("@typescript-eslint/utils"); var isNameFromFileRoot = ({ nodeType, node }) => { if (nodeType === "ArrowFunctionExpression" || nodeType === "VariableDeclarator" || nodeType === "Expression") return node.parent.parent?.type === import_utils5.TSESTree.AST_NODE_TYPES.Program || node.parent.parent?.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.parent?.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration || node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.Program; return node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.Program || node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration || node.parent.type === import_utils5.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration; }; // src/consts.ts var SNAKE_CASE_LOWER_RE = /((([a-z]|\d)+_)*([a-z]|\d)+)/; var SNAKE_CASE_UPPER_RE = /((([A-Z]|\d)+_)*([A-Z]|\d)+)/; var KEBAB_CASE_RE = /((([a-z]|\d)+-)*([a-z]|\d)+)/; var CAMEL_CASE_RE = /([a-z]+[A-Z0-9]*[A-Z0-9]*)*/; var PASCAL_CASE_RE = /([A-Z]+[a-z0-9]*[A-Z0-9]*)*/; var STRICT_CAMEL_CASE_RE = /[a-z][a-z0-9]*(([A-Z][a-z0-9]+)*[A-Z]?|([a-z0-9]+[A-Z])*|[A-Z])/; var STRICT_PASCAL_CASE_RE = /[A-Z](([a-z0-9]+[A-Z]?)*)/; var SNAKE_CASE_LOWER = `${SNAKE_CASE_LOWER_RE}`.replace(/\//g, ""); var SNAKE_CASE_UPPER = `${SNAKE_CASE_UPPER_RE}`.replace(/\//g, ""); var KEBAB_CASE = `${KEBAB_CASE_RE}`.replace(/\//g, ""); var CAMEL_CASE = `${CAMEL_CASE_RE}`.replace(/\//g, ""); var PASCAL_CASE = `${PASCAL_CASE_RE}`.replace(/\//g, ""); var STRICT_CAMEL_CASE = `${STRICT_CAMEL_CASE_RE}`.replace(/\//g, ""); var STRICT_PASCAL_CASE = `${STRICT_PASCAL_CASE_RE}`.replace(/\//g, ""); var RECURSION_LIMIT = 1e3; var WILDCARD_REGEX = "(([^/]*)+)"; var ESLINT_ERRORS2 = { error: `{{error}}` }; var PROJECT_STRUCTURE_CACHE_FILE_NAME = "projectStructure.cache.json"; // src/errors/getInvalidRegexError.ts var getInvalidRegexError = (regex) => new Error(`\u{1F525} Regex: ${regex} is invalid. \u{1F525}`); // src/helpers/isRegexInvalid.ts var isRegexInvalid = (regex) => { try { new RegExp(`^${regex}$`, "g"); } catch (_e) { return true; } return false; }; // src/rules/fileComposition/helpers/validateFile/helpers/isCorrectSelector.ts var isCorrectSelector = ({ selector, selectorType, expressionName }) => { if (typeof selector === "string") return selector === selectorType; if (!Array.isArray(selector)) { if (!expressionName) return false; if (typeof selector.limitTo === "string") { const regexImproved = selector.limitTo.replaceAll("*", WILDCARD_REGEX).replaceAll(`${WILDCARD_REGEX}${WILDCARD_REGEX}`, "*"); if (isRegexInvalid(regexImproved)) throw getInvalidRegexError(regexImproved); const finalRegex = new RegExp(`^${regexImproved}$`, "g"); return selector.type === selectorType && finalRegex.test(expressionName); } return selector.limitTo.some( (limitTo) => isCorrectSelector({ selector: { type: "variableExpression", limitTo }, selectorType, expressionName }) ); } return selector.some( (sel) => isCorrectSelector({ selector: sel, selectorType, expressionName }) ); }; // src/rules/fileComposition/helpers/validateFile/helpers/isSelectorAllowed/helpers/getCustomError.ts var getCustomError = ({ selectorType, allowOnlySpecifiedSelectors, scope }) => { if (allowOnlySpecifiedSelectors === true) return ""; if (typeof allowOnlySpecifiedSelectors[scope] === "object") { const scopeErrors = { ...allowOnlySpecifiedSelectors.error, ...allowOnlySpecifiedSelectors[scope] }; if (!scopeErrors[selectorType]) return ""; return ` ${scopeErrors[selectorType]} `; } if (allowOnlySpecifiedSelectors.error?.[selectorType]) return ` ${allowOnlySpecifiedSelectors.error[selectorType]} `; return ""; }; // src/rules/fileComposition/helpers/validateFile/helpers/isSelectorAllowed/isSelectorAllowed.ts var isSelectorAllowed = ({ rules, report, node, errorMessageId, selectorType, expressionName, scope, allowOnlySpecifiedSelectors }) => { const isAllowed = rules.map(({ selector }) => selector).flat().some( (selector) => isCorrectSelector({ selector, selectorType, expressionName }) ); if (isAllowed || !allowOnlySpecifiedSelectors || typeof allowOnlySpecifiedSelectors === "object" && allowOnlySpecifiedSelectors[scope] === false) return true; report({ messageId: errorMessageId, data: { selectorType, error: getCustomError({ selectorType, scope, allowOnlySpecifiedSelectors }) }, node }); return false; }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/getFileNameWithoutExtension.ts var import_path4 = __toESM(require("path")); var getFileNameWithoutExtension = (filenamePath) => { const fileNameWithExtension = import_path4.default.basename(filenamePath); return fileNameWithExtension.substring( 0, fileNameWithExtension.lastIndexOf(".") ); }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/helpers/removeFilenameParts.ts var removeFilenameParts = ({ filenameWithoutExtension, filenamePartsToRemove }) => { if (!filenamePartsToRemove) return filenameWithoutExtension; const currentFilenamePartsToRemove = typeof filenamePartsToRemove === "string" ? [filenamePartsToRemove] : filenamePartsToRemove; return currentFilenamePartsToRemove.reduce( (acc, removePart) => acc.replaceAll(removePart, ""), filenameWithoutExtension ); }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFilenameWithoutParts/getFilenameWithoutParts.ts var getFilenameWithoutParts = ({ filenamePartsToRemove, filenamePath }) => { const filenameWithoutExtension = getFileNameWithoutExtension(filenamePath); return removeFilenameParts({ filenameWithoutExtension, filenamePartsToRemove }); }; // src/helpers/getUpperCaseFirstLetter.ts var getUpperCaseFirstLetter = (text) => text.charAt(0).toUpperCase() + text.slice(1); // src/helpers/transformStringToCase.ts var transformStringToCase = ({ str, transformTo }) => { const toCamelCase = (input) => { if (input === input.toUpperCase()) { return input.toLowerCase().replace(/(_[a-z])/g, (_, char) => char.toUpperCase()).replace(/^[A-Z]/, (char) => char.toLowerCase()).replace(/_/g, ""); } return input.replace( /([-_][a-z])/gi, (match) => match.toUpperCase().replace(/[-_]/g, "") ).replace(/^([A-Z])/, (char) => char.toLowerCase()).replace(/_/g, ""); }; const toSnakeCaseLower = (input) => input.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Za-z])(\d)/g, "$1_$2").replace(/(\d)([A-Za-z])/g, "$1_$2").replace(/[-\s]/g, "_").replace(/_+/g, "_").toLowerCase(); const toKebabCase = (input) => input.replace( /([a-z\d])([A-Z]+)/g, (_match, p1, p2) => p1 + "-" + p2.toLowerCase() ).replace( /([A-Z]+)([A-Z][a-z])/g, (_match, p1, p2) => `${p1.toLowerCase()}-${p2}` ).replace( /(\d)([a-zA-Z])/g, (_match, p1, p2) => `${p1}-${p2.toLowerCase()}` ).replace( /([a-zA-Z])(\d)/g, (_match, p1, p2) => `${p1}-${p2}` ).replace(/[_\s]+/g, "-").toLowerCase(); let result; switch (transformTo) { case "PascalCase": result = getUpperCaseFirstLetter(toCamelCase(str)); break; case "kebab-case": result = toKebabCase(str); break; case "snake_case": result = toSnakeCaseLower(str); break; case "SNAKE_CASE": result = toSnakeCaseLower(str).toUpperCase(); break; default: result = toCamelCase(str); break; } return result; }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.consts.ts var REFERENCES = { FileName: "{FileName}", fileName: "{fileName}", file_name: "{file_name}", FILE_NAME: "{FILE_NAME}", camelCase: "{camelCase}", PascalCase: "{PascalCase}", strictCamelCase: "{strictCamelCase}", StrictPascalCase: "{StrictPascalCase}", snake_case: "{snake_case}", SNAKE_CASE: "{SNAKE_CASE}" }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/getFormatWithFilenameReferences.ts var getFormatWithFilenameReferences = ({ filename, formatWithReferences }) => formatWithReferences.map( (pattern) => pattern.replaceAll( REFERENCES.fileName, transformStringToCase({ str: filename, transformTo: "camelCase" }) ).replaceAll( REFERENCES.FileName, transformStringToCase({ str: filename, transformTo: "PascalCase" }) ).replaceAll( REFERENCES.file_name, transformStringToCase({ str: filename, transformTo: "snake_case" }) ).replaceAll( REFERENCES.FILE_NAME, transformStringToCase({ str: filename, transformTo: "SNAKE_CASE" }) ) ); // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getBodyWithoutImports.ts var import_utils6 = require("@typescript-eslint/utils"); var getBodyWithoutImports = (node) => { const program = getProgramFromNode(node); return program.body.filter( ({ type }) => type !== import_utils6.TSESTree.AST_NODE_TYPES.ImportDeclaration ); }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts var import_utils8 = require("@typescript-eslint/utils"); // src/rules/fileComposition/helpers/getIdentifierFromExpression.ts var import_utils7 = require("@typescript-eslint/utils"); var getIdentifierFromExpression = (expression) => { if (!expression) return; if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression && expression.callee.type === import_utils7.TSESTree.AST_NODE_TYPES.Identifier) return expression.callee.name; if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression && expression.object.type === import_utils7.TSESTree.AST_NODE_TYPES.Identifier) return expression.object.name; if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TaggedTemplateExpression && expression.tag.type === import_utils7.TSESTree.AST_NODE_TYPES.Identifier) return expression.tag.name; if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TaggedTemplateExpression && (expression.tag.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression || expression.tag.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression)) return getIdentifierFromExpression(expression.tag); if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TSAsExpression && (expression.expression.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression || expression.expression.type === import_utils7.TSESTree.AST_NODE_TYPES.TaggedTemplateExpression)) return getIdentifierFromExpression(expression.expression); if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression && (expression.callee.type === import_utils7.TSESTree.AST_NODE_TYPES.CallExpression || expression.callee.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression)) return getIdentifierFromExpression(expression.callee); if (expression.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression && expression.object.type === import_utils7.TSESTree.AST_NODE_TYPES.MemberExpression) return getIdentifierFromExpression(expression.object); return; }; // src/rules/fileComposition/helpers/validateFile/validateFile.consts.ts var SELECTORS = { VariableDeclarator: "variable", Expression: "variableExpression", ClassDeclaration: "class", FunctionDeclaration: "function", ArrowFunctionExpression: "arrowFunction", TSEnumDeclaration: "enum", TSInterfaceDeclaration: "interface", TSTypeAliasDeclaration: "type", PropertyDefinition: "propertyDefinition" }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getSelectorNamesFromBody.ts var getSelectorNamesFromBody = (body) => body.map((node) => { const currentNode = node.type === import_utils8.TSESTree.AST_NODE_TYPES.ExportDefaultDeclaration || node.type === import_utils8.TSESTree.AST_NODE_TYPES.ExportNamedDeclaration ? node.declaration : node; if (currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.VariableDeclaration && currentNode.declarations[0].id.type === import_utils8.TSESTree.AST_NODE_TYPES.Identifier) { const expressionName = getIdentifierFromExpression( currentNode.declarations[0].init ); if (expressionName) { return { selector: "variableExpression", name: currentNode.declarations[0].id.name, expressionName, range: JSON.stringify(currentNode.declarations[0].range) }; } return { selector: currentNode.declarations[0].init?.type === import_utils8.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ? "arrowFunction" : "variable", name: currentNode.declarations[0].id.name, range: JSON.stringify(currentNode.declarations[0].range) }; } if ((currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.FunctionDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.ClassDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.TSInterfaceDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.TSTypeAliasDeclaration || currentNode?.type === import_utils8.TSESTree.AST_NODE_TYPES.TSEnumDeclaration) && currentNode.id?.name) { const selector = SELECTORS[currentNode.type]; return { selector, name: currentNode.id.name, range: JSON.stringify(currentNode.range) }; } return void 0; }).filter((v) => v !== void 0); // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/isNameValid.ts var isNameValid = ({ formatWithoutReferences, name }) => formatWithoutReferences.some((pattern) => { if (isRegexInvalid(pattern)) throw getInvalidRegexError(pattern); const regexp = new RegExp(`^${pattern}$`, "g"); return regexp.test(name); }); // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndex.ts var getPositionIndex = ({ positionIndexRules, bodyWithoutImports, nodeRange, positionIndex }) => { const selectorNamesFromBody = getSelectorNamesFromBody(bodyWithoutImports); const positionIndexRulesBody = selectorNamesFromBody.sort((a, b) => { if (typeof positionIndex === "object" && positionIndex.sorting === "none") return 0; return a.name.localeCompare(b.name, void 0, { numeric: true, sensitivity: "base" }); }).map((body) => { const rule = positionIndexRules.find( ({ format, selector }) => isCorrectSelector({ selector, selectorType: body.selector, expressionName: body.expressionName }) && isNameValid({ formatWithoutReferences: format, name: body.name }) ); if (!rule) return; return { ...rule, range: body.range, expressionName: body.expressionName }; }).filter((v) => v !== void 0).sort((a, b) => { if (a.positionIndex < 0 && b.positionIndex >= 0) return 1; if (a.positionIndex >= 0 && b.positionIndex < 0) return -1; return a.positionIndex - b.positionIndex; }); const positionIndexRulesNewOrderPositive = positionIndexRulesBody.filter(({ positionIndex: positionIndex2 }) => positionIndex2 >= 0).map((rule, index) => ({ ...rule, positionIndex: index })); const positionIndexRulesNewOrderNegative = positionIndexRulesBody.filter(({ positionIndex: positionIndex2 }) => positionIndex2 < 0).reverse().map((rule, index) => ({ ...rule, positionIndex: selectorNamesFromBody.length - 1 - index })); const positionIndexRulesNewOrder = [ ...positionIndexRulesNewOrderPositive, ...positionIndexRulesNewOrderNegative ]; const newPositionIndex = positionIndexRulesNewOrder.find( ({ range }) => range === nodeRange )?.positionIndex; return newPositionIndex ?? 0; }; // src/errors/getInvalidReferenceError.ts var getInvalidReferenceError = ({ invalidReferences, allowedReferences, key }) => new Error( `\u{1F525} Reference ${invalidReferences.join(", ")} in '${key}' do not exist. \u{1F525} Allowed references = ${allowedReferences.join(", ")}. ` ); // src/helpers/getRegexWithoutReferences/helpers/validateReferences/helpers/extractReferencesFromRegex.ts var extractReferencesFromRegex = ({ filterReferences, regex }) => regex.match(/\{([^}]+)\}/g)?.map((match) => match.slice(1, -1)).filter((p) => !filterReferences?.test(p)) ?? []; // src/helpers/getRegexWithoutReferences/helpers/validateReferences/validateReferences.ts var validateReferences = ({ allowedReferences, regex, filterReferences, key }) => { const references = extractReferencesFromRegex({ regex, filterReferences }); const invalidReferences = references.filter((reference) => !allowedReferences.includes(reference)).map((ref) => `{${ref}}`); if (!invalidReferences.length) return; throw getInvalidReferenceError({ invalidReferences, allowedReferences, key }); }; // src/helpers/getRegexWithoutReferences/getRegexWithoutReferences.ts var getRegexWithoutReferences = ({ regex, regexParameters, key }) => { let currentRegex = regex; const regexParametersKeys = Object.keys(regexParameters); validateReferences({ regex, allowedReferences: regexParametersKeys, key }); regexParametersKeys.forEach( (key2) => currentRegex = currentRegex.replaceAll( `{${key2}}`, regexParameters[key2] ) ); return currentRegex; }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/helpers/getDefaultRegexParameters.ts var getDefaultRegexParameters = ({ fileName, regexParameters = {} }) => ({ camelCase: CAMEL_CASE, PascalCase: PASCAL_CASE, strictCamelCase: STRICT_CAMEL_CASE, StrictPascalCase: STRICT_PASCAL_CASE, snake_case: SNAKE_CASE_LOWER, SNAKE_CASE: SNAKE_CASE_UPPER, ...regexParameters, fileName: transformStringToCase({ str: fileName, transformTo: "camelCase" }), FileName: transformStringToCase({ str: fileName, transformTo: "PascalCase" }), file_name: transformStringToCase({ str: fileName, transformTo: "snake_case" }), FILE_NAME: transformStringToCase({ str: fileName, transformTo: "SNAKE_CASE" }) }); // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.consts.ts var DEFAULT_FORMAT = [REFERENCES.camelCase]; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/prepareFormat/prepareFormat.ts var prepareFormat = ({ format, filenameWithoutParts, regexParameters }) => { let currentFormat = []; if (!format) currentFormat = DEFAULT_FORMAT; if (typeof format === "string") currentFormat = [format]; if (Array.isArray(format)) currentFormat = format; const defaultRegexParameters = getDefaultRegexParameters({ fileName: filenameWithoutParts, regexParameters }); const formatWithoutReferences = currentFormat.map( (regex) => getRegexWithoutReferences({ regex: regex.replaceAll("*", WILDCARD_REGEX).replaceAll(`${WILDCARD_REGEX}${WILDCARD_REGEX}`, "*"), regexParameters: defaultRegexParameters, key: "format" }) ); return { formatWithoutReferences, formatWithReferences: currentFormat }; }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/getPositionIndexRules.ts var getPositionIndexRules = ({ rules, regexParameters, filenamePath }) => rules.map(({ format, selector, filenamePartsToRemove, positionIndex }) => { if (positionIndex === void 0) return; const filenameWithoutParts = getFilenameWithoutParts({ filenamePartsToRemove, filenamePath }); return { positionIndex: typeof positionIndex === "number" ? positionIndex : positionIndex.index, selector, format: prepareFormat({ format, filenameWithoutParts, regexParameters }).formatWithoutReferences }; }).filter((v) => v !== void 0); // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/validatePositionIndex/helpers/getNodePosition.ts var getNodePosition = ({ bodyWithoutImports, node }) => bodyWithoutImports.findIndex( (bodyNode) => bodyNode.range[0] === node.range[0] && bodyNode.range[1] === node.range[1] || bodyNode.range[0] === node.parent.range[0] && bodyNode.range[1] === node.parent.range[1] || bodyNode.range[0] === node.parent.parent?.range[0] && bodyNode.range[1] === node.parent.parent.range[1] ); // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/helpers/validatePositionIndex/validatePositionIndex.ts var validatePositionIndex = ({ node, selectorType, context: { report, sourceCode }, positionIndex, bodyWithoutImports }) => { const nodePosition = getNodePosition({ bodyWithoutImports, node }); if (nodePosition === positionIndex) return; const nodeToReplace = bodyWithoutImports[positionIndex]; const currentNodePosition = bodyWithoutImports[nodePosition]; report({ messageId: "invalidPosition", node, data: { selectorType, currentLine: currentNodePosition.loc.start.line, correctLine: nodeToReplace.loc.start.line }, fix: (fixer) => [ fixer.replaceText(nodeToReplace, sourceCode.getText(currentNodePosition)), fixer.replaceText(currentNodePosition, sourceCode.getText(nodeToReplace)) ] }); }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/helpers/handlePositionIndex/handlePositionIndex.ts var handlePositionIndex = ({ filenamePath, node, rules, regexParameters, context, selectorType, positionIndex }) => { const positionIndexRules = getPositionIndexRules({ filenamePath, rules, regexParameters }); const bodyWithoutImports = getBodyWithoutImports(node); const newPositionIndex = getPositionIndex({ bodyWithoutImports, positionIndexRules, nodeRange: JSON.stringify(node.range), positionIndex }); validatePositionIndex({ node, positionIndex: newPositionIndex, selectorType, context, bodyWithoutImports }); }; // src/rules/fileComposition/helpers/validateFile/helpers/validateRules/validateRules.ts var validateRules = ({ name, node, filenamePath, rules, errorMessageId, regexParameters, expressionName, allowOnlySpecifiedSelectors, scope, nodeNotExported, context, context: { report }, allRules, selectorType }) => { if (!isSelectorAllowed({ rules, scope, allowOnlySpecifiedSelectors, node, selectorType, report, errorMessageId, expressionName }) || name === "*") return; const selectorTypeRules = rules.filter( ({ selector }) => isCorrectSelector({ selectorType, selector, expressionName }) ); const formatWithoutReferences = selectorTypeRules.map(({ format, filenamePartsToRemove, positionIndex }) => { const filenameWithoutParts = getFilenameWithoutParts({ filenamePartsToRemove, filenamePath }); const { formatWithReferences, formatWithoutReferences: formatWithoutReferences2 } = prepareFormat({ format, filenameWithoutParts, regexParameters }); const isValid = isNameValid({ formatWithoutReferences: formatWithoutReferences2, name }); if (isValid) { if (positionIndex === void 0 || scope === "nestedSelectors") return; return handlePositionIndex({ context, filenamePath, node: nodeNotExported ?? node, rules: allRules, selectorType, regexParameters, positionIndex }); } return getFormatWithFilenameReferences({ formatWithReferences, filename: filenameWithoutParts }); }).filter((v) => v !== void 0); if (!formatWithoutReferences.length || formatWithoutReferences.length !== selectorTypeRules.length) return; report({ node, messageId: "invalidName", data: { selectorType, formatWithoutReferences: formatWithoutReferences.flat().join(", ") } }); }; // src/rules/fileComposition/helpers/validateFile/validateFile.ts var validateFile = ({ name, expressionName, context, context: { filename, cwd }, node, nodeType, fileConfig, config }) => { if (!fileConfig) return; const { rules = [], allowOnlySpecifiedSelectors } = fileConfig; const fileExportRules = rules.filter( ({ scope: scope2 }) => isCorrectScope({ expect: "fileExport", scope: scope2 }) ); const fileRootRules = rules.filter( ({ scope: scope2 }) => isCorrectScope({ expect: "fileRoot", scope: scope2 }) ); const nestedSelectorsRules = rules.filter( ({ scope: scope2 }) => isCorrectScope({ expect: "nestedSelectors", scope: scope2 }) ); const filenamePath = import_path5.default.relative( getProjectRoot({ cwd, projectRootConfig: config.projectRoot }), filename ); const regexParameters = config.regexParameters; const selectorType = SELECTORS[nodeType]; const { isExportName, currentName, currentNode } = isExportedName({ nodeType, node, name }); if (fileExportRules.length && isExportName) { return validateRules({ rules: fileExportRules, name: currentName, selectorType, node: currentNode, nodeNotExported: node, context, filenamePath, errorMessageId: "prohibitedSelectorExport", regexParameters, expressionName, allowOnlySpecifiedSelectors, scope: "fileExport", allRules: rules }); } const isFileRootName = isNameFromFileRoot({ nodeType, node }); if (fileRootRules.length && isFileRootName && !isExportName) { return validateRules({ rules: fileRootRules, name, selectorType, node, context, filenamePath, errorMessageId: "prohibitedSelectorRoot", regexParameters, expressionName, allowOnlySpecifiedSelectors, scope: "fileRoot", allRules: rules }); } if (nestedSelectorsRules.length && !isExportName && !isFileRootName) { return validateRules({ rules: nestedSelectorsRules, name, node, filenamePath, errorMessageId: "prohibitedSelectorNested", regexParameters, expressionName, allowOnlySpecifiedSelectors, scope: "nestedSelectors", context, allRules: rules, selectorType }); } const { scope, errorMessageId } = getCurrentScopeData({ isFileExport: isExportName, isFileRoot: isFileRootName }); isSelectorAllowed({ rules: [], scope, allowOnlySpecifiedSelectors, node, selectorType, report: context.report, errorMessageId, expressionName }); }; // src/rules/fileComposition/helpers/handleClassDeclaration.ts var handleClassDeclaration = ({ node, context, config, fileConfig }) => { if (!node.id?.name) return; validateFile({ node, context, name: node.id.name, nodeType: "ClassDeclaration", config, fileConfig }); }; // src/rules/fileComposition/helpers/handleFunctionDeclaration.ts var handleFunctionDeclaration = ({ node, context, config, fileConfig }) => { if (!node.id?.name) return; validateFile({ node, context, name: node.id.name, nodeType: "FunctionDeclaration", config, fileConfig }); }; // src/rules/fileComposition/helpers/handleMethodDefinition.ts var import_utils9 = require("@typescript-eslint/utils"); var handleMethodDefinition = ({ context, node, config, fileConfig }) => { if (node.key.type !== import_utils9.TSESTree.AST_NODE_TYPES.Identifier) return; validateFile({ node, context, name: node.key.name, nodeType: "FunctionDeclaration", config, fileConfig }); }; // src/rules/fileComposition/helpers/handlePropertyDefinition.ts var import_utils10 = require("@typescript-eslint/utils"); var handlePropertyDefinition = ({ context, node, config, fileConfig }) => { if (node.key.type !== import_utils10.TSESTree.AST_NODE_TYPES.Identifier) return; const nodeType = node.value?.type === import_utils10.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ? "ArrowFunctionExpression" : "PropertyDefinition"; validateFile({ node, context, name: node.key.name, nodeType, config, fileConfig }); }; // src/rules/fileComposition/helpers/handleVariableDeclarator.ts var import_utils11 = require("@typescript-eslint/utils"); var handleVariableDeclarator = ({ node, context, config, fileConfig }) => { const expressionName = getIdentifierFromExpression(node.init); if (node.id.type === import_utils11.TSESTree.AST_NODE_TYPES.ArrayPattern || node.id.type === import_utils11.TSESTree.AST_NODE_TYPES.ObjectPattern) { if (expressionName) return validateFile({ node, context, name: "*", nodeType: "Expression", expressionName, config, fileConfig }); return validateFile({ node, context, name: "*", nodeType: "VariableDeclarator", config, fileConfig }); } if (expressionName) { return validateFile({ node, context, name: node.id.name, nodeType: "Expression", expressionName, config, fileConfig }); } const currentNodeType = node.init?.type === import_utils11.TSESTree.AST_NODE_TYPES.ArrowFunctionExpression ? "ArrowFunctionExpression" : "VariableDeclarator"; validateFile({ node, context, name: node.id.name, nodeType: currentNodeType, config, fileConfig }); }; // src/rules/fileComposition/helpers/validateRootSelectorsL