diff --git a/.changeset/curly-oranges-repair.md b/.changeset/curly-oranges-repair.md new file mode 100644 index 00000000..39fe95fb --- /dev/null +++ b/.changeset/curly-oranges-repair.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-decorator-position': major +--- + +Adds support for ESLint 9 with flat configs, and drops ESLint 6 compatibility. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dfc4b76..b14a5605 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,10 +69,18 @@ jobs: - { node: "16", pnpm: "7" } # pnpm 9 only supports node >= 18 - { node: "18", pnpm: "9" } + - { node: "20", pnpm: "9" } + - { node: "22", pnpm: "9" } eslint: + - "eslint@9" - "eslint@8" - "eslint@7" - - "eslint@6" + exclude: + - eslint: "eslint@9" + node: { node: "14", pnpm: "6" } + - eslint: "eslint@9" + node: { node: "16", pnpm: "7" } + steps: - uses: actions/checkout@v4 - uses: wyvox/action-setup-pnpm@v3 @@ -104,6 +112,7 @@ jobs: # tese are all relative to the smoke-tests directory script: - integration/position-default + - integration/flat-config - integration/position-prettier - integration/external-config-prettier - examples/ember diff --git a/README.md b/README.md index 09f980a4..f9b8b9ab 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ ### 1. Install plugin +```shell +pnpm add -D eslint-plugin-decorator-position +``` + +Or + ```shell yarn add --dev eslint-plugin-decorator-position ``` @@ -24,7 +30,28 @@ Or npm install --save-dev eslint-plugin-decorator-position ``` -### 2. Modify your `.eslintrc.js` +### 2. Modify your eslint config + +For modern 'flat config', append the eslint-plugin-decorator-position config to your `eslint.config.js` file: + +```diff +module.exports = [ ++ ...require('eslint-plugin-decorator-position/config/recommended'), + { + languageOptions: { + parser: require('@babel/eslint-parser'), + parserOptions: { + requireConfigFile: false, + babelOptions: { + plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], + }, + }, + }, + }, +]; +``` + +Or for the legacy eslint config format, extend the base config provided by eslint-plugin-decorator-position: ```javascript // .eslintrc.js @@ -47,6 +74,30 @@ module.exports = { Since eslint 8, the printWidth option must be specified to be compatible with the eslint-plugin-prettier rule `prettier/prettier` +```diff +// eslint.config.js +module.exports = [ + ...require('eslint-plugin-decorator-position/config/recommended'), + { + languageOptions: { + parser: require('@babel/eslint-parser'), + parserOptions: { + requireConfigFile: false, + babelOptions: { + plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], + }, + }, + }, + }, ++ rules: { ++ 'decorator-position/decorator-position': ['error', { printWidth: 100 }], ++ 'prettier/prettier': ['error', { printWidth: 100 }] ++ } +] +``` + +or + ```javascript // .eslintrc.js module.exports = { @@ -66,10 +117,35 @@ If there is a `.prettierrc.js` file, that will be read instead, and `printwidth` ## 🧰 Configurations +'Flat' configurations: | | Name | Description | |:---|:-----|:------------| -| | [base](./lib/config/base.js) | contains no rules settings, but the basic eslint configuration suitable for any project. You can use it to configure rules as you wish. | -| :hamster: | [ember](./lib/config/ember.js) | extends the `base` configuration by enabling the recommended rules for ember projects. | +| | [`/config/base`](./lib/config/base.js) | Only installs the plugin. You can use it to configure rules as you wish. | +| | [`/config/rules`](./lib/config/rules.js) | Only configures the rules. Expects the plugin to be installed. | +| | [`/config/recommended`](./lib/config/recommended.js) | Installs the plugin and configures the rules | + +Legacy configurations: +| | Name | Description | +|:---|:-----|:------------| +| | [base](./lib/config-legacy/base.js) | contains no rules settings, but the basic eslint configuration suitable for any project. You can use it to configure rules as you wish. | +| :hamster: | [ember](./lib/config-legacy/ember.js) | extends the `base` configuration by enabling the recommended rules for ember projects. | + +## Manual plugin installation + +If you prefer to manage the config yourself, the plugin can be installed like this: + +```diff +// eslint.config.js +module.exports = [ + // ... your existing config + plugins: { ++ "decorator-position": require("eslint-plugin-decorator-position"), + }, + rules: { ++ 'decorator-position/decorator-position': 'error', + } +] +``` ## 🍟 Rules diff --git a/lib/config-legacy/base.js b/lib/config-legacy/base.js new file mode 100644 index 00000000..84a0da6c --- /dev/null +++ b/lib/config-legacy/base.js @@ -0,0 +1,50 @@ +const { resolve } = require('path'); + +// Eslint v8+ does not have CLIEngine. +const { CLIEngine: v7AndEarlier } = require('eslint'); + +const isEslint7 = Boolean(v7AndEarlier); + +const esLint7Config = { + root: true, + + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, + }, + + env: { + browser: true, + es6: true, + }, + + plugins: ['decorator-position'], +}; + +const esLint8Config = { + root: true, + parser: '@babel/eslint-parser', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + legacyDecorators: true, + }, + babelOptions: { + configFile: resolve(__dirname, '../../babel.config.cjs'), + }, + }, + + env: { + browser: true, + es6: true, + }, + + plugins: ['decorator-position'], +}; + +module.exports = isEslint7 ? esLint7Config : esLint8Config; diff --git a/lib/config/ember.js b/lib/config-legacy/ember.js similarity index 100% rename from lib/config/ember.js rename to lib/config-legacy/ember.js diff --git a/lib/config/base.js b/lib/config/base.js index 84a0da6c..ef51a6ce 100644 --- a/lib/config/base.js +++ b/lib/config/base.js @@ -1,50 +1,9 @@ -const { resolve } = require('path'); +const DecoratorPosition = require('../index'); -// Eslint v8+ does not have CLIEngine. -const { CLIEngine: v7AndEarlier } = require('eslint'); - -const isEslint7 = Boolean(v7AndEarlier); - -const esLint7Config = { - root: true, - - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: { - legacyDecorators: true, - }, - }, - - env: { - browser: true, - es6: true, - }, - - plugins: ['decorator-position'], -}; - -const esLint8Config = { - root: true, - parser: '@babel/eslint-parser', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: { - legacyDecorators: true, - }, - babelOptions: { - configFile: resolve(__dirname, '../../babel.config.cjs'), +module.exports = [ + { + plugins: { + 'decorator-position': DecoratorPosition, }, }, - - env: { - browser: true, - es6: true, - }, - - plugins: ['decorator-position'], -}; - -module.exports = isEslint7 ? esLint7Config : esLint8Config; +]; diff --git a/lib/config/recommended.js b/lib/config/recommended.js new file mode 100644 index 00000000..9229a7eb --- /dev/null +++ b/lib/config/recommended.js @@ -0,0 +1 @@ +module.exports = [...require('./base'), ...require('./rules')]; diff --git a/lib/config/rules.js b/lib/config/rules.js new file mode 100644 index 00000000..8d160f35 --- /dev/null +++ b/lib/config/rules.js @@ -0,0 +1,7 @@ +module.exports = [ + { + rules: { + 'decorator-position/decorator-position': ['error', {}], + }, + }, +]; diff --git a/lib/index.js b/lib/index.js index 7ad27c13..08d0e72a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,7 +5,7 @@ module.exports = { 'decorator-position': require('./rules/decorator-position'), }, configs: { - base: require('./config/base'), - ember: require('./config/ember'), + base: require('./config-legacy/base'), + ember: require('./config-legacy/ember'), }, }; diff --git a/package.json b/package.json index 69f5aa2f..faa4b7fe 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "lib/index.js", "exports": { ".": "./lib/index.js", - "./lib/rules/decorator-position": "./lib/rules/decorator-position.js" + "./lib/rules/decorator-position": "./lib/rules/decorator-position.js", + "./config/*": "./lib/config/*.js" }, "files": [ "lib", @@ -83,7 +84,7 @@ }, "peerDependencies": { "@babel/eslint-parser": "^7.18.2", - "eslint": "^6.0.0 || ^7.31.0 || ^8.0.0" + "eslint": "^7.31.0 || ^8.0.0 || ^9.0.0" }, "peerDependenciesMeta": { "@babel/eslint-parser": { diff --git a/scripts/-pnpm.sh b/scripts/-pnpm.sh index 7b4a81f6..c6b49987 100644 --- a/scripts/-pnpm.sh +++ b/scripts/-pnpm.sh @@ -39,10 +39,8 @@ function testWithPnpm() { echo "PWD: $(pwd)" node_modules/.bin/eslint . \ --no-ignore \ - --no-eslintrc \ --config $config_path \ - --fix \ - --ext js,ts + --fix git diff --exit-code ./ } diff --git a/scripts/-yarn.sh b/scripts/-yarn.sh index b2850e1f..ca569eab 100644 --- a/scripts/-yarn.sh +++ b/scripts/-yarn.sh @@ -18,7 +18,7 @@ function testWithYarn() { echo "Running tests for $target with yarn" - config_path=".eslintrc.js" + config_path=$(test -f ".eslintrc.js" && echo ".eslintrc.js" || echo "eslint.config.js") echo "" echo "" @@ -42,10 +42,8 @@ function testWithYarn() { echo "$(pwd)" node_modules/.bin/eslint . \ --no-ignore \ - --no-eslintrc \ --config $config_path \ - --fix \ - --ext js,ts + --fix git diff --exit-code ./ } diff --git a/smoke-tests/integration/flat-config/defaults.js b/smoke-tests/integration/flat-config/defaults.js new file mode 100644 index 00000000..d74d7680 --- /dev/null +++ b/smoke-tests/integration/flat-config/defaults.js @@ -0,0 +1,25 @@ +export class Foo { + @attr title; + + @belongsTo('user') author; + @hasMany('comments') comments; + + @tracked uninitialized; + @tracked initialized = 2; + + + @foo + get myMethod() { + + } + + @foo + myMethod2() { + + } + + @foo + async myMethod3() { + + } +} diff --git a/smoke-tests/integration/flat-config/eslint.config.js b/smoke-tests/integration/flat-config/eslint.config.js new file mode 100644 index 00000000..6f96a72b --- /dev/null +++ b/smoke-tests/integration/flat-config/eslint.config.js @@ -0,0 +1,14 @@ +module.exports = [ + { + languageOptions: { + parser: require('@babel/eslint-parser'), + parserOptions: { + requireConfigFile: false, + babelOptions: { + plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], + }, + }, + }, + }, + ...require('eslint-plugin-decorator-position/config/recommended'), +]; diff --git a/smoke-tests/integration/flat-config/package.json b/smoke-tests/integration/flat-config/package.json new file mode 100644 index 00000000..869c86c8 --- /dev/null +++ b/smoke-tests/integration/flat-config/package.json @@ -0,0 +1,13 @@ +{ + "name": "test", + "version": "0.0.0", + "description": "smoke-test", + "license": "MIT", + "private": true, + "packageManager": "yarn@1.22.22", + "dependencies": { + "babel-eslint": "^10.0.3", + "eslint-plugin-decorator-position": "*", + "eslint": "^9.0.0" + } +} diff --git a/tests/lib/rules/decorator-position-js.js b/tests/lib/rules/decorator-position-js.js index 1e2315f6..1e5303b2 100644 --- a/tests/lib/rules/decorator-position-js.js +++ b/tests/lib/rules/decorator-position-js.js @@ -5,22 +5,20 @@ //------------------------------------------------------------------------------ const { stripIndent } = require('common-tags'); -const { RuleTester, CLIEngine: v7AndEarlier } = require('eslint'); +const { ESLint, RuleTester } = require('eslint'); const rule = require('../../../lib/rules/decorator-position'); // const { ERROR_MESSAGE } = rule; // -const isV7 = Boolean(v7AndEarlier); -const isV8 = !isV7; +const eslintMajor = parseInt(ESLint.version.split('.')[0], 10); // eslint-disable-next-line no-console console.debug(` ============================= JS Test Info ============================= - isV7 (or earlier): ${isV7} - isV8: ${isV8} + ESLint version: ${ESLint.version} `); //------------------------------------------------------------------------------ @@ -28,10 +26,14 @@ console.debug(` //------------------------------------------------------------------------------ const using = '@babel/eslint-parser'; -const parser = require.resolve('@babel/eslint-parser'); -const ruleTester = new RuleTester({ parser }); +const parserPath = require.resolve('@babel/eslint-parser'); +const parser = require('@babel/eslint-parser'); -const supportsESLintPrettier = Boolean(v7AndEarlier); +const ruleTester = new RuleTester( + eslintMajor >= 9 ? { languageOptions: { parser } } : { parser: parserPath } +); + +const supportsESLintPrettier = eslintMajor <= 7; describe(`JavaScript (using ${using})`, () => { ruleTester.run('decorator-position', rule, { diff --git a/tests/lib/rules/decorator-position-ts.js b/tests/lib/rules/decorator-position-ts.js index 0f917295..6f4f8c67 100644 --- a/tests/lib/rules/decorator-position-ts.js +++ b/tests/lib/rules/decorator-position-ts.js @@ -4,32 +4,31 @@ // Requirements //------------------------------------------------------------------------------ -const tsParser = require.resolve('@typescript-eslint/parser'); -const { CLIEngine: v7AndEarlier } = require('eslint'); +const tsParserPath = require.resolve('@typescript-eslint/parser'); +const tsParser = require('@typescript-eslint/parser'); +const { ESLint, RuleTester } = require('eslint'); const { stripIndent } = require('common-tags'); -const RuleTester = require('eslint').RuleTester; const rule = require('../../../lib/rules/decorator-position'); -// const { ERROR_MESSAGE } = rule; - -const isV7 = Boolean(v7AndEarlier); -const isV8 = !isV7; // eslint-disable-next-line no-console console.debug(` ============================= JS Test Info ============================= - isV7 (or earlier): ${isV7} - isV8: ${isV8} + ESLint version: ${ESLint.version} `); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const tsRuleTester = new RuleTester({ parser: tsParser }); +const eslintMajor = parseInt(ESLint.version.split('.')[0], 10); + +const tsRuleTester = new RuleTester( + eslintMajor >= 9 ? { languageOptions: { parser: tsParser } } : { parser: tsParserPath } +); describe('TypeScript (using @typescript-eslint/parser)', () => { tsRuleTester.run('decorator-position', rule, { diff --git a/tests/plugin-exports.js b/tests/plugin-exports.js index 0a256f98..138374e2 100644 --- a/tests/plugin-exports.js +++ b/tests/plugin-exports.js @@ -3,8 +3,8 @@ 'use strict'; const plugin = require('../lib'); -const base = require('../lib/config/base'); -const ember = require('../lib/config/ember'); +const base = require('../lib/config-legacy/base'); +const ember = require('../lib/config-legacy/ember'); describe('plugin exports', () => { describe('configs', () => {