diff --git a/README.md b/README.md index 4fb82cd7cb5..437897157f2 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Currently it is a thin layer on top of many amazing community projects, such as: * [webpack](https://webpack.github.io/) with [webpack-dev-server](https://github.com/webpack/webpack-dev-server), [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin) and [style-loader](https://github.com/webpack/style-loader) * [Babel](http://babeljs.io/) with [preset-es2015](https://www.npmjs.com/package/babel-preset-es2015), [preset-es2016](https://www.npmjs.com/package/babel-preset-es2016), [preset-react](https://www.npmjs.com/package/babel-preset-react) and [transform-rest-spread](https://babeljs.io/docs/plugins/transform-object-rest-spread/) * [Autoprefixer](https://github.com/postcss/autoprefixer) -* [ESLint](http://eslint.org/) with [eslint-config-airbnb](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) +* [ESLint](http://eslint.org/) * and more. All of them are transient dependencies of the provided npm package. diff --git a/config/.eslintrc b/config/.eslintrc deleted file mode 100644 index b0c0c8bdac9..00000000000 --- a/config/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "airbnb" -} diff --git a/config/babel.dev.js b/config/babel.dev.js new file mode 100644 index 00000000000..746b215ea0a --- /dev/null +++ b/config/babel.dev.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +module.exports = { + cacheDirectory: true, + presets: ['es2015', 'es2016', 'react'], + plugins: ['transform-object-rest-spread'] +}; diff --git a/config/babel.prod.js b/config/babel.prod.js new file mode 100644 index 00000000000..ad4f120d19a --- /dev/null +++ b/config/babel.prod.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +module.exports = { + presets: ['es2015', 'es2016', 'react'], + plugins: [ + 'transform-object-rest-spread', + 'transform-react-constant-elements' + ] +}; diff --git a/config/eslint.js b/config/eslint.js new file mode 100644 index 00000000000..580893f5ef4 --- /dev/null +++ b/config/eslint.js @@ -0,0 +1,265 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +// Inspired by https://github.com/airbnb/javascript but less opinionated. + +var OFF = 0; // rules that split the community (e.g. semicolons) +var WARNING = 1; // style rules accepted by the majority of popular styleguides +var ERROR = 2; // rules that prevent common mistakes + +module.exports = { + root: true, + + plugins: ['react', 'import'], + + env: { + es6: true, + commonjs: true, + browser: true + }, + + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + generators: true, + experimentalObjectRestSpread: true + } + }, + + settings: { + 'import/ignore': [ + 'node_modules', + '\\.(json|css|jpg|png|gif|eot|svg|ttf|woff|woff2|mp4|webm)$', + ], + 'import/extensions': ['.js'], + 'import/resolver': { + node: { + extensions: ['.js', '.json'] + } + } + }, + + rules: { + // http://eslint.org/docs/rules/ + 'array-callback-return': ERROR, + 'arrow-spacing': [WARNING, { before: true, after: true }], + 'block-scoped-var': WARNING, + 'block-spacing': [WARNING, 'always'], + 'brace-style': [WARNING, '1tbs', { allowSingleLine: true }], + 'comma-spacing': [WARNING, { before: false, after: true }], + 'comma-style': [WARNING, 'last'], + 'computed-property-spacing': [WARNING, 'never'], + curly: [WARNING, 'multi-line'], + 'default-case': [ERROR, { commentPattern: '^no default$' }], + 'dot-notation': [WARNING, { allowKeywords: true }], + 'dot-location': [ERROR, 'property'], + 'eol-last': WARNING, + eqeqeq: [ERROR, 'allow-null'], + 'generator-star-spacing': [WARNING, { before: false, after: true }], + 'guard-for-in': ERROR, + 'key-spacing': [WARNING, { beforeColon: false, afterColon: true }], + 'keyword-spacing': [WARNING, { + before: true, + after: true, + overrides: { + return: { after: true }, + throw: { after: true }, + case: { after: true } + } + }], + 'linebreak-style': [WARNING, 'unix'], + 'new-cap': [ERROR, { newIsCap: true }], + 'new-parens': ERROR, + 'newline-per-chained-call': [WARNING, { ignoreChainWithDepth: 4 }], + 'no-array-constructor': ERROR, + 'no-caller': ERROR, + 'no-case-declarations': WARNING, + 'no-cond-assign': [ERROR, 'always'], + 'no-confusing-arrow': [WARNING, { + allowParens: true, + }], + 'no-console': OFF, // TODO: enable for production? + 'no-constant-condition': WARNING, + 'no-const-assign': ERROR, + 'no-control-regex': ERROR, + 'no-debugger': WARNING, // TODO: enable for production? + 'no-delete-var': ERROR, + 'no-dupe-args': ERROR, + 'no-dupe-class-members': ERROR, + 'no-dupe-keys': ERROR, + 'no-duplicate-case': ERROR, + 'no-duplicate-imports': WARNING, + 'no-empty': [WARNING, { + allowEmptyCatch: true + }], + 'no-empty-character-class': ERROR, + 'no-empty-pattern': ERROR, + 'no-eval': ERROR, + 'no-ex-assign': ERROR, + 'no-extend-native': ERROR, + 'no-extra-bind': ERROR, + 'no-extra-boolean-cast': WARNING, + 'no-extra-label': ERROR, + 'no-extra-semi': WARNING, + 'no-fallthrough': ERROR, + 'no-func-assign': ERROR, + 'no-implied-eval': ERROR, + 'no-invalid-this': WARNING, + 'no-invalid-regexp': ERROR, + 'no-irregular-whitespace': WARNING, + 'no-iterator': ERROR, + 'no-label-var': ERROR, + 'no-labels': [ERROR, { allowLoop: false, allowSwitch: false }], + 'no-lone-blocks': ERROR, + 'no-loop-func': ERROR, + 'no-mixed-operators': [ERROR, { + groups: [ + ['+', '-', '*', '/', '%', '**'], + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'] + ], + allowSamePrecedence: false + }], + 'no-multi-spaces': WARNING, + 'no-multi-str': ERROR, + 'no-multiple-empty-lines': [WARNING, { max: 2, maxEOF: 1 }], + 'no-native-reassign': ERROR, + 'no-negated-in-lhs': ERROR, + 'no-nested-ternary': WARNING, + 'no-new': ERROR, + 'no-new-func': ERROR, + 'no-new-object': ERROR, + 'no-new-symbol': ERROR, + 'no-new-wrappers': ERROR, + 'no-obj-calls': ERROR, + 'no-octal': ERROR, + 'no-octal-escape': ERROR, + 'no-prototype-builtins': WARNING, + 'no-redeclare': ERROR, + 'no-regex-spaces': ERROR, + 'no-restricted-syntax': [ + ERROR, + 'LabeledStatement', + 'WithStatement', + ], + 'no-return-assign': ERROR, + 'no-script-url': ERROR, + 'no-self-assign': ERROR, + 'no-self-compare': ERROR, + 'no-sequences': ERROR, + 'no-shadow': WARNING, + 'no-shadow-restricted-names': ERROR, + 'no-spaced-func': WARNING, + 'no-sparse-arrays': ERROR, + 'no-this-before-super': ERROR, + 'no-throw-literal': ERROR, + 'no-trailing-spaces': WARNING, + 'no-undef': ERROR, + 'no-unexpected-multiline': ERROR, + 'no-unneeded-ternary': [WARNING, { defaultAssignment: false }], + 'no-unreachable': ERROR, + 'no-unsafe-finally': WARNING, + 'no-unused-expressions': ERROR, + 'no-unused-labels': ERROR, + 'no-unused-vars': [ERROR, { vars: 'local', args: 'after-used' }], + 'no-use-before-define': [ERROR, 'nofunc'], + 'no-useless-computed-key': ERROR, + 'no-useless-concat': ERROR, + 'no-useless-constructor': ERROR, + 'no-useless-escape': ERROR, + 'no-useless-rename': [ERROR, { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }], + 'no-var': WARNING, + 'no-with': ERROR, + 'no-whitespace-before-property': ERROR, + 'object-property-newline': [WARNING, { + allowMultiplePropertiesPerLine: true, + }], + 'operator-assignment': [ERROR, 'always'], + radix: ERROR, + 'require-yield': ERROR, + 'rest-spread-spacing': [ERROR, 'never'], + 'semi-spacing': [WARNING, { before: false, after: true }], + 'space-before-blocks': WARNING, + 'space-before-function-paren': [WARNING, { anonymous: 'always', named: 'never' }], + 'space-in-parens': [WARNING, 'never'], + 'space-infix-ops': WARNING, + 'space-unary-ops': [WARNING, { + words: true, + nonwords: false, + overrides: {}, + }], + 'spaced-comment': [WARNING, 'always', { + exceptions: ['-', '+'], + markers: ['=', '!'] + }], + strict: [ERROR, 'never'], + 'template-curly-spacing': WARNING, + 'unicode-bom': [ERROR, 'never'], + 'use-isnan': ERROR, + 'valid-typeof': ERROR, + 'yield-star-spacing': [WARNING, 'after'], + yoda: WARNING, + + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/ + + 'import/default': ERROR, + 'import/export': ERROR, + 'import/imports-first': WARNING, + 'import/named': ERROR, + 'import/namespace': ERROR, + 'import/no-amd': ERROR, + 'import/no-commonjs': WARNING, + 'import/no-duplicates': ERROR, + 'import/no-extraneous-dependencies': ERROR, + 'import/no-named-as-default': ERROR, + 'import/no-named-as-default-member': ERROR, + 'import/no-unresolved': [ERROR, { commonjs: true }], + + // https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules + 'react/jsx-equals-spacing': [ERROR, 'never'], + 'react/jsx-handler-names': [WARNING, { + eventHandlerPrefix: 'handle', + eventHandlerPropPrefix: 'on', + }], + 'react/jsx-key': WARNING, + 'react/jsx-no-duplicate-props': [ERROR, { ignoreCase: true }], + 'react/jsx-no-undef': ERROR, + 'react/jsx-pascal-case': [ERROR, { + allowAllCaps: true, + ignore: [], + }], + 'react/jsx-space-before-closing': WARNING, + 'react/jsx-uses-react': [ERROR, { pragma: 'React' }], + 'react/jsx-uses-vars': ERROR, + 'react/no-deprecated': ERROR, + 'react/no-did-mount-set-state': [WARNING, 'allow-in-func'], + 'react/no-did-update-set-state': [WARNING, 'allow-in-func'], + 'react/no-direct-mutation-state': ERROR, + 'react/no-is-mounted': ERROR, + 'react/no-multi-comp': [WARNING, { ignoreStateless: true }], + 'react/no-string-refs': WARNING, + 'react/prefer-es6-class': OFF, // TODO: revisit after updating docs + 'react/prefer-stateless-function': OFF, // TODO: revisit after updating docs + 'react/react-in-jsx-scope': ERROR, + 'react/require-render-return': ERROR, + 'react/wrap-multilines': [WARNING, { + declaration: true, + assignment: true, + return: true + }] + } +}; diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index a57f226a867..18420f7b7e9 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -19,8 +19,8 @@ var relative = isInNodeModules ? '../../..' : '..'; module.exports = { devtool: 'eval', entry: [ - './src/index.js', - 'webpack-dev-server/client?http://localhost:3000' + 'webpack-dev-server/client?http://localhost:3000', + './src/index.js' ], output: { // Next line is not used in dev but WebpackDevServer crashes without it: @@ -42,11 +42,7 @@ module.exports = { test: /\.js$/, include: path.resolve(__dirname, relative, 'src'), loader: 'babel', - query: { - cacheDirectory: true, - presets: ['es2015', 'es2016', 'react'], - plugins: ['transform-object-rest-spread'] - } + query: require('./babel.dev') }, { test: /\.css$/, @@ -68,7 +64,8 @@ module.exports = { ] }, eslint: { - configFile: path.join(__dirname, '.eslintrc') + configFile: path.join(__dirname, 'eslint.js'), + useEslintrc: false }, postcss: function() { return [autoprefixer]; diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index 0c2e215a589..5eca60c59d4 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -39,13 +39,7 @@ module.exports = { test: /\.js$/, include: path.resolve(__dirname, relative, 'src'), loader: 'babel', - query: { - presets: ['es2015', 'es2016', 'react'], - plugins: [ - 'transform-object-rest-spread', - 'transform-react-constant-elements' - ] - } + query: require('./babel.prod') }, { test: /\.css$/, @@ -67,7 +61,10 @@ module.exports = { ] }, eslint: { - configFile: path.join(__dirname, '.eslintrc') + // TODO: consider separate config for production, + // e.g. to enable no-console and no-debugger only in prod. + configFile: path.join(__dirname, 'eslint.js'), + useEslintrc: false }, postcss: function() { return [autoprefixer]; diff --git a/package.json b/package.json index 4b338be57d2..1379ed15e5c 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,11 @@ "babel-preset-es2015": "^6.9.0", "babel-preset-es2016": "^6.11.3", "babel-preset-react": "^6.11.1", - "css-loader": "^0.23.1", "cross-spawn": "^4.0.0", - "eslint": "^2.13.1", - "eslint-config-airbnb": "^9.0.1", + "css-loader": "^0.23.1", + "eslint": "^3.1.1", "eslint-loader": "^1.4.1", "eslint-plugin-import": "^1.10.3", - "eslint-plugin-jsx-a11y": "^1.5.5", "eslint-plugin-react": "^5.2.2", "file-loader": "^0.9.0", "html-webpack-plugin": "^2.22.0", diff --git a/scripts/eject.js b/scripts/eject.js index 2bb42bc9cfe..7955f15cbcc 100644 --- a/scripts/eject.js +++ b/scripts/eject.js @@ -40,7 +40,9 @@ prompt('Are you sure you want to eject? This action is permanent. [y/N]', functi var hostPath = path.join(selfPath, '..', '..'); var files = [ - path.join('config', '.eslintrc'), + path.join('config', 'babel.dev.js'), + path.join('config', 'babel.prod.js'), + path.join('config', 'eslint.js'), path.join('config', 'webpack.config.dev.js'), path.join('config', 'webpack.config.prod.js'), path.join('scripts', 'build.js'),