Skip to content

Instantly share code, notes, and snippets.

@hrdyjan1
Created September 11, 2023 10:45
Show Gist options
  • Select an option

  • Save hrdyjan1/99e4bdea63f7f642e95aed9938b46816 to your computer and use it in GitHub Desktop.

Select an option

Save hrdyjan1/99e4bdea63f7f642e95aed9938b46816 to your computer and use it in GitHub Desktop.
Carvago - Frontend Monorepo - eslint
module.exports = {
settings: {
react: {
version: '18',
},
},
parser: '@typescript-eslint/parser',
extends: [
'plugin:prettier/recommended',
// enable typescript support
'plugin:@typescript-eslint/recommended',
// now disable all of the rules that are in conflict with prettier
'prettier',
// note that we don't add the prettier rules, they add noise to the IDE
// and the code is all being formatted on commit anyway.
/*
* Disallow regex lookbehinds. Whole app crashes in unsupported browsers
*/
'plugin:no-lookahead-lookbehind-regexp/recommended',
],
plugins: [
'@typescript-eslint',
'react',
'unused-imports',
'eag',
'unicorn',
'@nx',
'import',
'filename-export',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: ['tsconfig.base.json'],
},
},
},
rules: {
'import/no-duplicates': 'warn',
'import/no-default-export': 'warn',
'import/no-cycle': ['error', {ignoreExternal: false, maxDepth: 1}],
// 'filename-export/match-named-export': 'warn',
'no-nested-ternary': 'warn',
'no-restricted-imports': [
'warn',
{
paths: [
{
name: '@chakra-ui/react',
message: "Don't use chakra. Use platform instead.",
},
{
name: '@chakra-ui/utils',
message: "Don't use chakra. Use platform instead.",
},
{
name: '@chakra-ui/react-utils',
message: "Don't use chakra. Use platform instead.",
},
{
name: '@chakra-ui/layout',
message: "Don't use chakra. Use platform instead.",
},
{
name: '@chakra-ui/system',
importNames: ['useTheme'],
message: "Don't use this chakra component",
},
{
name: '@chakra-ui/styled-system',
importNames: ['CSSObject'],
message: "Don't use chakra for styling. Use Platform components or styled-components.",
},
{
name: 'platform',
importNames: ['theme'],
message:
"Don't use theme directly. Use 'useTheme' hook from styled-components or theme provided inside styled-components css: ${({theme}) => theme.some.token};",
},
{
name: 'ts-pattern',
importNames: ['P'],
message: 'Import Pattern instead the P shortcut. Pattern has better readability.',
},
{
name: 'react-router-dom',
importNames: ['generatePath'],
message: 'generatePath is untyped native function, use composePath from platform',
},
{
name: 'ramda',
importNames: [
'path',
'pathOr',
'paths',
'pathEq',
'modifyPath',
'assocPath',
'dissocPath',
'pathSatisfies',
],
message:
"Don't use path function if not absolutely necessary. They are not TS friendly, and TS errors can slip through them without any detection ",
},
{
name: 'react',
importNames: ['FC'],
message:
"Don't use `const MyButton: React.FC<MyButtonProps>` but `function MyButton(props: MyButtonProps)`",
},
],
patterns: [
{
group: ['lodash', 'lodash/*'],
message: 'Use ramda please.',
},
{
group: ['@material-ui', '@material-ui/*'],
message: "Don't use Material UI anymore. Use Platform components instead.",
},
],
},
],
'no-restricted-syntax': [
'error',
{
selector: "TSAsExpression[expression.type='TSAsExpression']",
message: 'Using double assertion is an anti-pattern. It is not safe.',
},
{
selector: "CallExpression[callee.name='String'][arguments.0.type='ChainExpression']",
message:
'String(undefined) returns "undefined". Please use undefined ?? \'\' or some other approach.',
},
{
selector:
"CallExpression[callee.property.name='catch'][arguments.0.name='handleApiError'] MemberExpression[object.callee.type='Identifier']:not([property.name='unwrap'])",
message:
'In order to catch an RTK Query error, you need to unwrap the promise first. Example: deleteVehicle().unwrap().then().catch()',
},
{
selector: "MemberExpression[object.name='P'][property.name='_']",
message: 'Use Pattern.any instead. It will improve readability.',
},
{
selector:
"ImportDeclaration[source.value='react'][specifiers.0.type='ImportDefaultSpecifier']",
message: "Use named import instead. Example: import {useState} from 'react'.",
},
{
selector:
"ImportDeclaration[source.value='ramda'][specifiers.0.type='ImportDefaultSpecifier']",
message:
"Don't import whole library. Use named import instead. Example: import {map} from 'ramda'.",
},
{
selector: "JSXOpeningElement[name.name='DataGrid'] JSXAttribute[name.name='key']",
message: 'Do not use key to force refresh. Use the useRefreshDataGrid hook instead.',
},
{
selector:
"Property[value] CallExpression:has(MemberExpression[object.callee.property.name='number']) Identifier[name='transform']",
message: "Don't use Yup.number().transform(...). Use yupNumber() from shared instead.",
},
{
selector:
"Property[value] CallExpression:has(MemberExpression[object.callee.name='number']) Identifier[name='transform']",
message: "Don't use number().transform(...). Use yupNumber() from shared instead.",
},
],
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-unsafe-regex': 'error',
'unicorn/prefer-array-flat-map': 'warn',
'unicorn/prefer-array-some': 'warn',
'unicorn/prefer-includes': 'warn',
'arrow-body-style': 'warn',
'dot-notation': 'warn',
'no-var': 'error',
'no-debugger': 'error',
'no-alert': 'error',
'no-console': 'error',
'react/no-array-index-key': 'warn',
'react/jsx-no-leaked-render': 'off',
'eag/no-index': 'warn',
'eag/no-date-parsing': 'warn',
'eag/no-css-property': [
'warn',
{
property: 'button',
message:
"Using button selector is considered an anti-pattern. Use Button component from platform and it's API as is.",
},
{
property: 'svg',
message:
"Using svg selector is considered an anti-pattern. Use Icon component from platform and it's API as is.",
},
{
property: 'letter-spacing',
message:
'Using letter-spacing directly is considered an anti-pattern. Use on of text components from platform.',
},
{
property: 'font-size',
message:
'Using font-size directly is considered an anti-pattern. Use on of text components from platform.',
},
{
property: 'font-weight',
message:
'Using font-weight directly is considered an anti-pattern. Use on of text components from platform.',
},
{
property: 'font-family',
message:
'Using font-family directly is considered an anti-pattern. Use on of text components from platform.',
},
{
property: 'font-size',
message:
'Using font-size directly is considered an anti-pattern. Use on of text components from platform.',
},
{
property: 'margin',
message:
'Using margin is considered an anti-pattern. Use a wrapper element like Box with some padding in it.',
},
{
property: 'px',
exclude: '1px',
message:
'Using px units is considered an anti-pattern. Use theme.getSize() utility or Box element.',
},
{
property: 'rem',
message:
'Using rem units is considered an anti-pattern. Use theme.getSize() utility or Box element.',
},
{
property: '#',
message: 'Using hex colors is considered an anti-pattern. Use one of theme tokens.',
},
{
property: ' > ',
message:
'Using css selectors is considered an anti-pattern. It breaks component encapsulation.',
},
{
property: 'line-height',
message:
'Using line-height directly is considered an anti-pattern. Use on of text components from platform.',
},
],
'object-shorthand': 'warn',
'prefer-arrow-callback': 'warn',
'prefer-const': 'warn',
// In principle I like this rule, but I also want to use names such as _Foo
// for those internal classes that get passed on to redux connect.
// After much refactoring I realized that I just prefer _Foo to FooImpl.
// Since this rule is being well followed without an eslint rule, I"m switching
// this one off.
'@typescript-eslint/class-name-casing': 'off',
// too many of the graphql generate types break this rule, and they do so
// in a way that makes enough sense that I don"t want to deal with it
'@typescript-eslint/camelcase': 'off',
'no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
},
overrides: [
{
files: '*.{js,jsx}',
rules: {
// this should just be fixed
'@typescript-eslint/no-unused-vars': 'off',
// opinion: this is reasonable to disable
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
},
{
files: '*.{ts,tsx}',
/**
* https://stackoverflow.com/questions/64933543/parsing-error-cannot-read-file-tsconfig-json-eslint
*/
// parserOptions: {
// tsconfigRootDir: __dirname,
// project: ['./tsconfig.base.json'],
// sourceType: 'module',
// },
rules: {
/**
* Don't forget to regenerate dep-graph if you change those rules
*/
'@nx/enforce-module-boundaries': [
'error',
{
allowCircularSelfDependency: false,
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'scope:caraudit',
notDependOnLibsWithTags: [
'scope:price-report',
'scope:omnetic-admin-service',
'scope:omnetic-dms',
],
},
{
sourceTag: 'scope:omnetic-dms',
notDependOnLibsWithTags: [
'scope:price-report',
'scope:omnetic-admin-service',
'scope:caraudit',
],
},
{
sourceTag: 'scope:price-report',
notDependOnLibsWithTags: [
'scope:omnetic-dms',
'scope:omnetic-admin-service',
'scope:caraudit',
],
},
{
sourceTag: 'scope:digital-certificate',
notDependOnLibsWithTags: [
'scope:price-report',
'scope:omnetic-dms',
'scope:omnetic-admin-service',
],
},
{
sourceTag: 'scope:omnetic-admin-service',
notDependOnLibsWithTags: [
'scope:omnetic-dms',
'scope:price-report',
'scope:digital-certificate',
'scope:caraudit',
],
},
{
sourceTag: 'scope:features',
onlyDependOnLibsWithTags: ['shared'],
},
{
sourceTag: 'type:app',
onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:util'],
},
{
sourceTag: 'type:ui',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'type:feature',
notDependOnLibsWithTags: ['type:app', 'feature:shared'],
},
{
sourceTag: 'shared-feature',
notDependOnLibsWithTags: ['type:app', 'feature:shared'],
},
{
sourceTag: 'type:e2e',
onlyDependOnLibsWithTags: ['*'],
},
{
sourceTag: 'e2e-utils',
onlyDependOnLibsWithTags: ['type:feature', 'type:util'],
},
{
sourceTag: 'shared',
notDependOnLibsWithTags: ['*'],
},
],
},
],
// 'eag/number-boolean-explicit-conversion': 'error',
'require-await': 'error',
'react/boolean-prop-naming': 'error',
'react/jsx-key': 'error',
'react/jsx-no-useless-fragment': 'off',
'jsx-quotes': ['error', 'prefer-double'],
'react/jsx-curly-brace-presence': [
'error',
{props: 'never', children: 'never', propElementValues: 'always'},
],
// disabled because it conflicts with jsx-a11y/alt-text
'jsx-a11y/img-redundant-alt': 'off',
// changed to match the default tsconfig
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/prefer-interface': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-namespace': 'warn',
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'@typescript-eslint/no-angle-bracket-type-assertion': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'react/forbid-dom-props': [
'warn',
{
forbid: [
'className',
'style',
{
propName: 'data-testId',
message: "Don't use data-testId prop (capital I). Use data-testid instead.",
},
],
},
],
'react/forbid-component-props': [
'warn',
{
forbid: [
'css',
'style',
'className',
{
propName: '__css',
message:
"Don't style components with chakra. Use platform components or styled-compoennts.",
},
{
propName: 'data-testId',
message: "Don't use data-testId prop (capital I). Use data-testid instead.",
},
],
},
],
},
},
{
files: ['**/src/components/{Text,Heading,Display,Avatar}/**.tsx'],
rules: {
'eag/no-css-property': 0,
},
},
{
files: ['**/models/**/*.ts'],
rules: {
'unicorn/no-abusive-eslint-disable': 0,
},
},
{
files: ['**/*.stories.ts', '**/*.stories.tsx'],
rules: {
'import/no-default-export': 0,
},
},
{
files: ['**/src/index.ts', '**/src/index.tsx', '**/src/__modules/*/index.*'],
rules: {
'eag/no-index': 0,
},
},
],
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment