From 048e22ea989120dc9b6afe715dc24e7762504aa8 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Tue, 18 Aug 2015 18:02:31 -0700 Subject: [PATCH 1/3] somewhat modularize eslint-config-airbnb; add some tests --- packages/eslint-config-airbnb/base/index.js | 171 +++++++++++++ packages/eslint-config-airbnb/index.js | 228 +----------------- packages/eslint-config-airbnb/package.json | 12 +- packages/eslint-config-airbnb/react.js | 36 +++ .../eslint-config-airbnb/test/test-base.js | 13 + .../test/test-react-order.js | 83 +++++++ 6 files changed, 323 insertions(+), 220 deletions(-) create mode 100644 packages/eslint-config-airbnb/base/index.js create mode 100644 packages/eslint-config-airbnb/react.js create mode 100644 packages/eslint-config-airbnb/test/test-base.js create mode 100644 packages/eslint-config-airbnb/test/test-react-order.js diff --git a/packages/eslint-config-airbnb/base/index.js b/packages/eslint-config-airbnb/base/index.js new file mode 100644 index 00000000..c47c2c45 --- /dev/null +++ b/packages/eslint-config-airbnb/base/index.js @@ -0,0 +1,171 @@ +module.exports = { + 'parser': 'babel-eslint', // https://github.com/babel/babel-eslint + 'env': { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments + 'browser': true, // browser global variables + 'node': true // Node.js global variables and Node.js-specific rules + }, + 'ecmaFeatures': { + 'arrowFunctions': true, + 'blockBindings': true, + 'classes': true, + 'defaultParams': true, + 'destructuring': true, + 'forOf': true, + 'generators': false, + 'modules': true, + 'objectLiteralComputedProperties': true, + 'objectLiteralDuplicateProperties': false, + 'objectLiteralShorthandMethods': true, + 'objectLiteralShorthandProperties': true, + 'spread': true, + 'superInFunctions': true, + 'templateStrings': true, + 'jsx': true + }, + 'rules': { +/** + * Strict mode + */ + // babel inserts 'use strict'; for us + 'strict': [2, 'never'], // http://eslint.org/docs/rules/strict + +/** + * ES6 + */ + 'no-var': 2, // http://eslint.org/docs/rules/no-var + 'prefer-const': 2, // http://eslint.org/docs/rules/prefer-const + +/** + * Variables + */ + 'no-shadow': 2, // http://eslint.org/docs/rules/no-shadow + 'no-shadow-restricted-names': 2, // http://eslint.org/docs/rules/no-shadow-restricted-names + 'no-unused-vars': [2, { // http://eslint.org/docs/rules/no-unused-vars + 'vars': 'local', + 'args': 'after-used' + }], + 'no-use-before-define': 2, // http://eslint.org/docs/rules/no-use-before-define + +/** + * Possible errors + */ + 'comma-dangle': [2, 'always-multiline'], // http://eslint.org/docs/rules/comma-dangle + 'no-cond-assign': [2, 'always'], // http://eslint.org/docs/rules/no-cond-assign + 'no-console': 1, // http://eslint.org/docs/rules/no-console + 'no-debugger': 1, // http://eslint.org/docs/rules/no-debugger + 'no-alert': 1, // http://eslint.org/docs/rules/no-alert + 'no-constant-condition': 1, // http://eslint.org/docs/rules/no-constant-condition + 'no-dupe-keys': 2, // http://eslint.org/docs/rules/no-dupe-keys + 'no-duplicate-case': 2, // http://eslint.org/docs/rules/no-duplicate-case + 'no-empty': 2, // http://eslint.org/docs/rules/no-empty + 'no-ex-assign': 2, // http://eslint.org/docs/rules/no-ex-assign + 'no-extra-boolean-cast': 0, // http://eslint.org/docs/rules/no-extra-boolean-cast + 'no-extra-semi': 2, // http://eslint.org/docs/rules/no-extra-semi + 'no-func-assign': 2, // http://eslint.org/docs/rules/no-func-assign + 'no-inner-declarations': 2, // http://eslint.org/docs/rules/no-inner-declarations + 'no-invalid-regexp': 2, // http://eslint.org/docs/rules/no-invalid-regexp + 'no-irregular-whitespace': 2, // http://eslint.org/docs/rules/no-irregular-whitespace + 'no-obj-calls': 2, // http://eslint.org/docs/rules/no-obj-calls + 'no-sparse-arrays': 2, // http://eslint.org/docs/rules/no-sparse-arrays + 'no-unreachable': 2, // http://eslint.org/docs/rules/no-unreachable + 'use-isnan': 2, // http://eslint.org/docs/rules/use-isnan + 'block-scoped-var': 2, // http://eslint.org/docs/rules/block-scoped-var + +/** + * Best practices + */ + 'consistent-return': 2, // http://eslint.org/docs/rules/consistent-return + 'curly': [2, 'multi-line'], // http://eslint.org/docs/rules/curly + 'default-case': 2, // http://eslint.org/docs/rules/default-case + 'dot-notation': [2, { // http://eslint.org/docs/rules/dot-notation + 'allowKeywords': true + }], + 'eqeqeq': 2, // http://eslint.org/docs/rules/eqeqeq + 'guard-for-in': 2, // http://eslint.org/docs/rules/guard-for-in + 'no-caller': 2, // http://eslint.org/docs/rules/no-caller + 'no-else-return': 2, // http://eslint.org/docs/rules/no-else-return + 'no-eq-null': 2, // http://eslint.org/docs/rules/no-eq-null + 'no-eval': 2, // http://eslint.org/docs/rules/no-eval + 'no-extend-native': 2, // http://eslint.org/docs/rules/no-extend-native + 'no-extra-bind': 2, // http://eslint.org/docs/rules/no-extra-bind + 'no-fallthrough': 2, // http://eslint.org/docs/rules/no-fallthrough + 'no-floating-decimal': 2, // http://eslint.org/docs/rules/no-floating-decimal + 'no-implied-eval': 2, // http://eslint.org/docs/rules/no-implied-eval + 'no-lone-blocks': 2, // http://eslint.org/docs/rules/no-lone-blocks + 'no-loop-func': 2, // http://eslint.org/docs/rules/no-loop-func + 'no-multi-str': 2, // http://eslint.org/docs/rules/no-multi-str + 'no-native-reassign': 2, // http://eslint.org/docs/rules/no-native-reassign + 'no-new': 2, // http://eslint.org/docs/rules/no-new + 'no-new-func': 2, // http://eslint.org/docs/rules/no-new-func + 'no-new-wrappers': 2, // http://eslint.org/docs/rules/no-new-wrappers + 'no-octal': 2, // http://eslint.org/docs/rules/no-octal + 'no-octal-escape': 2, // http://eslint.org/docs/rules/no-octal-escape + 'no-param-reassign': 2, // http://eslint.org/docs/rules/no-param-reassign + 'no-proto': 2, // http://eslint.org/docs/rules/no-proto + 'no-redeclare': 2, // http://eslint.org/docs/rules/no-redeclare + 'no-return-assign': 2, // http://eslint.org/docs/rules/no-return-assign + 'no-script-url': 2, // http://eslint.org/docs/rules/no-script-url + 'no-self-compare': 2, // http://eslint.org/docs/rules/no-self-compare + 'no-sequences': 2, // http://eslint.org/docs/rules/no-sequences + 'no-throw-literal': 2, // http://eslint.org/docs/rules/no-throw-literal + 'no-with': 2, // http://eslint.org/docs/rules/no-with + 'radix': 2, // http://eslint.org/docs/rules/radix + 'vars-on-top': 2, // http://eslint.org/docs/rules/vars-on-top + 'wrap-iife': [2, 'any'], // http://eslint.org/docs/rules/wrap-iife + 'yoda': 2, // http://eslint.org/docs/rules/yoda + +/** + * Style + */ + 'indent': [2, 2], // http://eslint.org/docs/rules/indent + 'brace-style': [2, // http://eslint.org/docs/rules/brace-style + '1tbs', { + 'allowSingleLine': true + }], + 'quotes': [ + 2, 'single', 'avoid-escape' // http://eslint.org/docs/rules/quotes + ], + 'camelcase': [2, { // http://eslint.org/docs/rules/camelcase + 'properties': 'never' + }], + 'comma-spacing': [2, { // http://eslint.org/docs/rules/comma-spacing + 'before': false, + 'after': true + }], + 'comma-style': [2, 'last'], // http://eslint.org/docs/rules/comma-style + 'eol-last': 2, // http://eslint.org/docs/rules/eol-last + 'func-names': 1, // http://eslint.org/docs/rules/func-names + 'key-spacing': [2, { // http://eslint.org/docs/rules/key-spacing + 'beforeColon': false, + 'afterColon': true + }], + 'new-cap': [2, { // http://eslint.org/docs/rules/new-cap + 'newIsCap': true + }], + 'no-multiple-empty-lines': [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines + 'max': 2 + }], + 'no-nested-ternary': 2, // http://eslint.org/docs/rules/no-nested-ternary + 'no-new-object': 2, // http://eslint.org/docs/rules/no-new-object + 'no-spaced-func': 2, // http://eslint.org/docs/rules/no-spaced-func + 'no-trailing-spaces': 2, // http://eslint.org/docs/rules/no-trailing-spaces + 'no-extra-parens': [2, 'functions'], // http://eslint.org/docs/rules/no-extra-parens + 'no-underscore-dangle': 0, // http://eslint.org/docs/rules/no-underscore-dangle + 'one-var': [2, 'never'], // http://eslint.org/docs/rules/one-var + 'padded-blocks': [2, 'never'], // http://eslint.org/docs/rules/padded-blocks + 'semi': [2, 'always'], // http://eslint.org/docs/rules/semi + 'semi-spacing': [2, { // http://eslint.org/docs/rules/semi-spacing + 'before': false, + 'after': true + }], + 'space-after-keywords': 2, // http://eslint.org/docs/rules/space-after-keywords + 'space-before-blocks': 2, // http://eslint.org/docs/rules/space-before-blocks + 'space-before-function-paren': [2, 'never'], // http://eslint.org/docs/rules/space-before-function-paren + 'space-infix-ops': 2, // http://eslint.org/docs/rules/space-infix-ops + 'space-return-throw-case': 2, // http://eslint.org/docs/rules/space-return-throw-case + 'spaced-comment': [2, 'always', {// http://eslint.org/docs/rules/spaced-comment + 'exceptions': ['-', '+'], + 'markers': ['=', '!'] // space here to support sprockets directives + }], + } +}; diff --git a/packages/eslint-config-airbnb/index.js b/packages/eslint-config-airbnb/index.js index 0d87e4ad..e74501fc 100644 --- a/packages/eslint-config-airbnb/index.js +++ b/packages/eslint-config-airbnb/index.js @@ -1,221 +1,13 @@ -module.exports = { - 'parser': 'babel-eslint', // https://github.com/babel/babel-eslint - 'plugins': [ - 'react' // https://github.com/yannickcr/eslint-plugin-react - ], - 'env': { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments - 'browser': true, // browser global variables - 'node': true // Node.js global variables and Node.js-specific rules - }, - 'ecmaFeatures': { - 'arrowFunctions': true, - 'blockBindings': true, - 'classes': true, - 'defaultParams': true, - 'destructuring': true, - 'forOf': true, - 'generators': false, - 'modules': true, - 'objectLiteralComputedProperties': true, - 'objectLiteralDuplicateProperties': false, - 'objectLiteralShorthandMethods': true, - 'objectLiteralShorthandProperties': true, - 'spread': true, - 'superInFunctions': true, - 'templateStrings': true, - 'jsx': true - }, - 'rules': { -/** - * Strict mode - */ - // babel inserts 'use strict'; for us - 'strict': [2, 'never'], // http://eslint.org/docs/rules/strict +var reactRules = require('./react'); +var base = require('./base'); -/** - * ES6 - */ - 'no-var': 2, // http://eslint.org/docs/rules/no-var - 'prefer-const': 2, // http://eslint.org/docs/rules/prefer-const +// clone this so we aren't mutating a module +var eslintrc = JSON.parse(JSON.stringify(base)); -/** - * Variables - */ - 'no-shadow': 2, // http://eslint.org/docs/rules/no-shadow - 'no-shadow-restricted-names': 2, // http://eslint.org/docs/rules/no-shadow-restricted-names - 'no-unused-vars': [2, { // http://eslint.org/docs/rules/no-unused-vars - 'vars': 'local', - 'args': 'after-used' - }], - 'no-use-before-define': 2, // http://eslint.org/docs/rules/no-use-before-define +// manually merge in React rules +eslintrc.plugins = reactRules.plugins; +Object.keys(reactRules.rules).forEach(function(ruleId) { + eslintrc.rules[ruleId] = reactRules.rules[ruleId]; +}); -/** - * Possible errors - */ - 'comma-dangle': [2, 'always-multiline'], // http://eslint.org/docs/rules/comma-dangle - 'no-cond-assign': [2, 'always'], // http://eslint.org/docs/rules/no-cond-assign - 'no-console': 1, // http://eslint.org/docs/rules/no-console - 'no-debugger': 1, // http://eslint.org/docs/rules/no-debugger - 'no-alert': 1, // http://eslint.org/docs/rules/no-alert - 'no-constant-condition': 1, // http://eslint.org/docs/rules/no-constant-condition - 'no-dupe-keys': 2, // http://eslint.org/docs/rules/no-dupe-keys - 'no-duplicate-case': 2, // http://eslint.org/docs/rules/no-duplicate-case - 'no-empty': 2, // http://eslint.org/docs/rules/no-empty - 'no-ex-assign': 2, // http://eslint.org/docs/rules/no-ex-assign - 'no-extra-boolean-cast': 0, // http://eslint.org/docs/rules/no-extra-boolean-cast - 'no-extra-semi': 2, // http://eslint.org/docs/rules/no-extra-semi - 'no-func-assign': 2, // http://eslint.org/docs/rules/no-func-assign - 'no-inner-declarations': 2, // http://eslint.org/docs/rules/no-inner-declarations - 'no-invalid-regexp': 2, // http://eslint.org/docs/rules/no-invalid-regexp - 'no-irregular-whitespace': 2, // http://eslint.org/docs/rules/no-irregular-whitespace - 'no-obj-calls': 2, // http://eslint.org/docs/rules/no-obj-calls - 'no-sparse-arrays': 2, // http://eslint.org/docs/rules/no-sparse-arrays - 'no-unreachable': 2, // http://eslint.org/docs/rules/no-unreachable - 'use-isnan': 2, // http://eslint.org/docs/rules/use-isnan - 'block-scoped-var': 2, // http://eslint.org/docs/rules/block-scoped-var - -/** - * Best practices - */ - 'consistent-return': 2, // http://eslint.org/docs/rules/consistent-return - 'curly': [2, 'multi-line'], // http://eslint.org/docs/rules/curly - 'default-case': 2, // http://eslint.org/docs/rules/default-case - 'dot-notation': [2, { // http://eslint.org/docs/rules/dot-notation - 'allowKeywords': true - }], - 'eqeqeq': 2, // http://eslint.org/docs/rules/eqeqeq - 'guard-for-in': 2, // http://eslint.org/docs/rules/guard-for-in - 'no-caller': 2, // http://eslint.org/docs/rules/no-caller - 'no-else-return': 2, // http://eslint.org/docs/rules/no-else-return - 'no-eq-null': 2, // http://eslint.org/docs/rules/no-eq-null - 'no-eval': 2, // http://eslint.org/docs/rules/no-eval - 'no-extend-native': 2, // http://eslint.org/docs/rules/no-extend-native - 'no-extra-bind': 2, // http://eslint.org/docs/rules/no-extra-bind - 'no-fallthrough': 2, // http://eslint.org/docs/rules/no-fallthrough - 'no-floating-decimal': 2, // http://eslint.org/docs/rules/no-floating-decimal - 'no-implied-eval': 2, // http://eslint.org/docs/rules/no-implied-eval - 'no-lone-blocks': 2, // http://eslint.org/docs/rules/no-lone-blocks - 'no-loop-func': 2, // http://eslint.org/docs/rules/no-loop-func - 'no-multi-str': 2, // http://eslint.org/docs/rules/no-multi-str - 'no-native-reassign': 2, // http://eslint.org/docs/rules/no-native-reassign - 'no-new': 2, // http://eslint.org/docs/rules/no-new - 'no-new-func': 2, // http://eslint.org/docs/rules/no-new-func - 'no-new-wrappers': 2, // http://eslint.org/docs/rules/no-new-wrappers - 'no-octal': 2, // http://eslint.org/docs/rules/no-octal - 'no-octal-escape': 2, // http://eslint.org/docs/rules/no-octal-escape - 'no-param-reassign': 2, // http://eslint.org/docs/rules/no-param-reassign - 'no-proto': 2, // http://eslint.org/docs/rules/no-proto - 'no-redeclare': 2, // http://eslint.org/docs/rules/no-redeclare - 'no-return-assign': 2, // http://eslint.org/docs/rules/no-return-assign - 'no-script-url': 2, // http://eslint.org/docs/rules/no-script-url - 'no-self-compare': 2, // http://eslint.org/docs/rules/no-self-compare - 'no-sequences': 2, // http://eslint.org/docs/rules/no-sequences - 'no-throw-literal': 2, // http://eslint.org/docs/rules/no-throw-literal - 'no-with': 2, // http://eslint.org/docs/rules/no-with - 'radix': 2, // http://eslint.org/docs/rules/radix - 'vars-on-top': 2, // http://eslint.org/docs/rules/vars-on-top - 'wrap-iife': [2, 'any'], // http://eslint.org/docs/rules/wrap-iife - 'yoda': 2, // http://eslint.org/docs/rules/yoda - -/** - * Style - */ - 'indent': [2, 2], // http://eslint.org/docs/rules/indent - 'brace-style': [2, // http://eslint.org/docs/rules/brace-style - '1tbs', { - 'allowSingleLine': true - }], - 'quotes': [ - 2, 'single', 'avoid-escape' // http://eslint.org/docs/rules/quotes - ], - 'camelcase': [2, { // http://eslint.org/docs/rules/camelcase - 'properties': 'never' - }], - 'comma-spacing': [2, { // http://eslint.org/docs/rules/comma-spacing - 'before': false, - 'after': true - }], - 'comma-style': [2, 'last'], // http://eslint.org/docs/rules/comma-style - 'eol-last': 2, // http://eslint.org/docs/rules/eol-last - 'func-names': 1, // http://eslint.org/docs/rules/func-names - 'key-spacing': [2, { // http://eslint.org/docs/rules/key-spacing - 'beforeColon': false, - 'afterColon': true - }], - 'new-cap': [2, { // http://eslint.org/docs/rules/new-cap - 'newIsCap': true - }], - 'no-multiple-empty-lines': [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines - 'max': 2 - }], - 'no-nested-ternary': 2, // http://eslint.org/docs/rules/no-nested-ternary - 'no-new-object': 2, // http://eslint.org/docs/rules/no-new-object - 'no-spaced-func': 2, // http://eslint.org/docs/rules/no-spaced-func - 'no-trailing-spaces': 2, // http://eslint.org/docs/rules/no-trailing-spaces - 'no-extra-parens': [2, 'functions'], // http://eslint.org/docs/rules/no-extra-parens - 'no-underscore-dangle': 0, // http://eslint.org/docs/rules/no-underscore-dangle - 'one-var': [2, 'never'], // http://eslint.org/docs/rules/one-var - 'padded-blocks': [2, 'never'], // http://eslint.org/docs/rules/padded-blocks - 'semi': [2, 'always'], // http://eslint.org/docs/rules/semi - 'semi-spacing': [2, { // http://eslint.org/docs/rules/semi-spacing - 'before': false, - 'after': true - }], - 'space-after-keywords': 2, // http://eslint.org/docs/rules/space-after-keywords - 'space-before-blocks': 2, // http://eslint.org/docs/rules/space-before-blocks - 'space-before-function-paren': [2, 'never'], // http://eslint.org/docs/rules/space-before-function-paren - 'space-infix-ops': 2, // http://eslint.org/docs/rules/space-infix-ops - 'space-return-throw-case': 2, // http://eslint.org/docs/rules/space-return-throw-case - 'spaced-comment': [2, 'always', {// http://eslint.org/docs/rules/spaced-comment - 'exceptions': ['-', '+'], - 'markers': ['=', '!'] // space here to support sprockets directives - }], - -/** - * JSX style - */ - 'react/display-name': 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md - 'react/jsx-boolean-value': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md - 'react/jsx-quotes': [2, 'double'], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-quotes.md - 'react/jsx-no-undef': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md - 'react/jsx-sort-props': 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md - 'react/jsx-sort-prop-types': 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-prop-types.md - 'react/jsx-uses-react': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md - 'react/jsx-uses-vars': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md - 'react/no-did-mount-set-state': [2, 'allow-in-func'], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md - 'react/no-did-update-set-state': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md - 'react/no-multi-comp': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md - 'react/no-unknown-property': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md - 'react/prop-types': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md - 'react/react-in-jsx-scope': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md - 'react/self-closing-comp': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md - 'react/wrap-multilines': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md - 'react/sort-comp': [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md - 'order': [ - 'displayName', - 'propTypes', - 'contextTypes', - 'childContextTypes', - 'mixins', - 'statics', - 'defaultProps', - 'constructor', - 'getDefaultProps', - 'getInitialState', - 'getChildContext', - 'componentWillMount', - 'componentDidMount', - 'componentWillReceiveProps', - 'shouldComponentUpdate', - 'componentWillUpdate', - 'componentDidUpdate', - 'componentWillUnmount', - '/^on.+$/', - 'everything-else', - '/^get.+$/', - '/^render.+$/', - 'render' - ] - }] - } -}; +module.exports = eslintrc; diff --git a/packages/eslint-config-airbnb/package.json b/packages/eslint-config-airbnb/package.json index a0825185..3af4564f 100644 --- a/packages/eslint-config-airbnb/package.json +++ b/packages/eslint-config-airbnb/package.json @@ -4,7 +4,7 @@ "description": "Airbnb's ESLint config, following our styleguide", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "./node_modules/.bin/babel-tape-runner ./test/test-*.js" }, "repository": { "type": "git", @@ -23,5 +23,13 @@ "bugs": { "url": "https://github.com/airbnb/javascript/issues" }, - "homepage": "https://github.com/airbnb/javascript" + "homepage": "https://github.com/airbnb/javascript", + "devDependencies": { + "babel-eslint": "4.0.10", + "babel-tape-runner": "1.2.0", + "eslint": "1.1.0", + "eslint-plugin-react": "3.2.3", + "react": "0.13.3", + "tape": "4.2.0" + } } diff --git a/packages/eslint-config-airbnb/react.js b/packages/eslint-config-airbnb/react.js new file mode 100644 index 00000000..a0ab17f3 --- /dev/null +++ b/packages/eslint-config-airbnb/react.js @@ -0,0 +1,36 @@ +module.exports = { + 'plugins': [ + 'react' // https://github.com/yannickcr/eslint-plugin-react + ], + rules: { + /** + * JSX style + */ + 'react/display-name': 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md + 'react/jsx-boolean-value': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md + 'react/jsx-quotes': [2, 'double'], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-quotes.md + 'react/jsx-no-undef': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md + 'react/jsx-sort-props': 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md + 'react/jsx-sort-prop-types': 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-prop-types.md + 'react/jsx-uses-react': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md + 'react/jsx-uses-vars': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md + 'react/no-did-mount-set-state': [2, 'allow-in-func'], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md + 'react/no-did-update-set-state': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md + 'react/no-multi-comp': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md + 'react/no-unknown-property': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md + 'react/prop-types': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md + 'react/react-in-jsx-scope': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md + 'react/self-closing-comp': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md + 'react/wrap-multilines': 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md + 'react/sort-comp': [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md + 'order': [ + 'lifecycle', + '/^on.+$/', + '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', + 'everything-else', + '/^render.+$/', + 'render' + ] + }] + } +}; diff --git a/packages/eslint-config-airbnb/test/test-base.js b/packages/eslint-config-airbnb/test/test-base.js new file mode 100644 index 00000000..ecf06cdf --- /dev/null +++ b/packages/eslint-config-airbnb/test/test-base.js @@ -0,0 +1,13 @@ +import test from 'tape'; +import base from '../base'; + +test('base: does not reference react', t => { + t.plan(2); + + t.notOk(base.plugins, 'plugins is unspecified'); + + // scan rules for react/ and fail if any exist + const reactRuleIds = Object.keys(base.rules) + .filter(ruleId => ruleId.indexOf('react/') === 0); + t.deepEquals(reactRuleIds, [], 'there are no react/ rules'); +}); diff --git a/packages/eslint-config-airbnb/test/test-react-order.js b/packages/eslint-config-airbnb/test/test-react-order.js new file mode 100644 index 00000000..83df3b1b --- /dev/null +++ b/packages/eslint-config-airbnb/test/test-react-order.js @@ -0,0 +1,83 @@ +import test from 'tape'; +import { CLIEngine, linter } from 'eslint'; +import eslintrc from '../'; + +const cli = new CLIEngine({ + useEslintrc: false, + baseConfig: eslintrc, +}); + +function lint(text) { + // @see http://eslint.org/docs/developer-guide/nodejs-api.html#executeonfiles + // @see http://eslint.org/docs/developer-guide/nodejs-api.html#executeontext + return cli.executeOnText(text).results[0]; +} + +function wrapComponent(body) { + return ` +import React from 'react'; +export default class MyComponent extends React.Component { +${body} +} +`; +} + +test('validate react prop order', t => { + t.test('make sure our eslintrc has React linting dependencies', t => { + t.plan(2); + t.equal(eslintrc.parser, 'babel-eslint', 'uses babel-eslint'); + t.equal(eslintrc.plugins[0], 'react', 'uses eslint-plugin-react'); + }); + + t.test('passes a good component', t => { + t.plan(3); + const result = lint(wrapComponent(` + componentWillMount() { } + componentDidMount() { } + setFoo() { } + getFoo() { } + setBar() { } + someMethod() { } + renderDogs() { } + render() { return
; } +`)); + + t.notOk(result.warningCount, 'no warnings'); + t.notOk(result.errorCount, 'no errors'); + t.deepEquals(result.messages, [], 'no messages in results'); + }); + + t.test('order: when random method is first', t => { + t.plan(2); + const result = lint(wrapComponent(` + someMethod() { } + componentWillMount() { } + componentDidMount() { } + setFoo() { } + getFoo() { } + setBar() { } + renderDogs() { } + render() { return
; } +`)); + + t.ok(result.errorCount, 'fails'); + t.equal(result.messages[0].ruleId, 'react/sort-comp', 'fails due to sort'); + }); + + t.test('order: when random method after lifecycle methods', t => { + t.plan(2); + const result = lint(wrapComponent(` + componentWillMount() { } + componentDidMount() { } + someMethod() { } + setFoo() { } + getFoo() { } + setBar() { } + renderDogs() { } + render() { return
; } +`)); + + t.ok(result.errorCount, 'fails'); + t.equal(result.messages[0].ruleId, 'react/sort-comp', 'fails due to sort'); + }); +}); From 7646a8619bf7546a05a33556404060869e64ac16 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Wed, 19 Aug 2015 13:42:10 -0700 Subject: [PATCH 2/3] lint eslint-config-airbnb with eslint-config-airbnb/base --- packages/eslint-config-airbnb/.eslintrc | 11 +++++ packages/eslint-config-airbnb/base/index.js | 48 ++++++++++--------- packages/eslint-config-airbnb/index.js | 8 ++-- .../node_modules/eslint-config-airbnb | 1 + packages/eslint-config-airbnb/package.json | 1 + .../test/test-react-order.js | 2 +- 6 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 packages/eslint-config-airbnb/.eslintrc create mode 120000 packages/eslint-config-airbnb/node_modules/eslint-config-airbnb diff --git a/packages/eslint-config-airbnb/.eslintrc b/packages/eslint-config-airbnb/.eslintrc new file mode 100644 index 00000000..cbf450f0 --- /dev/null +++ b/packages/eslint-config-airbnb/.eslintrc @@ -0,0 +1,11 @@ +{ + "extends": "airbnb", + "rules": { + // disable requiring trailing commas because it might be nice to revert to + // being JSON at some point, and I don't want to make big changes now. + "comma-dangle": 0, + // disabled because I find it tedious to write tests while following this + // rule + "no-shadow": 0 + } +} diff --git a/packages/eslint-config-airbnb/base/index.js b/packages/eslint-config-airbnb/base/index.js index c47c2c45..65174d85 100644 --- a/packages/eslint-config-airbnb/base/index.js +++ b/packages/eslint-config-airbnb/base/index.js @@ -23,21 +23,21 @@ module.exports = { 'jsx': true }, 'rules': { -/** - * Strict mode - */ + /** + * Strict mode + */ // babel inserts 'use strict'; for us 'strict': [2, 'never'], // http://eslint.org/docs/rules/strict -/** - * ES6 - */ + /** + * ES6 + */ 'no-var': 2, // http://eslint.org/docs/rules/no-var 'prefer-const': 2, // http://eslint.org/docs/rules/prefer-const -/** - * Variables - */ + /** + * Variables + */ 'no-shadow': 2, // http://eslint.org/docs/rules/no-shadow 'no-shadow-restricted-names': 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 'no-unused-vars': [2, { // http://eslint.org/docs/rules/no-unused-vars @@ -46,9 +46,9 @@ module.exports = { }], 'no-use-before-define': 2, // http://eslint.org/docs/rules/no-use-before-define -/** - * Possible errors - */ + /** + * Possible errors + */ 'comma-dangle': [2, 'always-multiline'], // http://eslint.org/docs/rules/comma-dangle 'no-cond-assign': [2, 'always'], // http://eslint.org/docs/rules/no-cond-assign 'no-console': 1, // http://eslint.org/docs/rules/no-console @@ -71,9 +71,9 @@ module.exports = { 'use-isnan': 2, // http://eslint.org/docs/rules/use-isnan 'block-scoped-var': 2, // http://eslint.org/docs/rules/block-scoped-var -/** - * Best practices - */ + /** + * Best practices + */ 'consistent-return': 2, // http://eslint.org/docs/rules/consistent-return 'curly': [2, 'multi-line'], // http://eslint.org/docs/rules/curly 'default-case': 2, // http://eslint.org/docs/rules/default-case @@ -114,14 +114,16 @@ module.exports = { 'wrap-iife': [2, 'any'], // http://eslint.org/docs/rules/wrap-iife 'yoda': 2, // http://eslint.org/docs/rules/yoda -/** - * Style - */ + /** + * Style + */ 'indent': [2, 2], // http://eslint.org/docs/rules/indent - 'brace-style': [2, // http://eslint.org/docs/rules/brace-style + 'brace-style': [ + 2, // http://eslint.org/docs/rules/brace-style '1tbs', { - 'allowSingleLine': true - }], + 'allowSingleLine': true + } + ], 'quotes': [ 2, 'single', 'avoid-escape' // http://eslint.org/docs/rules/quotes ], @@ -136,8 +138,8 @@ module.exports = { 'eol-last': 2, // http://eslint.org/docs/rules/eol-last 'func-names': 1, // http://eslint.org/docs/rules/func-names 'key-spacing': [2, { // http://eslint.org/docs/rules/key-spacing - 'beforeColon': false, - 'afterColon': true + 'beforeColon': false, + 'afterColon': true }], 'new-cap': [2, { // http://eslint.org/docs/rules/new-cap 'newIsCap': true diff --git a/packages/eslint-config-airbnb/index.js b/packages/eslint-config-airbnb/index.js index e74501fc..3cda9b0f 100644 --- a/packages/eslint-config-airbnb/index.js +++ b/packages/eslint-config-airbnb/index.js @@ -1,12 +1,12 @@ -var reactRules = require('./react'); -var base = require('./base'); +const reactRules = require('./react'); +const base = require('./base'); // clone this so we aren't mutating a module -var eslintrc = JSON.parse(JSON.stringify(base)); +const eslintrc = JSON.parse(JSON.stringify(base)); // manually merge in React rules eslintrc.plugins = reactRules.plugins; -Object.keys(reactRules.rules).forEach(function(ruleId) { +Object.keys(reactRules.rules).forEach(function assignRule(ruleId) { eslintrc.rules[ruleId] = reactRules.rules[ruleId]; }); diff --git a/packages/eslint-config-airbnb/node_modules/eslint-config-airbnb b/packages/eslint-config-airbnb/node_modules/eslint-config-airbnb new file mode 120000 index 00000000..a96aa0ea --- /dev/null +++ b/packages/eslint-config-airbnb/node_modules/eslint-config-airbnb @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/packages/eslint-config-airbnb/package.json b/packages/eslint-config-airbnb/package.json index 3af4564f..f277fa24 100644 --- a/packages/eslint-config-airbnb/package.json +++ b/packages/eslint-config-airbnb/package.json @@ -4,6 +4,7 @@ "description": "Airbnb's ESLint config, following our styleguide", "main": "index.js", "scripts": { + "lint": "./node_modules/.bin/eslint .", "test": "./node_modules/.bin/babel-tape-runner ./test/test-*.js" }, "repository": { diff --git a/packages/eslint-config-airbnb/test/test-react-order.js b/packages/eslint-config-airbnb/test/test-react-order.js index 83df3b1b..9ef23f67 100644 --- a/packages/eslint-config-airbnb/test/test-react-order.js +++ b/packages/eslint-config-airbnb/test/test-react-order.js @@ -1,5 +1,5 @@ import test from 'tape'; -import { CLIEngine, linter } from 'eslint'; +import { CLIEngine } from 'eslint'; import eslintrc from '../'; const cli = new CLIEngine({ From 2d90ebf11b06788f7d686a183182eeacaa767a5c Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Wed, 19 Aug 2015 14:01:14 -0700 Subject: [PATCH 3/3] update README with new package structure --- packages/eslint-config-airbnb/README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/eslint-config-airbnb/README.md b/packages/eslint-config-airbnb/README.md index 6edb34f3..3bc20dc4 100644 --- a/packages/eslint-config-airbnb/README.md +++ b/packages/eslint-config-airbnb/README.md @@ -4,9 +4,26 @@ This package provides Airbnb's .eslintrc as an extensible shared config. ## Usage +### With React Style + 1. `npm install --save-dev eslint-config-airbnb babel-eslint eslint-plugin-react` -2. add `"extends": "eslint-config-airbnb"` to your .eslintrc +2. add `"extends": "airbnb"` to your .eslintrc + +### Without React Style + +1. `npm install --save-dev eslint-config-airbnb babel-eslint ` +2. add `"extends": "airbnb/base"` to your .eslintrc See [Airbnb's Javascript styleguide](https://github.com/airbnb/javascript) and the [ESlint config docs](http://eslint.org/docs/user-guide/configuring#extending-configuration-files) for more information. + +## Improving this config + +Consider adding test cases if you're making complicated rules changes, like +anything involving regexes. Perhaps in a distant future, we could use literate +programming to structure our README as test cases for our .eslintrc? + +You can run tests with `npm test`. + +You can make sure this module lints with itself using `npm run lint`.