diff --git a/.eslintrc b/.eslintrc index 76867059..e1c24ff2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,3 @@ { - "extends": "./base.js" + "extends": "./script.js" } diff --git a/.gitignore b/.gitignore index 0a5f716e..f30c215e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ yarn.lock +.npmrc diff --git a/CHANGELOG.md b/CHANGELOG.md index f3e38818..59ac3a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +# v1.0.0 + +## Changes + +- **[Breaking]**: upgrade `eslint` to `^7.10.0` +- **[Breaking]**: upgrade `eslint-plugin-react-hooks` to `^4.1.2` +- **[Breaking]**: upgrade `eslint-plugin-flowtype` to `^5.2.0` +- **[Breaking]**: remove `eslint-plugin-ramda` to `^5.1.0` +- **[Breaking]**: minimum supported NodeJs version 12.18 +- upgrade `babel-eslint` to `^10.1.0` +- upgrade `eslint-plugin-eslint-comments` to `^3.2.0` +- upgrade `eslint-plugin-import` to `^2.22.1` +- upgrade `eslint-plugin-jsx-a11y` to `^6.3.1` +- upgrade `eslint-plugin-react` to `^7.21.2` +- Fix dependency list in README +- Consider import starting with $ to be internal + +## Likely to cause new errors + +- `require-atomic-updates` +- `array-bracket-newline` +- `func-names`: is now reported as error instead of warning +- `func-style` +- `lines-around-directive`: removed & replaced by `padding-line-between-statements` +- `no-multiple-empty-lines`: `{ max: 1, maxBOF: 0, maxEOF: 0 }` +- `no-restricted-syntax`: allow `for..of` usage +- `flowtype/require-readonly-react-props` +- `flowtype/no-existential-type` +- `import/no-namespace` +- `import/order`: add `alphabetize: { order: 'asc' 'caseInsensitive': true }` & change `groups` to the following - `order: `builtin`, `external`, `internal`, `parent`, `sibling` +- `import/no-unassigned-import` +- `import/no-anonymous-default-export`: for functions & classes +- `import/exports-last` +- `import/group-exports` +- `react/function-component-definition` +- `react/jsx-key` +- `react/no-direct-mutation-state` +- `react/state-in-constructor` +- `react/prefer-read-only-props` + # v0.3.1 ## Changes diff --git a/README.md b/README.md index 61512793..e139c2c8 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,6 @@ see `eslint-config-nugit/base`. Our default export contains all of our ESLint rules, including ECMAScript 6+ and FlowJS. -It requires `eslint` and `eslint-plugin-import`. +It requires `eslint`, `eslint-plugin-import` and `eslint-plugin-flowtype`. - Add `"extends": "nugit/base"` to your .eslintrc diff --git a/base.js b/base.js index 85079ae4..67915d21 100644 --- a/base.js +++ b/base.js @@ -8,7 +8,6 @@ module.exports = { './rules/es6', './rules/imports', './rules/strict', - './rules/ramda.js', './rules/flow.js', './rules/eslint-comments', ].map(require.resolve), @@ -33,6 +32,11 @@ module.exports = { 'jest': true, 'shared-node-browser': true, }, + rules: { + // Reports modules without any exports, or with unused exports + // https://github.com/benmosher/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md + 'import/no-unused-modules': 'off', + }, }, ], }; diff --git a/package.json b/package.json index be36e5c7..81c38054 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-nugit", - "version": "0.3.1", + "version": "1.0.0", "description": "Nugit's JS ESLint config, following our styleguide", "main": "index.js", "scripts": { @@ -28,31 +28,29 @@ }, "homepage": "https://github.com/nugit/eslint-config-nugit", "devDependencies": { - "babel-eslint": "^10.0.3", - "eslint": "^6.8.0", - "eslint-plugin-eslint-comments": "^3.1.2", - "eslint-plugin-flowtype": "^4.5.3", - "eslint-plugin-import": "^2.19.1", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-ramda": "^2.5.1", - "eslint-plugin-react": "^7.17.0", - "eslint-plugin-react-hooks": "^2.3.0" + "babel-eslint": "^10.1.0", + "eslint": "^7.10.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-flowtype": "^5.2.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.21.2", + "eslint-plugin-react-hooks": "^4.1.2" }, "peerDependencies": { - "babel-eslint": "^10.0.3", - "eslint": "^6.8.0", - "eslint-plugin-eslint-comments": "^3.1.2", - "eslint-plugin-flowtype": "^4.5.3", - "eslint-plugin-import": "^2.19.1", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-ramda": "^2.5.1", - "eslint-plugin-react": "^7.17.0", - "eslint-plugin-react-hooks": "^2.3.0" + "babel-eslint": "^10.1.0", + "eslint": "^7.10.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-flowtype": "^5.2.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.21.2", + "eslint-plugin-react-hooks": "^4.1.2" }, "engines": { - "node": ">= 6" + "node": ">= 12" }, "dependencies": { - "confusing-browser-globals": "^1.0.7" + "confusing-browser-globals": "^1.0.9" } } diff --git a/rules/errors.js b/rules/errors.js index d7e704bd..2db35418 100644 --- a/rules/errors.js +++ b/rules/errors.js @@ -123,8 +123,7 @@ module.exports = { // Disallow assignments that can lead to race conditions due to usage of await or yield // https://eslint.org/docs/rules/require-atomic-updates - // TODO: enable, semver-major - 'require-atomic-updates': 'off', + 'require-atomic-updates': 'error', // disallow comparisons with the value NaN 'use-isnan': 'error', diff --git a/rules/flow.js b/rules/flow.js index 3bd74931..8698f47c 100644 --- a/rules/flow.js +++ b/rules/flow.js @@ -15,23 +15,35 @@ module.exports = { 'flowtype/delimiter-dangle': ['error', 'always-multiline', 'always-multiline', 'always-multiline'], 'flowtype/generic-spacing': ['error', 'never'], 'flowtype/newline-after-flow-annotation': ['error', 'always'], - 'flowtype/object-type-delimiter': ['error', 'comma'], + 'flowtype/no-existential-type': 'error', + 'flowtype/no-flow-fix-me-comments': 'off', 'flowtype/no-mixed': 'off', - 'flowtype/no-dupe-keys': 'error', + 'flowtype/no-mutable-array': 'off', 'flowtype/no-primitive-constructor-types': 'error', 'flowtype/no-types-missing-file-annotation': 'error', 'flowtype/no-unused-expressions': 'error', + // TODO: disable any? 'flowtype/no-weak-types': ['error', { any: false }], + 'flowtype/object-type-delimiter': ['error', 'comma'], + 'flowtype/require-compound-type-alias': ['off', 'always'], + 'flowtype/require-exact-type': 'off', + 'flowtype/require-indexer-name': 'off', + 'flowtype/require-inexact-type': 'off', 'flowtype/require-parameter-type': 'off', - 'flowtype/require-readonly-react-props': 'off', + 'flowtype/require-readonly-react-props': 'error', 'flowtype/require-return-type': 'off', + 'flowtype/require-types-at-top': 'off', 'flowtype/require-valid-file-annotation': 'off', - 'flowtype/sort-keys': ['error', 'asc', { caseSensitive: false, natural: true }], + 'flowtype/require-variable-type': 'off', 'flowtype/semi': ['error', 'always'], + 'flowtype/sort-keys': ['error', 'asc'], 'flowtype/space-after-type-colon': ['error', 'always'], 'flowtype/space-before-generic-bracket': ['error', 'never'], 'flowtype/space-before-type-colon': ['error', 'never'], + 'flowtype/spread-exact-type': 'off', 'flowtype/type-id-match': 'off', + // Use identifier as declaration imports are not well supported by vscode + 'flowtype/type-import-style': ['off', 'declaration'], 'flowtype/union-intersection-spacing': ['error', 'always'], 'flowtype/use-flow-type': 'warn', }, diff --git a/rules/imports.js b/rules/imports.js index 1217127a..9173233c 100644 --- a/rules/imports.js +++ b/rules/imports.js @@ -129,9 +129,8 @@ module.exports = { 'import/no-duplicates': 'error', // disallow namespace imports - // TODO: enable? // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-namespace.md - 'import/no-namespace': 'off', + 'import/no-namespace': 'error', // Ensure consistent use of file extension within the import path // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md @@ -143,11 +142,30 @@ module.exports = { // ensure absolute imports are above relative imports and that unassigned imports are ignored // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/order.md - // TODO: enforce a stricter convention in module import order? 'import/order': [ 'error', { - 'groups': [['builtin', 'external', 'internal']], + 'groups': [ + ['builtin'], + ['external'], + ['internal'], + ['parent'], + ['sibling'], + ], 'newlines-between': 'never', + 'alphabetize': { + order: 'asc', + caseInsensitive: true, + }, + 'pathGroups': [ + { + pattern: '$*/**', + group: 'internal', + }, + { + pattern: '$*', + group: 'internal', + }, + ], }, ], @@ -195,7 +213,7 @@ module.exports = { // Prevent unassigned imports // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unassigned-import.md // importing for side effects is perfectly acceptable, if you need side effects. - 'import/no-unassigned-import': 'off', + 'import/no-unassigned-import': 'error', // Prevent importing the default as if it were named // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-default.md @@ -203,25 +221,24 @@ module.exports = { // Reports if a module's default export is unnamed // https://github.com/benmosher/eslint-plugin-import/blob/d9b712ac7fd1fddc391f7b234827925c160d956f/docs/rules/no-anonymous-default-export.md - 'import/no-anonymous-default-export': ['off', { - allowArray: false, + 'import/no-anonymous-default-export': ['error', { + allowArray: true, allowArrowFunction: false, allowAnonymousClass: false, allowAnonymousFunction: false, - allowLiteral: false, - allowObject: false, + allowLiteral: true, + allowObject: true, }], // This rule enforces that all exports are declared at the bottom of the file. // https://github.com/benmosher/eslint-plugin-import/blob/98acd6afd04dcb6920b81330114e146dc8532ea4/docs/rules/exports-last.md - // TODO: enable? - 'import/exports-last': 'off', + 'import/exports-last': 'error', // Reports when named exports are not grouped together in a single export declaration // or when multiple assignments to CommonJS module.exports or exports object are present // in a single file. // https://github.com/benmosher/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/group-exports.md - 'import/group-exports': 'off', + 'import/group-exports': 'error', // forbid default exports. this is a terrible rule, do not use it. // https://github.com/benmosher/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/no-default-export.md @@ -237,7 +254,7 @@ module.exports = { // Forbid cyclical dependencies between modules // https://github.com/benmosher/eslint-plugin-import/blob/d81f48a2506182738409805f5272eff4d77c9348/docs/rules/no-cycle.md - 'import/no-cycle': ['error', { maxDepth: Infinity }], + 'import/no-cycle': ['error', { ignoreExternal: true, maxDepth: Infinity }], // Ensures that there are no useless path segments // https://github.com/benmosher/eslint-plugin-import/blob/ebafcbf59ec9f653b2ac2a0156ca3bcba0a7cf57/docs/rules/no-useless-path-segments.md @@ -256,9 +273,7 @@ module.exports = { // Reports modules without any exports, or with unused exports // https://github.com/benmosher/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md - // TODO: enable, semver-major 'import/no-unused-modules': ['off', { - ignoreExports: [], missingExports: true, unusedExports: true, }], diff --git a/rules/ramda.js b/rules/ramda.js deleted file mode 100644 index e693a6b7..00000000 --- a/rules/ramda.js +++ /dev/null @@ -1,38 +0,0 @@ -module.exports = { - env: { - es6: true, - }, - - plugins: [ - 'ramda', - ], - - rules: { - 'ramda/always-simplification': 'error', - 'ramda/any-pass-simplification': 'error', - 'ramda/both-simplification': 'error', - 'ramda/complement-simplification': 'error', - 'ramda/compose-pipe-style': 'off', - 'ramda/compose-simplification': 'error', - 'ramda/cond-simplification': 'error', - 'ramda/either-simplification': 'error', - 'ramda/eq-by-simplification': 'error', - 'ramda/filter-simplification': 'error', - 'ramda/if-else-simplification': 'error', - 'ramda/map-simplification': 'error', - 'ramda/merge-simplification': 'error', - 'ramda/no-redundant-and': 'error', - 'ramda/no-redundant-not': 'error', - 'ramda/no-redundant-or': 'error', - 'ramda/pipe-simplification': 'error', - 'ramda/prefer-both-either': 'off', - 'ramda/prefer-complement': 'error', - 'ramda/prefer-ramda-boolean': 'off', - 'ramda/prop-satisfies-simplification': 'error', - 'ramda/reduce-simplification': 'error', - 'ramda/reject-simplification': 'error', - 'ramda/set-simplification': 'error', - 'ramda/unless-simplification': 'error', - 'ramda/when-simplification': 'error', - }, -}; diff --git a/rules/react.js b/rules/react.js index c6f948e5..7fa84e6f 100644 --- a/rules/react.js +++ b/rules/react.js @@ -53,6 +53,13 @@ module.exports = { // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-dom-props.md 'react/forbid-dom-props': ['off', { forbid: [] }], + // Enforce a specific function type for function components. + // https://github.com/yannickcr/eslint-plugin-react/blob/v7.20.0/docs/rules/function-component-definition.md + 'react/function-component-definition': ['error', { + namedComponents: 'function-declaration', + unnamedComponents: 'function-expression', + }], + // Enforce boolean attributes notation in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md 'react/jsx-boolean-value': ['error', 'never', { always: [] }], @@ -82,7 +89,7 @@ module.exports = { // Validate JSX has key prop when in array or iterator // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md - 'react/jsx-key': 'off', + 'react/jsx-key': ['error', { checkFragmentShorthand: true }], // Limit maximum of props on a single line in JSX // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md @@ -177,7 +184,7 @@ module.exports = { // Prevent direct mutation of this.state // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md - 'react/no-direct-mutation-state': 'off', + 'react/no-direct-mutation-state': 'error', // Prevent usage of isMounted // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md @@ -300,7 +307,7 @@ module.exports = { // Enforce JSX indentation // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md - 'react/jsx-indent': ['error', 2], + 'react/jsx-indent': ['error', 2, { indentLogicalExpressions: true, checkAttributes: true }], // Disallow target="_blank" on links // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-no-target-blank.md @@ -476,12 +483,10 @@ module.exports = { // Enforce state initialization style // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/state-in-constructor.md - // TODO: set to "never" once babel-preset-airbnb supports public class fields - 'react/state-in-constructor': ['off', 'never'], + 'react/state-in-constructor': ['error', 'never'], // Enforces where React component static properties should be positioned // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/static-property-placement.md - // TODO: set to "static public field" once babel-preset-airbnb supports public class fields 'react/static-property-placement': ['error', 'static public field'], // Disallow JSX props spreading @@ -494,7 +499,7 @@ module.exports = { // Enforce that props are read-only // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-read-only-props.md - 'react/prefer-read-only-props': 'off', + 'react/prefer-read-only-props': 'error', }, settings: { diff --git a/rules/style.js b/rules/style.js index a8578933..5d0bb7ee 100644 --- a/rules/style.js +++ b/rules/style.js @@ -2,12 +2,10 @@ module.exports = { rules: { // enforce line breaks after opening and before closing array brackets // https://eslint.org/docs/rules/array-bracket-newline - // TODO: enable? semver-major - 'array-bracket-newline': ['off', 'consistent'], // object option alternative: { multiline: true, minItems: 3 } + 'array-bracket-newline': ['error', 'consistent'], // object option alternative: { multiline: true, minItems: 3 } // enforce line breaks between array elements // https://eslint.org/docs/rules/array-element-newline - // TODO: enable? semver-major 'array-element-newline': ['off', { multiline: true, minItems: 3 }], // enforce spacing inside array brackets @@ -90,12 +88,11 @@ module.exports = { // require function expressions to have a name // https://eslint.org/docs/rules/func-names - 'func-names': 'warn', + 'func-names': 'error', // enforces use of function declarations or expressions // https://eslint.org/docs/rules/func-style - // TODO: enable - 'func-style': ['off', 'expression'], + 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], // enforce consistent line breaks inside function parentheses // https://eslint.org/docs/rules/function-paren-newline @@ -163,7 +160,6 @@ module.exports = { // enforce position of line comments // https://eslint.org/docs/rules/line-comment-position - // TODO: enable? 'line-comment-position': ['off', { position: 'above', ignorePattern: '', @@ -183,7 +179,8 @@ module.exports = { // require or disallow newlines around directives // https://eslint.org/docs/rules/lines-around-directive - 'lines-around-directive': ['error', { + // Deprecated in favor of padding-line-between-statements + 'lines-around-directive': ['off', { before: 'always', after: 'always', }], @@ -303,7 +300,7 @@ module.exports = { // disallow multiple empty lines, only one newline at the end, and no new lines at the beginning // https://eslint.org/docs/rules/no-multiple-empty-lines - 'no-multiple-empty-lines': ['error', { max: 2, maxBOF: 1, maxEOF: 0 }], + 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], // disallow negated conditions // https://eslint.org/docs/rules/no-negated-condition @@ -327,10 +324,6 @@ module.exports = { selector: 'ForInStatement', message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.', }, - { - selector: 'ForOfStatement', - message: 'iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.', - }, { selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', diff --git a/rules/variables.js b/rules/variables.js index 96408bd4..759eb8a9 100644 --- a/rules/variables.js +++ b/rules/variables.js @@ -32,7 +32,6 @@ module.exports = { // disallow use of undefined variable // https://eslint.org/docs/rules/no-undefined - // TODO: enable? 'no-undefined': 'off', // disallow declaration of variables that are not used in the code diff --git a/script.js b/script.js index cc77de66..3f3645a1 100644 --- a/script.js +++ b/script.js @@ -1,11 +1,11 @@ module.exports = { - extends: [ - './base', - ].map(require.resolve), env: { 'node': true, 'shared-node-browser': false, }, + extends: [ + './base', + ].map(require.resolve), rules: { 'import/no-extraneous-dependencies': [ 'error', @@ -14,6 +14,7 @@ module.exports = { optionalDependencies: false, }, ], + 'import/no-unused-modules': 'off', 'no-console': 'off', }, };