diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..972bde9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index 3322b03..365c674 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.eslintcache /node_modules npm-debug.log /package-lock.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..92bed92 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,79 @@ +language: node_js + +git: + # Do not take whole history, but ensure to not break things: + # - Merging multiple PR's around same time may introduce a case where it's not + # the last merge commit that is to be tested + # - Aside of merge commit we need a previous commit to be able to detect a version switch + depth: 10 + +branches: + only: + - master # Do not build PR branches + - /^v\d+\.\d+\.\d+$/ # Ensure to build release tags + +stages: + - name: Test + - name: Deploy + if: tag =~ ^v\d+\.\d+\.\d+$ + +jobs: + include: + # In most cases it's best to configure one job per platform & Node.js version combination + # (job boot & setup takes ca 1 minute, one task run lasts ca few seconds) + + # PR's from branches + # Ensure commit messages follow CC, and confirm on changelog in case of release PR's + - name: 'Prettier check updated, Lint updated, Commitlint, Changelog confirm (on release) - Node.js v12' + if: type = pull_request AND fork = false + node_js: 12 + script: + - | + npm run prettier-check-updated && npm run lint-updated && npm run commitlint-ci-pull-request && + { + # If release PR, confirm we have a changelog + tagName=`git diff master package.json | grep '"version": "' | grep -oE "[0-9]+\.[0-9]+\.[0-9]+"` + if [ $? -eq 0 ]; then + npx dump-release-notes-from-cc-changelog $tagName + fi + } + + # PR's from forks + # Do not validate commit messages, + # (if user didn't ensure CC, PR should be squash merged with a valid CC commit message) + - name: 'Prettier check updated, Lint updated - Node.js v12' + if: type = pull_request AND fork = true + node_js: 12 + script: npm run prettier-check-updated && npm run lint-updated + + # master branch + - name: 'Lint, Tag on version bump - Node.js v12' + env: + # GITHUB_TOKEN + - secure: D1+mm66C9ik6AGzkVO8KkRF3Z8DwquK2clfVhNqL0ZErT2FRxWtv+21bDDfymd2ZOOm3hv9wLQw4O+nreKGWhzNDAZYwx46ngPl6bl+GA+ZeIWXyHbli02dcIevEJU2US1MA1f0kefhDvr6vNTMdOXOk7CmSMYazJa3kcEoCotdc6Fb6et20bXHXh6mE0LDzSTsVXuM9s3JIESYyU40qK9fFPcKDpOgxqbZ8K57b7ufOeL9caZ2VN0ixq2G/AoEvZPCijFAVAltcc0W11t6Kri1Cs4cv+ox64dBAwhk9w4fD6F75vgRxC/5aHitafTtRTbZPLSOCJpfeP7hmiBAK/7qMLCo0k1XML9S42fj1yhk87Dfa2FJ1Q69bLFSZbxiM1Z8esenIDk34rZpIszkEwrBDuTVJRC00M7jfWnw4sSDs4ka/hkU/968Fe3ppYWITfdBZccTGSTZK6/Zkn/fKqLIonapqh43O18hujhdcjQFNtnXUDOcnJ5leK9LRziquGi2kO/1Uhc3oR7nnRF5VR/qwXfYabdpDul5Kdp8lo7cp2bozsP5oFiDDO+t/JGLf4ryHtBfKh2HvZbMgcGDfEuRHxfQWDhtHf2xh/hHkYy1L0/W8mGSjopfF0jSHiqYcje05LmNchsSrY1nIA0lTPjnfZimNbKMhEQ0FspBBzpU= + if: branch = master AND type = push + node_js: 12 + script: + - | + npm run lint && + { + # If package version was changed with last merged PR, push tag + tagName=`git diff HEAD^ package.json | grep '"version": "' | grep -oE "[0-9]+\.[0-9]+\.[0-9]+"` + if [ $? -eq 0 ]; then + git tag v$tagName && git push -q https://$GITHUB_TOKEN@github.com/serverless/eslint-config --tags + fi + } + + # version tag + - stage: Deploy + env: + # GITHUB_TOKEN + - secure: D1+mm66C9ik6AGzkVO8KkRF3Z8DwquK2clfVhNqL0ZErT2FRxWtv+21bDDfymd2ZOOm3hv9wLQw4O+nreKGWhzNDAZYwx46ngPl6bl+GA+ZeIWXyHbli02dcIevEJU2US1MA1f0kefhDvr6vNTMdOXOk7CmSMYazJa3kcEoCotdc6Fb6et20bXHXh6mE0LDzSTsVXuM9s3JIESYyU40qK9fFPcKDpOgxqbZ8K57b7ufOeL9caZ2VN0ixq2G/AoEvZPCijFAVAltcc0W11t6Kri1Cs4cv+ox64dBAwhk9w4fD6F75vgRxC/5aHitafTtRTbZPLSOCJpfeP7hmiBAK/7qMLCo0k1XML9S42fj1yhk87Dfa2FJ1Q69bLFSZbxiM1Z8esenIDk34rZpIszkEwrBDuTVJRC00M7jfWnw4sSDs4ka/hkU/968Fe3ppYWITfdBZccTGSTZK6/Zkn/fKqLIonapqh43O18hujhdcjQFNtnXUDOcnJ5leK9LRziquGi2kO/1Uhc3oR7nnRF5VR/qwXfYabdpDul5Kdp8lo7cp2bozsP5oFiDDO+t/JGLf4ryHtBfKh2HvZbMgcGDfEuRHxfQWDhtHf2xh/hHkYy1L0/W8mGSjopfF0jSHiqYcje05LmNchsSrY1nIA0lTPjnfZimNbKMhEQ0FspBBzpU= + node_js: 12 + script: skip + deploy: + provider: npm + email: services@serverless.com + api_key: + secure: TR0iwraM6LkJFqKIjPbHP04yE4HYD0bY4jTGrf8TQTgeOUw3jF/5vLXU5XORYypWSgxmehKUaeeo8YC0QuC72NF37DkvzmwS7ENoIwcOxEtrIYjWhWVWBsPN8qfM5gFj0k4KY1clvamdTOTQ+yg5od7iYIDhg3L2CM3yMAsx/MifMYZbYvXIUGeXuoohd2XTwh89kSMMoae/E+zSvvZANxByDOAliSM10O6kAn9UclrE8lILskmchuNurzqMpUkIJQuAYXUojYMnou5uZhRPWrtbyrcrv+QFb7LQmqHD3gSJemLrgjKM0/7IkPSirRcca6bixxrJYwCDvr35KkWHUibEbmgBJL1pyAmftBG5cL6qMQzBihLCQDy/dI+leR48uTWDlWA+eSy05vkmYEm5JoxY49R7wbMG/aZb1lZlz+M9+HgUuJO9spK1wIiQrqOV8oLizjMAUFy94xp7/kXImj0KVR6yk2cxBBqDyVAbINitF+h98hl7BPQepoCcj4Y5Mz8y20RPZsk0+Y1z5sfaXhCCeNcPgLqv8uKyDwcXfM1TgoFk9L52m5DfHpBKfm5THWqbY4THB1dpQfPkjZEREwDoFo+fAWefHcB+erJ+kbGl2kxZw/7T/TRZmM34ExFLbUkl4kF/Xc4s3b4QdFkbNfbHljn/2rhg+sFHR3j83FY= + after_deploy: npx github-release-from-cc-changelog $TRAVIS_TAG diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..052617c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## 1.0.0 (2019-06-24) + +### Features + +- Prettier configuration ([74f6612](https://github.com/serverless/eslint-config/commit/74f6612)) +- Reflect ESLint configuration of `serverless` package ([be5561b](https://github.com/serverless/eslint-config/commit/be5561b)) +- Turn off rules that collide with Prettier ([c4ebb38](https://github.com/serverless/eslint-config/commit/c4ebb38)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6d212b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Serverless, Inc. http://www.serverless.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1426b76..367c0e2 100755 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ # @serverless/eslint-config + +## Common ESLint, Prettier configuration for Serverless projects + +### Configuration in a project + +#### 1. Install needed dependencies + +```bash +npm i --save-dev eslint eslint-plugin-import prettier git-list-updated +``` + +#### 2. Configure ESLint + +Add `eslintConfig` to package.json: + +_For Node.js projects:_ + +```json +{ + "eslintConfig": { + "extends": "@serverless/eslint-config/node", + "root": true + } +} +``` + +_For Browser projects:_ + +```json +{ + "eslintConfig": { + "extends": "@serverless/eslint-config/browser", + "root": true + } +} +``` + +#### 3. Configure Prettier + +Add `prettier.config.js` file with following content: + +```javascript +'use strict'; + +module.exports = require('@serverless/eslint-config/prettier.config'); +``` + +#### 4. Configure npm scripts + +```json +{ + "scripts": { + "lint": "eslint . --cache", + "lint-updated": "pipe-git-updated --ext=js -- eslint --cache", + "prettier-check": "prettier -c --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"", + "prettier-check-updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=md --ext=yaml --ext=yml -- prettier -c", + "prettify": "prettier --write --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"" + } +} +``` + +#### 5. Ensure to validate changes in CI + +Run following validation of PR's in CI build. + +Note: following should be run only for PR's (as `*-updated`) scripts may fail on _branch_ or _tag_ builds due to lack of existing reference to `master`) + +```bash +npm run lint-updated && npm run prettier-check-updated +``` + +--- + +### Commitlint and automation of release process (experimental) + +Commit messages in context of this project should follow [Convention Commits Convention](https://www.conventionalcommits.org/en/v1.0.0-beta.4/#summary) + +See proposed [Commit Message Guidelines](https://docs.google.com/document/d/1hKUs3qt_aVp_PBI1UqvfaIqKma3jAJimEoGCRGGbOqs/edit#) + +#### Release process automation with help of Travis CI + +1. Commit messages are validated (with [commitlint](https://commitlint.js.org/)) in context of PR's that are coming from local branches. + To not increase difficulty of contribution, we do not enforce that on external contributors. Still in turn PR's coming from forks (if commit messages do not follow CC), are expected to be squash merged with a valid CC commit message. +1. In Release PR by running `npm run prepare-release` command developer bumps version in `package.json` and generates the changelog entry (which can be altered if needed). +1. Release PR's are automatically detected in CI by fact of `version` in `package.json` file being changed. + In context of that build, existence of new version changelog entry (in `CHANGELOG.md`) is validated. +1. Once release PR is merged, `master` build detects that release PR was merged by fact that it covers change of `version` field in `package.json` file. In such case (after tests pass) version tag is created and pushed to the repository +1. In context fo version tag build, new version is published to npm, and release notes are retrieved from CHANGELOG.md and pushed to GitHub. +1. If needed release notes can be updated at any time afterwards. They should be updated in `CHANGELOG.md` and change can be pushed to GitHub release notes by running: + `npx github-release-from-cc-changelog ` diff --git a/browser.js b/browser.js new file mode 100644 index 0000000..e2cc8bb --- /dev/null +++ b/browser.js @@ -0,0 +1,12 @@ +'use strict'; + +const { join } = require('path'); + +module.exports = { + // The only way to ensure that ESLint resolves expected config from any location + extends: join(__dirname, 'index.js'), + env: { browser: true }, + rules: { + 'no-alert': 'error', + }, +}; diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..951ffae --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,23 @@ +'use strict'; + +module.exports = { + rules: { + 'body-leading-blank': [2, 'always'], + 'body-max-line-length': [2, 'always', 72], + 'footer-leading-blank': [2, 'always'], + 'footer-max-line-length': [2, 'always', 72], + 'header-max-length': [2, 'always', 72], + 'scope-case': [2, 'always', 'start-case'], + 'scope-enum': [2, 'always', ['']], + 'subject-case': [2, 'always', 'sentence-case'], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [2, 'never', '.'], + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'type-enum': [ + 2, + 'always', + ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'style', 'test'], + ], + }, +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..b5d5dfd --- /dev/null +++ b/index.js @@ -0,0 +1,111 @@ +'use strict'; + +module.exports = { + extends: 'eslint:recommended', + env: { es6: true }, + parserOptions: { ecmaVersion: 2015 }, + plugins: ['import'], + rules: { + 'array-callback-return': 'error', + 'block-scoped-var': 'error', + 'camelcase': ['error', { properties: 'never' }], + 'comma-dangle': ['error', 'always-multiline'], + 'consistent-return': 'error', + 'curly': ['error', 'multi-line'], + 'default-case': ['error', { commentPattern: '^no default$' }], + 'dot-notation': ['error', { allowKeywords: true }], + 'eqeqeq': ['error', 'allow-null'], + 'guard-for-in': 'error', + + 'import/export': 'error', + 'import/imports-first': ['error', 'absolute-first'], + 'import/newline-after-import': 'error', + 'import/no-amd': 'error', + 'import/no-duplicates': 'error', + 'import/no-extraneous-dependencies': [ + 'error', + { devDependencies: ['**/*.test.js', '**/scripts/**', '**/tests/**', 'prettier.config.js'] }, + ], + 'import/no-mutable-exports': 'error', + 'import/no-named-as-default': 'error', + 'import/no-named-as-default-member': 'error', + 'import/no-unresolved': ['error', { commonjs: true }], + 'import/prefer-default-export': 'error', + + 'new-cap': ['error', { newIsCap: true }], + 'no-array-constructor': 'error', + 'no-caller': 'error', + 'no-confusing-arrow': ['error', { allowParens: true }], + 'no-console': 'error', + 'no-duplicate-imports': 'error', + 'no-else-return': 'error', + 'no-empty-function': ['error', { allow: ['arrowFunctions', 'functions', 'methods'] }], + 'no-eval': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-extra-semi': 'off', // Handled by Prettier + 'no-implied-eval': 'error', + 'no-iterator': 'error', + 'no-label-var': 'error', + 'no-labels': ['error', { allowLoop: false, allowSwitch: false }], + 'no-lone-blocks': 'error', + 'no-lonely-if': 'error', + 'no-loop-func': 'error', + 'no-mixed-spaces-and-tabs': 'off', // Handled by Prettier + 'no-multi-str': 'error', + 'no-nested-ternary': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-object': 'error', + 'no-new-require': 'error', + 'no-new-wrappers': 'error', + 'no-octal-escape': 'error', + 'no-proto': 'error', + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'ForInStatement', + 'LabeledStatement', + 'WithStatement', + ], + 'no-return-assign': 'error', + 'no-script-url': 'error', + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-shadow': 'error', + 'no-throw-literal': 'error', + 'no-undef-init': 'error', + 'no-underscore-dangle': ['error', { allowAfterThis: false }], + 'no-unexpected-multiline': 'off', // Handled by Prettier + 'no-unneeded-ternary': ['error', { defaultAssignment: false }], + 'no-unused-expressions': ['error', { allowShortCircuit: false, allowTernary: false }], + 'no-unused-vars': ['error', { vars: 'local', args: 'after-used' }], + 'no-useless-computed-key': 'error', + 'no-useless-concat': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': [ + 'error', + { ignoreDestructuring: false, ignoreImport: false, ignoreExport: false }, + ], + 'no-var': 'error', + 'no-void': 'error', + 'object-shorthand': ['error', 'always', { ignoreConstructors: false, avoidQuotes: true }], + 'one-var': ['error', 'never'], + 'operator-assignment': ['error', 'always'], + 'quotes': ['error', 'single', { avoidEscape: true }], + 'prefer-arrow-callback': ['error', { allowNamedFunctions: false, allowUnboundThis: true }], + 'prefer-const': ['error', { destructuring: 'any', ignoreReadBeforeAssign: true }], + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'error', + 'radix': 'error', + 'vars-on-top': 'error', + 'spaced-comment': ['error', 'always', { exceptions: ['-', '+'], markers: ['=', '!'] }], + 'strict': ['error', 'safe'], + 'yoda': 'error', + }, + overrides: [ + { files: ['**/*.test.js', '**/tests/**'], env: { mocha: true, jest: true } }, + { files: ['jest.setupEnvironment.js'], env: { jest: true } }, + ], +}; diff --git a/node.js b/node.js new file mode 100644 index 0000000..dc19411 --- /dev/null +++ b/node.js @@ -0,0 +1,12 @@ +'use strict'; + +const { join } = require('path'); + +module.exports = { + // The only way to ensure that ESLint resolves expected config from any location + extends: join(__dirname, 'index.js'), + env: { node: true }, + rules: { + 'no-path-concat': 'error', + }, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..bc202f8 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "@serverless/eslint-config", + "version": "1.0.0", + "description": "ESLint & Prettier config for Serveless projects", + "keywords": [ + "lint", + "eslint", + "prettier" + ], + "author": "serverless.com", + "repository": "serverless/eslint-config", + "eslintConfig": { + "extends": "./node.js", + "root": true + }, + "standard-version": { + "skip": { + "commit": true, + "tag": true + } + }, + "devDependencies": { + "@commitlint/cli": "^8.0.0", + "eslint": "^5.16.0", + "eslint-plugin-import": "^2.17.3", + "git-list-updated": "^1.2.0", + "github-release-from-cc-changelog": "^2.2.0", + "prettier": "^1.18.2", + "standard-version": "^6.0.1" + }, + "peerDependencies": { + "eslint": ">=5" + }, + "scripts": { + "lint": "eslint . --cache", + "commitlint": "commitlint -f HEAD@{15}", + "commitlint-ci-pull-request": "commitlint -f HEAD~1", + "lint-updated": "pipe-git-updated --ext=js -- eslint --cache", + "prepare-release": "standard-version && prettier --write CHANGELOG.md", + "prettier-check": "prettier -c --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"", + "prettier-check-updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=md --ext=yaml --ext=yml -- prettier -c", + "prettify": "prettier --write --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"" + }, + "license": "MIT" +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..ad49949 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + endOfLine: 'lf', + printWidth: 100, + quoteProps: 'consistent', + singleQuote: true, + trailingComma: 'es5', +};