diff --git a/lib/rules/filenames-match-regex.js b/lib/rules/filenames-match-regex.js new file mode 100644 index 00000000..5c556ce7 --- /dev/null +++ b/lib/rules/filenames-match-regex.js @@ -0,0 +1,49 @@ +const path = require('path') +const parseFilename = require('../utils/parse-filename') +const getExportedName = require('../utils/get-exported-name') +const isIgnoredFilename = require('../utils/is-ignored-filename') + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'ensure filenames match a regex naming convention', + url: require('../url')(module), + }, + schema: { + type: 'array', + minItems: 1, + maxItems: 2, + items: [ + { + type: 'string', + }, + ], + }, + }, + + create(context) { + const defaultRegexp = /^([a-z0-9]+)([A-Z][a-z0-9]+)*$/g + const conventionRegexp = context.options[0] ? new RegExp(context.options[0]) : defaultRegexp + const ignoreExporting = context.options[1] ? context.options[1] : false + + return { + Program(node) { + const filename = context.getFilename() + const absoluteFilename = path.resolve(filename) + const parsed = parseFilename(absoluteFilename) + const shouldIgnore = isIgnoredFilename(filename) + const isExporting = Boolean(getExportedName(node)) + const matchesRegex = conventionRegexp.test(parsed.name) + + if (shouldIgnore) return + if (ignoreExporting && isExporting) return + if (!matchesRegex) { + context.report(node, "Filename '{{name}}' does not match the regex naming convention.", { + name: parsed.base, + }) + } + }, + } + }, +} diff --git a/lib/utils/get-exported-name.js b/lib/utils/get-exported-name.js new file mode 100644 index 00000000..642f0a0c --- /dev/null +++ b/lib/utils/get-exported-name.js @@ -0,0 +1,37 @@ +function getNodeName(node, options) { + const op = options || [] + + if (node.type === 'Identifier') { + return node.name + } + + if (node.id && node.id.type === 'Identifier') { + return node.id.name + } + + if (op[2] && node.type === 'CallExpression' && node.callee.type === 'Identifier') { + return node.callee.name + } +} + +module.exports = function getExportedName(programNode, options) { + for (let i = 0; i < programNode.body.length; i += 1) { + const node = programNode.body[i] + + if (node.type === 'ExportDefaultDeclaration') { + return getNodeName(node.declaration, options) + } + + if ( + node.type === 'ExpressionStatement' && + node.expression.type === 'AssignmentExpression' && + node.expression.left.type === 'MemberExpression' && + node.expression.left.object.type === 'Identifier' && + node.expression.left.object.name === 'module' && + node.expression.left.property.type === 'Identifier' && + node.expression.left.property.name === 'exports' + ) { + return getNodeName(node.expression.right, options) + } + } +} diff --git a/lib/utils/is-ignored-filename.js b/lib/utils/is-ignored-filename.js new file mode 100644 index 00000000..18f41153 --- /dev/null +++ b/lib/utils/is-ignored-filename.js @@ -0,0 +1,5 @@ +const ignoredFilenames = ['', ''] + +module.exports = function isIgnoredFilename(filename) { + return ignoredFilenames.indexOf(filename) !== -1 +} diff --git a/lib/utils/parse-filename.js b/lib/utils/parse-filename.js new file mode 100644 index 00000000..ce589c71 --- /dev/null +++ b/lib/utils/parse-filename.js @@ -0,0 +1,12 @@ +const path = require('path') + +module.exports = function parseFilename(filename) { + const ext = path.extname(filename) + + return { + dir: path.dirname(filename), + base: path.basename(filename), + ext, + name: path.basename(filename, ext), + } +}