diff --git a/.eslintignore b/.eslintignore index d3358a02fe4b..5338df11c520 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,11 @@ -**/node_modules/* -**/dist/* -android/**/build/* -.github/actions/**/index.js" +!.storybook +!.github +.github/actions/**/index.js +*.config.js +**/.eslintrc.js +**/node_modules/** +**/dist/** +android/**/build/** docs/vendor/** +docs/assets/** +web/gtm.js diff --git a/.eslintrc.js b/.eslintrc.js index c8a842fa4650..1f23ae22ca7e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,9 +4,9 @@ const restrictedImportPaths = [ importNames: ['useWindowDimensions', 'StatusBar', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', 'Text', 'ScrollView'], message: [ '', - "For 'useWindowDimensions', please use 'src/hooks/useWindowDimensions' instead.", - "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.", - "For 'StatusBar', please use 'src/libs/StatusBar' instead.", + "For 'useWindowDimensions', please use '@src/hooks/useWindowDimensions' instead.", + "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", + "For 'StatusBar', please use '@src/libs/StatusBar' instead.", "For 'Text', please use '@components/Text' instead.", "For 'ScrollView', please use '@components/ScrollView' instead.", ].join('\n'), @@ -14,7 +14,7 @@ const restrictedImportPaths = [ { name: 'react-native-gesture-handler', importNames: ['TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight'], - message: "Please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.", + message: "Please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", }, { name: 'awesome-phonenumber', @@ -24,7 +24,7 @@ const restrictedImportPaths = [ { name: 'react-native-safe-area-context', importNames: ['useSafeAreaInsets', 'SafeAreaConsumer', 'SafeAreaInsetsContext'], - message: "Please use 'useSafeAreaInsets' from 'src/hooks/useSafeAreaInset' and/or 'SafeAreaConsumer' from 'src/components/SafeAreaConsumer' instead.", + message: "Please use 'useSafeAreaInsets' from '@src/hooks/useSafeAreaInset' and/or 'SafeAreaConsumer' from '@components/SafeAreaConsumer' instead.", }, { name: 'react', @@ -34,18 +34,18 @@ const restrictedImportPaths = [ { name: '@styles/index', importNames: ['default', 'defaultStyles'], - message: 'Do not import styles directly. Please use the `useThemeStyles` hook or `withThemeStyles` HOC instead.', + message: 'Do not import styles directly. Please use the `useThemeStyles` hook instead.', }, { name: '@styles/utils', importNames: ['default', 'DefaultStyleUtils'], - message: 'Do not import StyleUtils directly. Please use the `useStyleUtils` hook or `withStyleUtils` HOC instead.', + message: 'Do not import StyleUtils directly. Please use the `useStyleUtils` hook instead.', }, { name: '@styles/theme', importNames: ['default', 'defaultTheme'], - message: 'Do not import themes directly. Please use the `useTheme` hook or `withTheme` HOC instead.', + message: 'Do not import themes directly. Please use the `useTheme` hook instead.', }, { name: '@styles/theme/illustrations', @@ -60,15 +60,15 @@ const restrictedImportPaths = [ const restrictedImportPatterns = [ { group: ['**/assets/animations/**/*.json'], - message: "Do not import animations directly. Please use the 'src/components/LottieAnimations' import instead.", + message: "Do not import animations directly. Please use the '@components/LottieAnimations' import instead.", }, { group: ['@styles/theme/themes/**'], - message: 'Do not import themes directly. Please use the `useTheme` hook or `withTheme` HOC instead.', + message: 'Do not import themes directly. Please use the `useTheme` hook instead.', }, { group: ['@styles/utils/**', '!@styles/utils/FontUtils', '!@styles/utils/types'], - message: 'Do not import style util functions directly. Please use the `useStyleUtils` hook or `withStyleUtils` HOC instead.', + message: 'Do not import style util functions directly. Please use the `useStyleUtils` hook instead.', }, { group: ['@styles/theme/illustrations/themes/**'], @@ -77,218 +77,175 @@ const restrictedImportPatterns = [ ]; module.exports = { - extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-native-a11y/basic', 'plugin:@dword-design/import-alias/recommended', 'prettier'], - plugins: ['react-native-a11y', 'testing-library'], - parser: 'babel-eslint', - ignorePatterns: ['!.*', 'src/vendor', '.github/actions/**/index.js', 'desktop/dist/*.js', 'dist/*.js', 'node_modules/.bin/**', 'node_modules/.cache/**', '.git/**'], + extends: [ + 'expensify', + 'airbnb-typescript', + 'plugin:storybook/recommended', + 'plugin:react-native-a11y/basic', + 'plugin:@dword-design/import-alias/recommended', + 'plugin:@typescript-eslint/recommended-type-checked', + 'plugin:@typescript-eslint/stylistic-type-checked', + 'plugin:you-dont-need-lodash-underscore/all', + 'prettier', + ], + plugins: ['@typescript-eslint', 'jsdoc', 'you-dont-need-lodash-underscore', 'react-native-a11y', 'react', 'testing-library'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + }, env: { jest: true, }, globals: { __DEV__: 'readonly', }, - overrides: [ - { - files: ['*.js', '*.jsx', '*.ts', '*.tsx'], - plugins: ['react'], - rules: { - 'prefer-regex-literals': 'off', - 'rulesdir/no-multiple-onyx-in-file': 'off', - 'react-native-a11y/has-accessibility-hint': ['off'], - 'react/jsx-no-constructed-context-values': 'error', - 'react-native-a11y/has-valid-accessibility-descriptors': [ - 'error', - { - touchables: ['PressableWithoutFeedback', 'PressableWithFeedback'], - }, - ], - '@dword-design/import-alias/prefer-alias': [ - 'warn', - { - alias: { - '@assets': './assets', - '@components': './src/components', - '@hooks': './src/hooks', - // This is needed up here, if not @libs/actions would take the priority - '@userActions': './src/libs/actions', - '@libs': './src/libs', - '@navigation': './src/libs/Navigation', - '@pages': './src/pages', - '@styles': './src/styles', - // This path is provide alias for files like `ONYXKEYS` and `CONST`. - '@src': './src', - '@desktop': './desktop', - '@github': './.github', - }, - }, - ], - 'rulesdir/avoid-anonymous-functions': 'off', + rules: { + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + + // TypeScript specific rules + '@typescript-eslint/prefer-enum-initializers': 'error', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/switch-exhaustiveness-check': 'error', + '@typescript-eslint/consistent-type-definitions': ['error', 'type'], + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-import-type-side-effects': 'error', + '@typescript-eslint/array-type': ['error', {default: 'array-simple'}], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: ['variable', 'property'], + format: ['camelCase', 'UPPER_CASE', 'PascalCase'], }, - }, - // This helps disable the `prefer-alias` rule to be enabled for specific directories - { - files: ['tests/**/*.js', 'tests/**/*.ts', 'tests/**/*.jsx', 'assets/**/*.js', '.storybook/**/*.js'], - rules: {'@dword-design/import-alias/prefer-alias': ['off']}, - }, - { - files: ['tests/**/*.js', 'tests/**/*.ts', 'tests/**/*.jsx', 'tests/**/*.tsx'], - extends: ['plugin:testing-library/react'], - rules: { - 'testing-library/await-async-queries': 'error', - 'testing-library/await-async-utils': 'error', - 'testing-library/no-debugging-utils': 'error', - 'testing-library/no-manual-cleanup': 'error', - 'testing-library/no-unnecessary-act': 'error', - 'testing-library/prefer-find-by': 'error', - 'testing-library/prefer-presence-queries': 'error', - 'testing-library/prefer-screen-queries': 'error', + { + selector: 'function', + format: ['camelCase', 'PascalCase'], }, - }, - { - files: ['*.js', '*.jsx'], - settings: { - 'import/resolver': { - node: { - extensions: ['.js', '.website.js', '.desktop.js', '.native.js', '.ios.js', '.android.js', '.config.js', '.ts', '.tsx'], - }, + { + selector: ['typeLike', 'enumMember'], + format: ['PascalCase'], + }, + { + selector: ['parameter', 'method'], + format: ['camelCase', 'PascalCase'], + leadingUnderscore: 'allow', + }, + ], + '@typescript-eslint/ban-types': [ + 'error', + { + types: { + object: "Use 'Record' instead.", }, + extendDefaults: true, }, - rules: { - 'import/extensions': [ - 'error', - 'ignorePackages', - { - js: 'never', - jsx: 'never', - ts: 'never', - tsx: 'never', - }, - ], - 'no-restricted-imports': [ - 'error', - { - paths: restrictedImportPaths, - patterns: restrictedImportPatterns, - }, - ], - curly: 'error', - 'react/display-name': 'error', + ], + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + fixStyle: 'separate-type-imports', }, - }, - { - files: ['*.ts', '*.tsx'], - extends: [ - 'airbnb-typescript', - 'plugin:@typescript-eslint/recommended-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - 'plugin:you-dont-need-lodash-underscore/all', - 'prettier', - ], - plugins: ['@typescript-eslint', 'jsdoc', 'you-dont-need-lodash-underscore'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', + ], + '@typescript-eslint/consistent-type-exports': [ + 'error', + { + fixMixedExportsWithInlineTypeSpecifier: false, }, - rules: { - // TODO: Remove the following rules after TypeScript migration is complete. - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', + ], + + // ESLint core rules + 'es/no-nullish-coalescing-operators': 'off', + 'es/no-optional-chaining': 'off', + + // Import specific rules + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import/no-extraneous-dependencies': 'off', + + // Rulesdir specific rules + 'rulesdir/no-default-props': 'error', + 'rulesdir/no-multiple-onyx-in-file': 'off', + 'rulesdir/prefer-underscore-method': 'off', + 'rulesdir/prefer-import-module-contents': 'off', - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: ['variable', 'property'], - format: ['camelCase', 'UPPER_CASE', 'PascalCase'], - }, - { - selector: 'function', - format: ['camelCase', 'PascalCase'], - }, - { - selector: ['typeLike', 'enumMember'], - format: ['PascalCase'], - }, - { - selector: ['parameter', 'method'], - format: ['camelCase', 'PascalCase'], - leadingUnderscore: 'allow', - }, - ], - '@typescript-eslint/ban-types': [ - 'error', - { - types: { - object: "Use 'Record' instead.", - }, - extendDefaults: true, - }, - ], - '@typescript-eslint/array-type': ['error', {default: 'array-simple'}], - '@typescript-eslint/prefer-enum-initializers': 'error', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/no-non-null-assertion': 'error', - '@typescript-eslint/switch-exhaustiveness-check': 'error', - '@typescript-eslint/consistent-type-definitions': ['error', 'type'], - '@typescript-eslint/no-floating-promises': 'off', - '@typescript-eslint/consistent-type-imports': [ - 'error', - { - prefer: 'type-imports', - fixStyle: 'separate-type-imports', - }, - ], - '@typescript-eslint/no-import-type-side-effects': 'error', - '@typescript-eslint/consistent-type-exports': [ - 'error', - { - fixMixedExportsWithInlineTypeSpecifier: false, - }, - ], - 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], - 'es/no-nullish-coalescing-operators': 'off', - 'es/no-optional-chaining': 'off', - 'valid-jsdoc': 'off', - 'jsdoc/no-types': 'error', - 'rulesdir/no-default-props': 'error', - 'import/no-extraneous-dependencies': 'off', - 'rulesdir/prefer-underscore-method': 'off', - 'rulesdir/prefer-import-module-contents': 'off', - 'react/require-default-props': 'off', - 'react/prop-types': 'off', - 'no-restricted-syntax': [ - 'error', - { - selector: 'TSEnumDeclaration', - message: "Please don't declare enums, use union types instead.", - }, - ], - 'no-restricted-properties': [ - 'error', - { - object: 'Image', - property: 'getSize', - message: 'Usage of Image.getImage is restricted. Please use the `react-native-image-size`.', - }, - ], - 'no-restricted-imports': [ - 'error', - { - paths: restrictedImportPaths, - patterns: restrictedImportPatterns, - }, - ], - curly: 'error', - 'you-dont-need-lodash-underscore/throttle': 'off', + // React and React Native specific rules + 'react-native-a11y/has-accessibility-hint': ['off'], + 'react/require-default-props': 'off', + 'react/prop-types': 'off', + 'react/jsx-no-constructed-context-values': 'error', + 'react-native-a11y/has-valid-accessibility-descriptors': [ + 'error', + { + touchables: ['PressableWithoutFeedback', 'PressableWithFeedback'], }, - }, + ], + + // Disallow usage of certain functions and imports + 'no-restricted-syntax': [ + 'error', + { + selector: 'TSEnumDeclaration', + message: "Please don't declare enums, use union types instead.", + }, + ], + 'no-restricted-properties': [ + 'error', + { + object: 'Image', + property: 'getSize', + message: 'Usage of Image.getImage is restricted. Please use the `react-native-image-size`.', + }, + ], + 'no-restricted-imports': [ + 'error', + { + paths: restrictedImportPaths, + patterns: restrictedImportPatterns, + }, + ], + + // Other rules + curly: 'error', + 'you-dont-need-lodash-underscore/throttle': 'off', + 'prefer-regex-literals': 'off', + 'valid-jsdoc': 'off', + 'jsdoc/no-types': 'error', + '@dword-design/import-alias/prefer-alias': [ + 'warn', + { + alias: { + '@assets': './assets', + '@components': './src/components', + '@hooks': './src/hooks', + // This is needed up here, if not @libs/actions would take the priority + '@userActions': './src/libs/actions', + '@libs': './src/libs', + '@navigation': './src/libs/Navigation', + '@pages': './src/pages', + '@styles': './src/styles', + // This path is provide alias for files like `ONYXKEYS` and `CONST`. + '@src': './src', + '@desktop': './desktop', + '@github': './.github', + }, + }, + ], + }, + + // Remove once no JS files are left + overrides: [ { - files: ['workflow_tests/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}', '.github/**/*.{js,jsx,ts,tsx}'], + files: ['*.js', '*.jsx'], rules: { - '@lwc/lwc/no-async-await': 'off', - 'no-await-in-loop': 'off', - 'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'], + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/unbound-method': 'off', + 'jsdoc/no-types': 'off', + 'react/jsx-filename-extension': 'off', + 'rulesdir/no-default-props': 'off', }, }, { diff --git a/.github/.eslintrc.js b/.github/.eslintrc.js index e769944cd1a9..d6d39822b737 100644 --- a/.github/.eslintrc.js +++ b/.github/.eslintrc.js @@ -1,6 +1,10 @@ -// For all these Node.js scripts, we do not want to disable `console` statements module.exports = { rules: { + // For all these Node.js scripts, we do not want to disable `console` statements 'no-console': 'off', + + '@lwc/lwc/no-async-await': 'off', + 'no-await-in-loop': 'off', + 'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'], }, }; diff --git a/src/components/tagPropTypes.js b/src/components/tagPropTypes.js index 2108b65ebbd5..bb85dc31b938 100644 --- a/src/components/tagPropTypes.js +++ b/src/components/tagPropTypes.js @@ -8,6 +8,7 @@ const tagListPropTypes = PropTypes.shape({ enabled: PropTypes.bool.isRequired, /** "General Ledger code" that corresponds to this tag in an accounting system. Similar to an ID. */ + // eslint-disable-next-line @typescript-eslint/naming-convention 'GL Code': PropTypes.string, }); diff --git a/src/pages/EnablePayments/IdologyQuestions.tsx b/src/pages/EnablePayments/IdologyQuestions.tsx index 6baea2158613..756965e961c8 100644 --- a/src/pages/EnablePayments/IdologyQuestions.tsx +++ b/src/pages/EnablePayments/IdologyQuestions.tsx @@ -1,6 +1,5 @@ import React, {useState} from 'react'; import {View} from 'react-native'; -import type {WalletAdditionalQuestionDetails} from 'src/types/onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; @@ -13,6 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {WalletAdditionalQuestionDetails} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; const MAX_SKIP = 1; diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index 4972022ae5e5..f3152bb7d2f1 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -1,5 +1,21 @@ module.exports = { + extends: ['plugin:testing-library/react'], rules: { 'no-import-assign': 'off', + '@lwc/lwc/no-async-await': 'off', + 'no-await-in-loop': 'off', + 'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'], + + // This helps disable the `prefer-alias` rule for tests + '@dword-design/import-alias/prefer-alias': ['off'], + + 'testing-library/await-async-queries': 'error', + 'testing-library/await-async-utils': 'error', + 'testing-library/no-debugging-utils': 'error', + 'testing-library/no-manual-cleanup': 'error', + 'testing-library/no-unnecessary-act': 'error', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-screen-queries': 'error', }, }; diff --git a/tsconfig.json b/tsconfig.json index 7f7b7479f44b..ea072fc4a354 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,37 +1,12 @@ { "$schema": "https://json.schemastore.org/tsconfig", + "extends": "expo/tsconfig.base", "compilerOptions": { - "target": "esnext", "module": "commonjs", "types": ["react-native", "jest"], - "lib": [ - "DOM", - "es2019", - "es2020.bigint", - "es2020.date", - "es2020.number", - "es2020.promise", - "es2020.string", - "es2020.symbol.wellknown", - "es2021.promise", - "es2021.string", - "es2021.weakref", - "es2022.array", - "es2022.object", - "es2022.string", - "ES2021.Intl", - "ES2023.Array" - ], - "allowJs": true, - "checkJs": false, - "jsx": "react-native", - "noEmit": true, "isolatedModules": true, "strict": true, - "moduleResolution": "node", - "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "skipLibCheck": true, "incremental": true, "baseUrl": ".", "paths": { @@ -48,7 +23,6 @@ "@userActions/*": ["./src/libs/actions/*"] } }, - "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js", "**/docs/*"], "include": ["src", "desktop", "web", "website", "docs", "assets", "config", "tests", "jest", "__mocks__", ".github/**/*", ".storybook/**/*", "workflow_tests", "scripts"], - "extends": "expo/tsconfig.base" + "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js", "**/docs/*"] } diff --git a/web/gtm.js b/web/gtm.js index ee96d01b9a67..6194d4ca2453 100644 --- a/web/gtm.js +++ b/web/gtm.js @@ -1,4 +1,3 @@ -/* eslint-disable no-param-reassign, prefer-template */ (function (w, d, s, l, i) { w[l] = w[l] || []; w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); diff --git a/workflow_tests/.eslintrc.js b/workflow_tests/.eslintrc.js new file mode 100644 index 000000000000..5cd4096bc831 --- /dev/null +++ b/workflow_tests/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + rules: { + '@lwc/lwc/no-async-await': 'off', + 'no-await-in-loop': 'off', + 'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'], + }, +};