diff --git a/.eslintrc.js b/.eslintrc.js index c3aa013..ec74528 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,5 @@ 'use strict' const eloquence = require('./src/index') -module.exports = eloquence({ target: 'node', esm: false }) + +module.exports = eloquence({ target: 'node', enableESM: false, enableTS: false }) diff --git a/src/__snapshots__/index.spec.js.snap b/src/__snapshots__/index.spec.js.snap index 86bea23..a049213 100644 --- a/src/__snapshots__/index.spec.js.snap +++ b/src/__snapshots__/index.spec.js.snap @@ -11,7 +11,11 @@ Object { "prettier", "plugin:node/recommended", ], - "ignorePatterns": Array [], + "ignorePatterns": Array [ + "!.*", + "public/*", + "dist/*", + ], "overrides": Array [ Object { "files": Array [ @@ -41,9 +45,6 @@ Object { "project": "./tsconfig.json", "sourceType": "module", }, - "plugins": Array [ - "@typescript-eslint", - ], "rules": Object { "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/ban-ts-comment": Array [ @@ -113,19 +114,6 @@ Object { "prefer-spread": "error", "valid-typeof": "off", }, - "settings": Object { - "import/external-module-folders": Array [ - "node_modules", - "node_modules/@types", - ], - "import/parsers": Object { - "@typescript-eslint/parser": Array [ - ".ts", - ".tsx", - ".d.ts", - ], - }, - }, }, Object { "env": Object { @@ -180,10 +168,11 @@ Object { "ecmaFeatures": Object { "jsx": true, }, - "ecmaVersion": 11, + "ecmaVersion": 12, "sourceType": "module", }, "plugins": Array [ + "@typescript-eslint", "import", "prettier", "node", @@ -1014,7 +1003,18 @@ Object { ".tsx", ".d.ts", ], + "import/external-module-folders": Array [ + "node_modules", + "node_modules/@types", + ], "import/internal-regex": /\\^@\\\\//, + "import/parsers": Object { + "@typescript-eslint/parser": Array [ + ".ts", + ".tsx", + ".d.ts", + ], + }, "import/resolver": "/mock/test/path", "react": Object { "linkComponents": Array [ @@ -1039,9 +1039,12 @@ Object { }, "extends": Array [ "prettier", - "prettier/react", ], - "ignorePatterns": Array [], + "ignorePatterns": Array [ + "!.*", + "public/*", + "dist/*", + ], "overrides": Array [ Object { "files": Array [ @@ -1069,9 +1072,6 @@ Object { "project": "./tsconfig.json", "sourceType": "module", }, - "plugins": Array [ - "@typescript-eslint", - ], "rules": Object { "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/ban-ts-comment": Array [ @@ -1141,19 +1141,6 @@ Object { "prefer-spread": "error", "valid-typeof": "off", }, - "settings": Object { - "import/external-module-folders": Array [ - "node_modules", - "node_modules/@types", - ], - "import/parsers": Object { - "@typescript-eslint/parser": Array [ - ".ts", - ".tsx", - ".d.ts", - ], - }, - }, }, Object { "env": Object { @@ -1208,10 +1195,11 @@ Object { "ecmaFeatures": Object { "jsx": true, }, - "ecmaVersion": 11, + "ecmaVersion": 12, "sourceType": "module", }, "plugins": Array [ + "@typescript-eslint", "import", "prettier", "jest-dom", @@ -2463,7 +2451,18 @@ Object { ".tsx", ".d.ts", ], + "import/external-module-folders": Array [ + "node_modules", + "node_modules/@types", + ], "import/internal-regex": /\\^@\\\\//, + "import/parsers": Object { + "@typescript-eslint/parser": Array [ + ".ts", + ".tsx", + ".d.ts", + ], + }, "import/resolver": "/mock/test/path", "react": Object { "linkComponents": Array [ diff --git a/src/index.js b/src/index.js index ac3c245..105d7d9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,12 @@ /** Eloquence ESLint * ----------------------------------------------------------------------------- * - * ## ℹ️ Notes + * ℹ️ Package notes + * * ESModules: Package defaults to using ESM for ESLint `sourceType` * configuration, with overrides for Node executed tooling like Jest. Node ESM * using cjs and mjs file extensions not yet handled. + * * @module */ @@ -60,11 +62,11 @@ const targetConfigs = { }, // --- REACT TARGET CONFIGS react: { - extends: ['prettier/react'], + extends: [], plugins: ['jest-dom', 'jsx-a11y', 'react', 'react-hooks', 'testing-library'], rules: { - ...pluginJestDom, ...pluginReact, + ...pluginJestDom, ...pluginReactA11y, ...pluginReactHooks, ...pluginTestingLibrary, @@ -77,43 +79,48 @@ const targetConfigs = { /** * Eloquence ESLint configs generator * @param {Object} opts - * @param {boolean} [opts.esm] + * @param {boolean} [opts.enableESM] Enables ESModule linting features + * @param {boolean} [opts.enableTS] Enables TypeScript linting features * @param {string[]} [opts.ignorePatterns] Array of paths that will be ignored * @param {{[key: string]: unknown}} [opts.rules] * @param {'node'|'react'} opts.target */ module.exports = function eloquence({ - esm = true, - ignorePatterns = [], + enableESM = true, + enableTS = true, + ignorePatterns, rules = {}, target, }) { - const sourceType = esm ? 'module' : 'script' - - return { + const baseConfigs = { // Default expectation is a single config at root of project, with overrides // for directory and file customizations root: true, - ignorePatterns, + // Project custom ignore patterns, defaults to ignoring build directories + // and forcing linting of dot files and directories + ignorePatterns: ignorePatterns || ['!.*', 'public/*', 'dist/*'], extends: ['prettier', ...targetConfigs[target].extends], - // Override Espree parser with Babel - parser: 'babel-eslint', - - // Default parser to latest ECMA version and ESModules with the goal of + // Set parser to Babel using latest ECMA version and ESModules with the goal of // staying as close to current syntax as possible + parser: 'babel-eslint', parserOptions: { - ecmaVersion: 11, - sourceType, + ecmaVersion: 12, + sourceType: enableESM ? 'module' : 'script', ecmaFeatures: { jsx: true, }, }, // Plugins for imports, accessibility and react - plugins: ['import', 'prettier', ...targetConfigs[target].plugins], + plugins: [ + '@typescript-eslint', + 'import', + 'prettier', + ...targetConfigs[target].plugins, + ], settings: { // Increase import cache lifetime to 60s @@ -125,10 +132,20 @@ module.exports = function eloquence({ // Use webpack to resolve projects to handle src alias 'import/resolver': path.resolve(__dirname, 'resolver'), + // ℹ️ Import plugin TS configs apply to all projects, ref plugin:import/typescript + // Extensions that will be parsed to check for exports, including JS, TS, // React extenions, Node ESM, and type definitions 'import/extensions': ['.js', '.jsx', '.mjs', '.ts', '.tsx', '.d.ts'], + // Ensure that types are considered external imports + 'import/external-module-folders': ['node_modules', 'node_modules/@types'], + + 'import/parsers': { + // Use the ESLint-TS parser when parsing TS and type definition files + '@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'], + }, + // --- React plugin settings --- 'react': { pragma: 'React', @@ -153,18 +170,18 @@ module.exports = function eloquence({ ...coreStylisticIssues, ...coreVariables, - // --- Plugin rules --- + // --- Plugin import rules --- ...pluginImport, // --- Target rules --- ...targetConfigs[target].rules, + // Custom project rules have priority over package rules + ...rules, + // Prettier formatting enforcement via Prettier *plugin* // (this is different from the rule overrides set in the Prettier *config*) 'prettier/prettier': 'error', - - // Project specified rules have priority - ...rules, }), // -------------------------------------------------------- @@ -174,6 +191,7 @@ module.exports = function eloquence({ // --- 1️⃣ Source -------------------------- { files: ['src/**'], + rules: { // ℹ️ Prevent forgotten console.logs only needed in project source // code @@ -205,16 +223,6 @@ module.exports = function eloquence({ project: './tsconfig.json', }, - plugins: ['@typescript-eslint'], - settings: { - // Config from plugin:import/typescript - 'import/external-module-folders': ['node_modules', 'node_modules/@types'], - 'import/parsers': { - // Use the ESLint-TS parser when parsing TS and type definition files - '@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'], - }, - }, - rules: envRuleSeverities(NODE_ENV, { ...pluginTypescript, ...targetTypeScript, @@ -224,6 +232,7 @@ module.exports = function eloquence({ // --- ✅ Test files -------------------------- { files: ['*.spec.js'], + env: { jest: true, }, @@ -238,7 +247,7 @@ module.exports = function eloquence({ // --- 🌲 Cypress files -------------------------- { files: ['cypress/**/*'], - // Enable Cypress test writing best practice rules + plugins: ['cypress'], env: { 'cypress/globals': true, @@ -256,8 +265,9 @@ module.exports = function eloquence({ 'jest.config.js', 'webpack.config.js', ], + parserOptions: { - // Ensure that configs read by Node are scripts (override Cypress) + // Ensure that configs read by Node are scripts sourceType: 'script', }, env: { @@ -266,4 +276,15 @@ module.exports = function eloquence({ }, ], } + + // IF TypeScript isn't enabled remove configs that will break ESLint + if (!enableTS) { + baseConfigs.plugins = baseConfigs.plugins.filter( + (plugin) => !plugin.includes('@typescript-eslint'), + ) + + delete baseConfigs.settings['import/parsers']['@typescript-eslint/parser'] + } + + return baseConfigs } diff --git a/src/rules/plugin-typescript.js b/src/rules/plugin-typescript.js index 5dade51..c28a801 100644 --- a/src/rules/plugin-typescript.js +++ b/src/rules/plugin-typescript.js @@ -69,19 +69,4 @@ module.exports = { '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/triple-slash-reference': 'error', - - // ℹ️ Disabled Prettier rules - // (https://github.com/prettier/eslint-config-prettier/blob/master/%40typescript-eslint.js) - '@typescript-eslint/brace-style': 'off', - '@typescript-eslint/comma-spacing': 'off', - '@typescript-eslint/func-call-spacing': 'off', - '@typescript-eslint/indent': 'off', - '@typescript-eslint/keyword-spacing': 'off', - '@typescript-eslint/member-delimiter-style': 'off', - '@typescript-eslint/no-extra-parens': 'off', - '@typescript-eslint/no-extra-semi': 'off', - '@typescript-eslint/quotes': 'off', - '@typescript-eslint/semi': 'off', - '@typescript-eslint/space-before-function-paren': 'off', - '@typescript-eslint/type-annotation-spacing': 'off', } diff --git a/src/rules/target-react.js b/src/rules/target-react.js index 28ec926..d4a405b 100644 --- a/src/rules/target-react.js +++ b/src/rules/target-react.js @@ -12,4 +12,20 @@ module.exports = { tsx: 'never', }, ], + + // --- prettier/react + 'react/jsx-child-element-spacing': 'off', + 'react/jsx-closing-bracket-location': 'off', + 'react/jsx-closing-tag-location': 'off', + 'react/jsx-curly-newline': 'off', + 'react/jsx-curly-spacing': 'off', + 'react/jsx-equals-spacing': 'off', + 'react/jsx-first-prop-new-line': 'off', + 'react/jsx-indent': 'off', + 'react/jsx-indent-props': 'off', + 'react/jsx-max-props-per-line': 'off', + 'react/jsx-one-expression-per-line': 'off', + 'react/jsx-props-no-multi-spaces': 'off', + 'react/jsx-tag-spacing': 'off', + 'react/jsx-wrap-multilines': 'off', } diff --git a/src/rules/target-typescript.js b/src/rules/target-typescript.js index 545992e..804b80d 100644 --- a/src/rules/target-typescript.js +++ b/src/rules/target-typescript.js @@ -1,3 +1,17 @@ 'use strict' -module.exports = {} +module.exports = { + // --- prettier/@typescript-eslint + '@typescript-eslint/quotes': 'off', + '@typescript-eslint/brace-style': 'off', + '@typescript-eslint/comma-spacing': 'off', + '@typescript-eslint/func-call-spacing': 'off', + '@typescript-eslint/indent': 'off', + '@typescript-eslint/keyword-spacing': 'off', + '@typescript-eslint/member-delimiter-style': 'off', + '@typescript-eslint/no-extra-parens': 'off', + '@typescript-eslint/no-extra-semi': 'off', + '@typescript-eslint/semi': 'off', + '@typescript-eslint/space-before-function-paren': 'off', + '@typescript-eslint/type-annotation-spacing': 'off', +}