UNPKG

eslint-plugin-file-export-name

Version:

ESLint plugin to enforce matching file names with default export names.

133 lines (119 loc) 4.48 kB
const path = require("path"); const { convertToCase, matchPattern } = require("./utils.cjs"); module.exports = { rules: { "match-file-export-name": { meta: { type: "suggestion", docs: { description: "Ensure default export name matches the file name", category: "Best Practices", recommended: false, }, schema: [{ oneOf: [ { type: "object", properties: { cases: { type: "array", items: { type: "string", enum: ["pascal", "camel", "kebab", "snake"] }, uniqueItems: true, }, pattern: { type: "string" }, ignore: { type: "array", items: { type: "string" } }, extensions: { type: "array", items: { type: "string" }, uniqueItems: true, }, }, additionalProperties: false, }, { type: "array", items: { type: "object", properties: { cases: { type: "array", items: { type: "string", enum: ["pascal", "camel", "kebab", "snake"] }, uniqueItems: true, }, pattern: { type: "string" }, ignore: { type: "array", items: { type: "string" } }, extensions: { type: "array", items: { type: "string" }, uniqueItems: true, }, }, additionalProperties: false, }, } ] }], messages: { mismatch: "Default export '{{exportName}}' does not match the file name '{{fileName}}' in any of the allowed cases or patterns.", }, }, create(context) { const defaultConfig = { cases: ["pascal", "camel"], extensions: [".jsx", ".tsx", ".js", ".ts"], ignore: ["<text>", "<input>"], pattern: null }; const options = context.options[0] ?? {}; const configurations = Array.isArray(options) ? options : [options]; return { Program(node) { const filePath = context.getFilename(); const fileExt = path.extname(filePath); const fileNameWithExt = path.basename(filePath); const fileName = fileNameWithExt.replace(fileExt, ""); const matchingConfig = configurations.length === 0 ? defaultConfig : configurations.find(config => [...defaultConfig.extensions, ...(config.extensions || [])].includes(fileExt) ) || defaultConfig; const allowedCases = matchingConfig.cases?.length ? matchingConfig.cases : defaultConfig.cases; const pattern = matchingConfig.pattern ? new RegExp(matchingConfig.pattern) : defaultConfig.pattern; const ignoreFiles = [...defaultConfig.ignore, ...(matchingConfig.ignore || [])]; const extensions = [...defaultConfig.extensions, ...(matchingConfig.extensions || [])]; if ( fileName === "index" || ignoreFiles.some((pattern) => new RegExp(pattern).test(filePath)) ) { return; } let defaultExportName = null; node.body.forEach((statement) => { if ( statement.type === "ExportDefaultDeclaration" && statement.declaration.type === "Identifier" ) { defaultExportName = statement.declaration.name; } }); if (!defaultExportName) return; const isCaseMatch = allowedCases.length > 0 && allowedCases.some((caseType) => convertToCase(fileName, caseType) === defaultExportName); const isPatternMatch = pattern && matchPattern(pattern, defaultExportName); if (!isCaseMatch && !isPatternMatch) { context.report({ node, messageId: "mismatch", data: { exportName: defaultExportName, fileName, }, }); } }, }; }, }, }, };