diff --git a/README.md b/README.md index c69717b4..e98fc8e0 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ This config will be interpreted in the following way: | [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | | | [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | | | [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | | +| [filenames-match-regex](docs/rules/filenames-match-regex.md) | ensure filenames match a regex naming convention | ✅ | | | | [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | | | [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | | | [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | | diff --git a/docs/rules/filenames-match-regex.md b/docs/rules/filenames-match-regex.md new file mode 100644 index 00000000..dbbf1900 --- /dev/null +++ b/docs/rules/filenames-match-regex.md @@ -0,0 +1,35 @@ +# Ensure filenames match a regex naming convention (`github/filenames-match-regex`) + +💼 This rule is enabled in the ✅ `recommended` config. + + + +## Rule Details + +Rule to ensure that filenames match a convention, with a default of camelCase. + +👎 Examples of **incorrect** filename for this default rule: + +`file-name.js` + +👍 Examples of **correct** code for this rule: + +`fileName.js` + +## Options + +regex - Regex to match the filename structure. Defaults to camelCase. + + +```json +{ + "filenames-match-regex": [ + "error", + "^[a-z0-9-]+(.[a-z0-9-]+)?$" + ] +} +``` + +## Version + +4.3.2 diff --git a/eslint.config.js b/eslint.config.js index bc454483..e5dae376 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,7 +1,6 @@ const globals = require('globals') const eslintPlugin = require('eslint-plugin-eslint-plugin') const importPlugin = require('eslint-plugin-import') -const filenames = require('eslint-plugin-filenames') const i18nTextPlugin = require('eslint-plugin-i18n-text') const recommendedGitHub = require('./lib/configs/flat/recommended') const {fixupPluginRules} = require('@eslint/compat') @@ -23,13 +22,12 @@ module.exports = [ plugins: { eslintPlugin, importPlugin, - filenames, 'i18n-text': fixupPluginRules(i18nTextPlugin), }, rules: { 'importPlugin/extensions': 'off', 'importPlugin/no-commonjs': 'off', - 'filenamesPlugin/match-regex': 'off', + 'github/filenames-match-regex': 'off', 'i18n-text/no-en': 'off', 'eslint-plugin/prefer-placeholders': 'off', 'eslint-plugin/test-case-shorthand-strings': 'off', diff --git a/lib/configs/flat/browser.js b/lib/configs/flat/browser.js index 17f2b74c..67ba85fe 100644 --- a/lib/configs/flat/browser.js +++ b/lib/configs/flat/browser.js @@ -2,6 +2,7 @@ const globals = require('globals') const github = require('../../plugin') const importPlugin = require('eslint-plugin-import') const escompatPlugin = require('eslint-plugin-escompat') +const {fixupPluginRules} = require('@eslint/compat') module.exports = { ...escompatPlugin.configs['flat/recommended'], @@ -10,7 +11,7 @@ module.exports = { ...globals.browser, }, }, - plugins: {importPlugin, escompatPlugin, github}, + plugins: {importPlugin, escompatPlugin, github: fixupPluginRules(github)}, rules: { 'escompatPlugin/no-dynamic-imports': 'off', 'github/async-currenttarget': 'error', diff --git a/lib/configs/flat/internal.js b/lib/configs/flat/internal.js index e3aa8e43..0ce81f51 100644 --- a/lib/configs/flat/internal.js +++ b/lib/configs/flat/internal.js @@ -1,7 +1,8 @@ const github = require('../../plugin') +const {fixupPluginRules} = require('@eslint/compat') module.exports = { - plugins: {github}, + plugins: {github: fixupPluginRules(github)}, rules: { 'github/authenticity-token': 'error', 'github/js-class-name': 'error', diff --git a/lib/configs/flat/react.js b/lib/configs/flat/react.js index 8d2e0f80..497505fc 100644 --- a/lib/configs/flat/react.js +++ b/lib/configs/flat/react.js @@ -1,5 +1,6 @@ const github = require('../../plugin') const jsxA11yPlugin = require('eslint-plugin-jsx-a11y') +const {fixupPluginRules} = require('@eslint/compat') module.exports = { ...jsxA11yPlugin.flatConfigs.recommended, @@ -11,7 +12,7 @@ module.exports = { }, }, }, - plugins: {github, jsxA11yPlugin}, + plugins: {github: fixupPluginRules(github), jsxA11yPlugin}, rules: { 'jsxA11yPlugin/role-supports-aria-props': 'off', // Override with github/a11y-role-supports-aria-props until https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/910 is resolved 'github/a11y-aria-label-is-well-formatted': 'error', diff --git a/lib/configs/flat/recommended.js b/lib/configs/flat/recommended.js index 061260fa..31cb3400 100644 --- a/lib/configs/flat/recommended.js +++ b/lib/configs/flat/recommended.js @@ -3,7 +3,6 @@ const github = require('../../plugin') const prettierPlugin = require('eslint-plugin-prettier') const eslintComments = require('eslint-plugin-eslint-comments') const importPlugin = require('eslint-plugin-import') -const filenames = require('eslint-plugin-filenames') const i18nTextPlugin = require('eslint-plugin-i18n-text') const noOnlyTestsPlugin = require('eslint-plugin-no-only-tests') const {fixupPluginRules} = require('@eslint/compat') @@ -17,13 +16,12 @@ module.exports = { }, }, plugins: { - filenamesPlugin: fixupPluginRules(filenames), prettierPlugin, eslintComments, importPlugin, 'i18n-text': fixupPluginRules(i18nTextPlugin), noOnlyTestsPlugin, - github, + github: fixupPluginRules(github), }, rules: { 'constructor-super': 'error', @@ -34,7 +32,7 @@ module.exports = { 'eslintComments/no-unused-disable': 'error', 'eslintComments/no-unused-enable': 'error', 'eslintComments/no-use': ['error', {allow: ['eslint', 'eslint-disable-next-line', 'eslint-env', 'globals']}], - 'filenamesPlugin/match-regex': ['error', '^[a-z0-9-]+(.[a-z0-9-]+)?$'], + 'github/filenames-match-regex': ['error', '^[a-z0-9-]+(.[a-z0-9-]+)?$'], 'func-style': ['error', 'declaration', {allowArrowFunctions: true}], 'github/array-foreach': 'error', 'github/no-implicit-buggy-globals': 'error', diff --git a/lib/configs/flat/typescript.js b/lib/configs/flat/typescript.js index eb6b71fd..50cc6c05 100644 --- a/lib/configs/flat/typescript.js +++ b/lib/configs/flat/typescript.js @@ -2,12 +2,13 @@ const eslint = require('@eslint/js') const tseslint = require('typescript-eslint') const escompatPlugin = require('eslint-plugin-escompat') const github = require('../../plugin') +const {fixupPluginRules} = require('@eslint/compat') module.exports = tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, { languageOptions: { parser: tseslint.parser, }, - plugins: {'@typescript-eslint': tseslint.plugin, escompatPlugin, github}, + plugins: {'@typescript-eslint': tseslint.plugin, escompatPlugin, github: fixupPluginRules(github)}, rules: { camelcase: 'off', 'no-unused-vars': 'off', diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js index cdbbd450..23399b9f 100644 --- a/lib/configs/recommended.js +++ b/lib/configs/recommended.js @@ -8,7 +8,7 @@ module.exports = { env: { es6: true, }, - plugins: ['github', 'prettier', 'eslint-comments', 'import', 'filenames', 'i18n-text', 'no-only-tests'], + plugins: ['github', 'prettier', 'eslint-comments', 'import', 'i18n-text', 'no-only-tests'], rules: { 'constructor-super': 'error', 'eslint-comments/disable-enable-pair': 'off', @@ -18,7 +18,7 @@ module.exports = { 'eslint-comments/no-unused-disable': 'error', 'eslint-comments/no-unused-enable': 'error', 'eslint-comments/no-use': ['error', {allow: ['eslint', 'eslint-disable-next-line', 'eslint-env', 'globals']}], - 'filenames/match-regex': ['error', '^[a-z0-9-]+(.[a-z0-9-]+)?$'], + 'github/filenames-match-regex': ['error', '^[a-z0-9-]+(.[a-z0-9-]+)?$'], 'func-style': ['error', 'declaration', {allowArrowFunctions: true}], 'github/array-foreach': 'error', 'github/no-implicit-buggy-globals': 'error', diff --git a/lib/plugin.js b/lib/plugin.js index 3ed34175..eb25a25e 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -13,6 +13,7 @@ module.exports = { 'async-currenttarget': require('./rules/async-currenttarget'), 'async-preventdefault': require('./rules/async-preventdefault'), 'authenticity-token': require('./rules/authenticity-token'), + 'filenames-match-regex': require('./rules/filenames-match-regex'), 'get-attribute': require('./rules/get-attribute'), 'js-class-name': require('./rules/js-class-name'), 'no-blur': require('./rules/no-blur'), diff --git a/lib/rules/filenames-match-regex.js b/lib/rules/filenames-match-regex.js new file mode 100644 index 00000000..515887ea --- /dev/null +++ b/lib/rules/filenames-match-regex.js @@ -0,0 +1,51 @@ +// This is adapted from https://github.com/selaux/eslint-plugin-filenames since it's no longer actively maintained +// and needed a fix for eslint v9 +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: 0, + maxItems: 1, + 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), + } +} diff --git a/package.json b/package.json index 49792dc1..00864e63 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "eslint-config-prettier": ">=8.0.0", "eslint-plugin-escompat": "^3.11.3", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-i18n-text": "^1.0.1", "eslint-plugin-import": "^2.25.2", "eslint-plugin-jsx-a11y": "^6.7.1", diff --git a/test-examples/flat/eslint.config.mjs b/test-examples/flat/eslint.config.mjs index db132c02..2d4f781e 100644 --- a/test-examples/flat/eslint.config.mjs +++ b/test-examples/flat/eslint.config.mjs @@ -1,8 +1,8 @@ import github from 'eslint-plugin-github' export default [ - github.getFlatConfigs().browser, github.getFlatConfigs().recommended, + github.getFlatConfigs().browser, github.getFlatConfigs().react, ...github.getFlatConfigs().typescript, { @@ -10,9 +10,9 @@ export default [ ignores: ['eslint.config.mjs'], rules: { 'github/array-foreach': 'error', - 'github/async-preventdefault': 'warn', 'github/no-then': 'error', 'github/no-blur': 'error', + 'github/async-preventdefault': 'error', }, }, ] diff --git a/test-examples/flat/package.json b/test-examples/flat/package.json index 99ee6c96..ab18bedc 100644 --- a/test-examples/flat/package.json +++ b/test-examples/flat/package.json @@ -9,7 +9,7 @@ "@eslint/js": "^9.5.0", "@types/node": "^20.14.5", "cross-env": "^7.0.3", - "eslint": "^8.57.0", + "eslint": "^9.14.0", "eslint-plugin-github": "file:../..", "typescript": "^5.6.3", "typescript-eslint": "^8.12.2" diff --git a/test-examples/flat/src/getAttribute.js b/test-examples/flat/src/getAttribute.js index 5aaca01c..bccff298 100644 --- a/test-examples/flat/src/getAttribute.js +++ b/test-examples/flat/src/getAttribute.js @@ -6,3 +6,9 @@ const title = document.createElement('h1') title.textContent = `${title}!` foobar(title) + +document.addEventListener('click', async function (event) { + const data = await fetch() + + event.preventDefault() +}) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy