diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d4c43f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.idea +.vscode +node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b2801c1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "always", + "overrides": [ + { + "files": ["*.scss", "*.css"], + "options": { + "singleQuote": false + } + } + ] +} diff --git a/eslint-config/README.md b/eslint-config/README.md new file mode 100644 index 0000000..2ef87c1 --- /dev/null +++ b/eslint-config/README.md @@ -0,0 +1,20 @@ +# MediaMonks - Eslint Configuration + +To make sure your project is following the [MediaMonks - Frontend Coding Standards](https://github.com/mediamonks/frontend-coding-standards) you should install `@mediamonks/eslint-config` and its peer dependencies as an [eslint extension](https://eslint.org/docs/user-guide/configuring#extending-configuration-files): + +``` +npx install-peerdeps --dev @mediamonks/eslint-config +``` + +And set `@mediamonks` as `extends` in your `.eslintrc.js` file: + +``` +module.exports = { + "extends": "@mediamonks" +} +``` + +This Eslint extension is configured for JavaScript, [TypeScript](http://typescriptlang.org/), [React](https://reactjs.org/), [Vue](https://vuejs.org/) and [Muban](https://mediamonks.github.io/muban/) projects. + +#### Prettier +This eslint-configuration uses [Prettier](https://prettier.io/) for the formatting. Please make sure you use the same [Prettier configuration](.prettierrc) to avoid conflicts. diff --git a/eslint-config/index.js b/eslint-config/index.js new file mode 100644 index 0000000..59a2fd9 --- /dev/null +++ b/eslint-config/index.js @@ -0,0 +1,224 @@ +const typeScriptSettings = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + }, + extends: [ + 'plugin:@typescript-eslint/recommended', + 'prettier/@typescript-eslint', + 'plugin:prettier/recommended', + 'plugin:import/typescript' + ], + rules: { + '@typescript-eslint/array-type': ['error', { default: 'generic', readonly: 'generic' }], + '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/consistent-type-assertions': 'error', + '@typescript-eslint/explicit-member-accessibility': 'error', + '@typescript-eslint/indent': 'off', + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'default', + format: ['camelCase'], + leadingUnderscore: 'forbid', + trailingUnderscore: 'forbid', + }, + { + selector: 'typeLike', + format: ['PascalCase'], + }, + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE'], + }, + { + selector: 'enumMember', + format: ['PascalCase'], + }, + ], + '@typescript-eslint/no-empty-function': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { vars: 'all', args: 'after-used', ignoreRestSiblings: true }, + ], + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': ['error', { functions: false }], + '@typescript-eslint/prefer-interface': 'off', + '@typescript-eslint/prefer-readonly': 'error', + camelcase: 'off', + }, +}; + +const reactSettings = { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended', 'prettier/react'], + settings: { + react: { + version: 'detect', + }, + }, + rules: { + 'react-hooks/exhaustive-deps': 'error', + 'react/button-has-type': 'error', + 'react/display-name': 'off', + 'react/jsx-boolean-value': 'error', + 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }], + 'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }], + 'react/jsx-wrap-multilines': 'off', + 'react/no-array-index-key': 'error', + 'react/no-this-in-sfc': 'error', + 'react/no-unused-prop-types': 'error', + 'react/no-unused-state': 'error', + 'react/void-dom-elements-no-children': 'error', + }, +}; + +module.exports = { + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, + extends: ['airbnb', 'plugin:prettier/recommended'], + plugins: ['import', 'unicorn', 'babel'], + env: { + browser: true, + es6: true, + }, + globals: { + require: true, + process: true, + }, + rules: { + 'babel/no-unused-expressions': 'error', + 'default-case': 'error', + 'import/extensions': [ + 'error', + 'always', + { js: 'never', jsx: 'never', ts: 'never', tsx: 'never', hbs: 'never', vue: 'never' }, + ], + 'import/no-unresolved': 'error', + 'import/order': ['error', { groups: ['external', 'builtin', ['sibling', 'parent']] }], + 'import/prefer-default-export': 'off', + 'lines-between-class-members': 'off', + 'max-lines': 'error', + 'no-console': 'error', + 'no-debugger': 'error', + 'no-extra-boolean-cast': 'off', + 'no-plusplus': 'off', + 'no-restricted-properties': [ + 'error', + { object: 'document', property: 'querySelector' }, + { object: 'document', property: 'querySelectorAll' }, + ], + 'no-undef': 'error', + 'no-underscore-dangle': 'error', + 'no-unused-expressions': 'off', + 'react/static-property-placement': 'off', + 'unicorn/catch-error-name': 'error', + 'unicorn/custom-error-definition': 'off', + 'unicorn/error-message': 'error', + 'unicorn/escape-case': 'error', + 'unicorn/explicit-length-check': 'error', + 'unicorn/new-for-builtins': 'error', + 'unicorn/no-abusive-eslint-disable': 'error', + 'unicorn/no-array-instanceof': 'error', + 'unicorn/no-console-spaces': 'error', + 'unicorn/no-fn-reference-in-iterator': 'off', + 'unicorn/no-for-loop': 'error', + 'unicorn/no-hex-escape': 'error', + 'unicorn/no-new-buffer': 'error', + 'unicorn/no-process-exit': 'error', + 'unicorn/no-unreadable-array-destructuring': 'error', + 'unicorn/no-unsafe-regex': 'off', + 'unicorn/no-unused-properties': 'error', + 'unicorn/no-zero-fractions': 'error', + 'unicorn/number-literal-case': 'error', + 'unicorn/prefer-exponentiation-operator': 'error', + 'unicorn/prefer-includes': 'error', + 'unicorn/prefer-node-remove': 'error', + 'unicorn/prefer-query-selector': 'error', + 'unicorn/prefer-starts-ends-with': 'error', + 'unicorn/prefer-text-content': 'error', + 'unicorn/prefer-type-error': 'error', + 'unicorn/prevent-abbreviations': [ + 'error', + { + replacements: { + ref: false, + refs: false, + prop: false, + props: false, + src: false, + param: false, + params: false, + args: false, + }, + }, + ], + 'unicorn/throw-new-error': 'error', + }, + overrides: [ + { + files: ['*.vue'], + parser: 'vue-eslint-parser', + rules: { + 'prettier/prettier': ['off'], + }, + }, + { + files: ['*.ts'], + ...typeScriptSettings, + }, + { + files: ['*.tsx'], + ...typeScriptSettings, + parserOptions: { + ...typeScriptSettings.parserOptions, + ...reactSettings.parserOptions, + }, + extends: [...typeScriptSettings.extends, ...reactSettings.extends], + rules: { + ...typeScriptSettings.rules, + ...reactSettings.rules, + 'react/prop-types': 'off', + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'default', + format: ['camelCase'], + leadingUnderscore: 'forbid', + trailingUnderscore: 'forbid', + }, + { + selector: 'typeLike', + format: ['PascalCase'], + }, + { + selector: 'variable', + // Exception for FunctionComponents + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], + }, + { + selector: 'function', + // Exception for FunctionComponents + format: ['camelCase', 'PascalCase'], + }, + { + selector: 'enumMember', + format: ['PascalCase'], + }, + ], + }, + }, + { + files: ['*.jsx'], + ...reactSettings, + }, + ], +}; diff --git a/eslint-config/package.json b/eslint-config/package.json new file mode 100644 index 0000000..07f7c01 --- /dev/null +++ b/eslint-config/package.json @@ -0,0 +1,62 @@ +{ + "name": "@mediamonks/eslint-config", + "version": "1.1.3", + "description": "MediaMonks Frontend Coding Standards Eslint Config", + "main": "index.js", + "scripts": { + "prettify": "prettier --write \"index.js\"", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mediamonks/frontend-coding-standards.git" + }, + "keywords": [ + "MediaMonks", + "Coding", + "Standards", + "eslint", + "eslintconfig", + "Frontend", + "JavaScript", + "TypeScript", + "React", + "Vue", + "Muban" + ], + "author": "MediaMonks", + "license": "MIT", + "bugs": { + "url": "https://github.com/mediamonks/frontend-coding-standards/issues" + }, + "homepage": "https://github.com/mediamonks/frontend-coding-standards#readme", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": ">= 3", + "eslint": ">= 6", + "eslint-friendly-formatter": ">= 4", + "eslint-import-resolver-webpack": ">= 0.11.0", + "eslint-plugin-babel": ">= 5", + "eslint-plugin-html": ">= 5", + "eslint-plugin-import": ">= 2", + "eslint-plugin-jsx-a11y": ">= 6", + "eslint-plugin-prettier": ">= 3", + "eslint-plugin-react": ">= 7", + "eslint-plugin-react-hooks": ">= 4", + "eslint-plugin-unicorn": ">= 18", + "prettier": "> 2", + "typescript": ">= 3.3" + }, + "dependencies": { + "@typescript-eslint/parser": ">= 3.2", + "babel-eslint": "^10.1.0", + "eslint": "^7.1.0", + "eslint-config-airbnb-base": ">= 13", + "eslint-config-airbnb": ">= 18", + "eslint-config-prettier": ">= 4", + "typescript": "^3.9.5", + "vue-eslint-parser": "^7.1.0" + }, + "devDependencies": { + "prettier": "^2.0.5" + } +}