From 8844eb019fb47f772dec70a3d6405a8db6b23d18 Mon Sep 17 00:00:00 2001 From: Ryan Block Date: Thu, 18 Apr 2024 10:54:44 -0700 Subject: [PATCH] Rework everything for ESLint v9 --- changelog.md | 6 ++- eslint.config.js | 80 ++++++++++++++++++++++------------ package.json | 12 +---- src/lib/get-exported-name.js | 40 +++++++++++++++++ src/lib/is-ignored-filename.js | 7 +++ src/lib/parse-filename.js | 14 ++++++ src/rules/index.js | 7 +++ src/rules/match-regex.js | 50 +++++++++++++++++++++ 8 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 src/lib/get-exported-name.js create mode 100644 src/lib/is-ignored-filename.js create mode 100644 src/lib/parse-filename.js create mode 100644 src/rules/index.js create mode 100644 src/rules/match-regex.js diff --git a/changelog.md b/changelog.md index 8151810..6dc6c2d 100644 --- a/changelog.md +++ b/changelog.md @@ -2,10 +2,11 @@ --- -## [3.0.0] 2024-03-26 +## [3.0.0] 2024-04-18 ### Added +- Added ESLint v9 support - Added new rules: - `block-spacing` - https://eslint.style/rules/default/block-spacing - `comma-dangle` (multiline) - https://eslint.style/rules/default/comma-dangle @@ -15,7 +16,10 @@ ### Changed +- Breaking change: now using ESLint v9's flat config format - Updated to ES2022 (v13) +- Breaking change: `eslint-plugin-import` is not yet ESLint v9 compatible, so that has been disabled + - This will be restored as soon as the plugin has been updated, see: https://github.com/import-js/eslint-plugin-import/issues/2948 - Breaking change: disabled `global-require` erroring - Breaking change: removed support for Node.js 14.x (now EOL) diff --git a/eslint.config.js b/eslint.config.js index 355061f..1b3f6dc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,41 +1,42 @@ -let off = 'off' -let err = 'error' -let s = '@stylistic/js/' +const eslint = require('@eslint/js') +const recommended = eslint.configs.recommended.rules +const stylistic = require('@stylistic/eslint-plugin-js') +const arc = require('./src/rules') +const fp = require('eslint-plugin-fp') +// TODO: re-enable eslint-plugin-import once eslint-plugin-import#2948 is fixed +// const importPlugin = require('eslint-plugin-import') -module.exports = { - parserOptions: { - ecmaVersion: 13, // 2022 - }, - // parserOptions default source type is `script`; override to `module` because eslint bailed on #12675 - overrides: [ { - files: [ '*.mjs' ], - parserOptions: { - sourceType: 'module', - }, - } ], - env: { - es6: true, - node: true, +const ecmaVersion = 13 // 2022 +const ignores = [ './node_modules/' ] +const off = 'off' +const err = 'error' +const s = '@stylistic/js/' + +const config = { + plugins: { + recommended, + arc, + '@stylistic/js': stylistic, + fp, + // importPlugin, }, - extends: 'eslint:recommended', - plugins: [ - '@stylistic/js', - 'filenames', - 'fp', - 'import', - ], rules: { + // Previously on `eslint:recommended`: + ...recommended, + // Disable rules from base configurations 'arrow-body-style': off, 'no-console': off, 'no-inner-declarations': off, 'no-redeclare': off, 'no-useless-escape': off, + // Enable additional rules 'global-require': off, 'handle-callback-err': err, [s + 'linebreak-style']: [ err, 'unix' ], 'no-cond-assign': [ err, 'always' ], + // Style [s + 'array-bracket-spacing']: [ err, 'always' ], [s + 'arrow-spacing']: err, @@ -61,9 +62,34 @@ module.exports = { [s + 'space-infix-ops']: err, [s + 'spaced-comment']: err, [s + 'template-curly-spacing']: err, - // Modules must resolve - 'import/no-unresolved': [ err, { commonjs: true, amd: true } ], + // Ensure filesystem safe filenames - 'filenames/match-regex': [ err, '^[a-z0-9-_.]+$', true ], + 'arc/match-regex': [ err, '^[a-z0-9-_.]+$', true ], + + // Modules must resolve + // 'import/no-unresolved': [ err, { commonjs: true, amd: true } ], }, } + + +module.exports = [ + // As of ESLint v9 flat config, *.js is assumed to be ESM, so undo that: + { + files: [ '**/*.js', '**/*.cjs' ], + ignores, + languageOptions: { + ecmaVersion, + sourceType: 'commonjs', + }, + ...config, + }, + { + files: [ '**/*.mjs' ], + ignores, + languageOptions: { + ecmaVersion, + sourceType: 'module', + }, + ...config, + }, +] diff --git a/package.json b/package.json index a91c428..563d023 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "@architect/eslint-config", "version": "3.0.0-RC.0", "description": "Architect standard ESLint configuration", - "main": "index.js", "scripts": { "lint": "eslint . --fix", "test": "npm run lint", @@ -15,16 +14,9 @@ "homepage": "https://github.com/architect/eslint-config", "bugs": "https://github.com/architect/architect/issues", "dependencies": { + "@eslint/js": "^9.0.0", "@stylistic/eslint-plugin-js": "^1.7.2", - "eslint-plugin-filenames": "^1.3.2", - "eslint-plugin-fp": "^2.3.0", - "eslint-plugin-import": "^2.29.1" - }, - "peerDependencies": { - "@stylistic/eslint-plugin-js": "^1.7.0", - "eslint-plugin-filenames": "^1.3.2", - "eslint-plugin-fp": "^2.3.0", - "eslint-plugin-import": "^2.27.4" + "eslint-plugin-fp": "^2.3.0" }, "devDependencies": { "eslint": "^9.0.0" diff --git a/src/lib/get-exported-name.js b/src/lib/get-exported-name.js new file mode 100644 index 0000000..9e3b120 --- /dev/null +++ b/src/lib/get-exported-name.js @@ -0,0 +1,40 @@ +// Adapted with gratitude from eslint-plugin-filenames (https://github.com/selaux/eslint-plugin-filenames) and used under an MIT license + +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] + + // export default ... + if (node.type === 'ExportDefaultDeclaration') { + return getNodeName(node.declaration, options) + } + + // module.exports = ... + 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/src/lib/is-ignored-filename.js b/src/lib/is-ignored-filename.js new file mode 100644 index 0000000..da5830a --- /dev/null +++ b/src/lib/is-ignored-filename.js @@ -0,0 +1,7 @@ +// Adapted with gratitude from eslint-plugin-filenames (https://github.com/selaux/eslint-plugin-filenames) and used under an MIT license + +const ignoredFilenames = [ '', '' ] + +module.exports = function isIgnoredFilename (filename) { + return ignoredFilenames.indexOf(filename) !== -1 +} diff --git a/src/lib/parse-filename.js b/src/lib/parse-filename.js new file mode 100644 index 0000000..98d0d19 --- /dev/null +++ b/src/lib/parse-filename.js @@ -0,0 +1,14 @@ +// Adapted with gratitude from eslint-plugin-filenames (https://github.com/selaux/eslint-plugin-filenames) and used under an MIT license + +const path = require('path') + +module.exports = function parseFilename (filename) { + const ext = path.extname(filename) + + return { + dir: path.dirname(filename), + base: path.basename(filename), + ext: ext, + name: path.basename(filename, ext), + } +} diff --git a/src/rules/index.js b/src/rules/index.js new file mode 100644 index 0000000..20c99b2 --- /dev/null +++ b/src/rules/index.js @@ -0,0 +1,7 @@ +// Adapted with gratitude from eslint-plugin-filenames (https://github.com/selaux/eslint-plugin-filenames) and used under an MIT license + +module.exports = { + rules: { + 'match-regex': require('./match-regex'), + }, +} diff --git a/src/rules/match-regex.js b/src/rules/match-regex.js new file mode 100644 index 0000000..0f32929 --- /dev/null +++ b/src/rules/match-regex.js @@ -0,0 +1,50 @@ +// Adapted with gratitude from eslint-plugin-filenames (https://github.com/selaux/eslint-plugin-filenames) and used under an MIT license + +/** + * @fileoverview Rule to ensure that filenames match a convention (default: camelCase) + * @author Stefan Lau + */ + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const path = require('path'), + parseFilename = require('../lib/parse-filename'), + getExportedName = require('../lib/get-exported-name'), + isIgnoredFilename = require('../lib/is-ignored-filename') + +module.exports = { + meta: { + schema: { + type: 'array', + minItems: 0, + maxItems: 3, + }, + }, + create: function (context) { + const defaultRegexp = /^([a-z0-9]+)([A-Z][a-z0-9]+)*$/g, + conventionRegexp = context.options[0] ? new RegExp(context.options[0]) : defaultRegexp, + ignoreExporting = context.options[1] ? context.options[1] : false + + return { + 'Program': function (node) { + const filename = context.getFilename(), + absoluteFilename = path.resolve(filename), + parsed = parseFilename(absoluteFilename), + shouldIgnore = isIgnoredFilename(filename), + isExporting = Boolean(getExportedName(node)), + matchesRegex = conventionRegexp.test(parsed.name) + + if (shouldIgnore) return + if (ignoreExporting && isExporting) return + if (!matchesRegex) { + context.report(node, + `Filename '{{name}}' does not match the naming convention.`, + { name: parsed.base }, + ) + } + }, + } + }, +}