| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- module.exports = {
- root: true,
- parser: '@typescript-eslint/parser',
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'standard',
- 'prettier',
- ],
- plugins: ['@overleaf'],
- env: {
- es2020: true,
- },
- settings: {
- // Tell eslint-plugin-react to detect which version of React we are using
- react: {
- version: 'detect',
- },
- },
- rules: {
- 'no-constant-binary-expression': 'error',
- // do not allow importing of implicit dependencies.
- 'import/no-extraneous-dependencies': 'error',
- '@overleaf/prefer-kebab-url': 'error',
- // disable some TypeScript rules
- '@typescript-eslint/no-var-requires': 'off',
- '@typescript-eslint/no-unused-vars': 'off',
- '@typescript-eslint/no-empty-function': 'off',
- '@typescript-eslint/no-explicit-any': 'off',
- '@typescript-eslint/no-this-alias': 'off',
- '@typescript-eslint/no-non-null-assertion': 'off',
- '@typescript-eslint/ban-ts-comment': 'off',
- 'no-use-before-define': 'off',
- '@typescript-eslint/no-use-before-define': [
- 'error',
- { functions: false, classes: false, variables: false },
- ],
- },
- overrides: [
- // NOTE: changing paths may require updating them in the Makefile too.
- {
- // Node
- files: [
- '**/app/src/**/*.{js,mjs}',
- 'app.{js,mjs}',
- 'i18next-scanner.config.js',
- 'scripts/**/*.{js,mjs}',
- 'webpack.config*.js',
- ],
- env: {
- node: true,
- },
- },
- {
- // Test specific rules
- files: ['**/test/**/*.*'],
- plugins: ['mocha', 'chai-expect', 'chai-friendly'],
- env: {
- mocha: true,
- },
- rules: {
- // mocha-specific rules
- 'mocha/handle-done-callback': 'error',
- 'mocha/no-exclusive-tests': 'error',
- 'mocha/no-global-tests': 'error',
- 'mocha/no-identical-title': 'error',
- 'mocha/no-nested-tests': 'error',
- 'mocha/no-pending-tests': 'error',
- 'mocha/no-skipped-tests': 'error',
- 'mocha/no-mocha-arrows': 'error',
- // Swap the no-unused-expressions rule with a more chai-friendly one
- 'no-unused-expressions': 'off',
- 'chai-friendly/no-unused-expressions': 'error',
- // chai-specific rules
- 'chai-expect/missing-assertion': 'error',
- 'chai-expect/terminating-properties': 'error',
- // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests.
- // we don't enforce this at the top-level - just in tests to manage `this` scope
- // based on mocha's context mechanism
- 'mocha/prefer-arrow-callback': 'error',
- '@typescript-eslint/no-unused-expressions': 'off',
- },
- },
- {
- // ES specific rules
- files: [
- '**/app/src/**/*.mjs',
- 'modules/*/index.mjs',
- 'app.mjs',
- 'scripts/**/*.mjs',
- 'migrations/**/*.mjs',
- ],
- excludedFiles: [
- // migration template file
- 'migrations/lib/template.mjs',
- ],
- parserOptions: {
- sourceType: 'module',
- },
- plugins: ['unicorn'],
- rules: {
- 'import/no-unresolved': [
- 'error',
- {
- // eslint-plugin-import does not support exports directive in package.json
- // https://github.com/import-js/eslint-plugin-import/issues/1810
- ignore: ['^p-queue$'],
- },
- ],
- 'import/extensions': [
- 'error',
- 'ignorePackages',
- {
- js: 'always',
- mjs: 'always',
- },
- ],
- 'unicorn/prefer-module': 'error',
- 'unicorn/prefer-node-protocol': 'error',
- },
- },
- {
- // Backend specific rules
- files: ['**/app/src/**/*.{js,mjs}', 'app.{js,mjs}'],
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: './tsconfig.backend.json',
- },
- rules: {
- // do not allow importing of implicit dependencies.
- 'import/no-extraneous-dependencies': [
- 'error',
- {
- // do not allow importing of devDependencies.
- devDependencies: false,
- },
- ],
- 'no-restricted-syntax': [
- 'error',
- // do not allow node-fetch in backend code
- {
- selector:
- "CallExpression[callee.name='require'] > .arguments[value='node-fetch']",
- message:
- 'Requiring node-fetch is not allowed in production services, please use fetch-utils.',
- },
- // mongoose populate must set fields to populate
- {
- selector:
- "CallExpression[callee.property.name='populate'][arguments.length<2]",
- message:
- "Populate without a second argument returns the whole document. Use populate('field',['prop1','prop2']) instead",
- },
- // Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
- message:
- 'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`',
- },
- // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
- message:
- "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead",
- },
- // Catch incorrect usage of `await db.collection.find()`
- {
- selector:
- "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']",
- message:
- 'Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead.',
- },
- ],
- '@typescript-eslint/no-floating-promises': [
- 'error',
- { checkThenables: true },
- ],
- },
- },
- {
- // Backend scripts specific rules
- files: ['**/scripts/**/*.js'],
- rules: {
- 'no-restricted-syntax': [
- 'error',
- // Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
- message:
- 'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`',
- },
- // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
- message:
- "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead",
- },
- // Catch incorrect usage of `await db.collection.find()`
- {
- selector:
- "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']",
- message:
- 'Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead.',
- },
- ],
- },
- },
- {
- // Cypress specific rules
- files: [
- 'cypress/**/*.{js,jsx,ts,tsx}',
- '**/test/frontend/**/*.spec.{js,jsx,ts,tsx}',
- ],
- extends: ['plugin:cypress/recommended'],
- },
- {
- // Frontend test specific rules
- files: ['**/frontend/**/*.test.{js,jsx,ts,tsx}'],
- plugins: ['testing-library'],
- extends: ['plugin:testing-library/react'],
- rules: {
- 'testing-library/no-await-sync-events': 'off',
- 'testing-library/no-await-sync-queries': 'off',
- 'testing-library/no-container': 'off',
- 'testing-library/no-node-access': 'off',
- 'testing-library/no-render-in-lifecycle': 'off',
- 'testing-library/no-wait-for-multiple-assertions': 'off',
- 'testing-library/no-wait-for-side-effects': 'off',
- 'testing-library/prefer-query-by-disappearance': 'off',
- 'testing-library/prefer-screen-queries': 'off',
- 'testing-library/render-result-naming-convention': 'off',
- },
- },
- {
- // Frontend specific rules
- files: [
- '**/frontend/js/**/*.{js,jsx,ts,tsx}',
- '**/frontend/stories/**/*.{js,jsx,ts,tsx}',
- '**/*.stories.{js,jsx,ts,tsx}',
- '**/test/frontend/**/*.{js,jsx,ts,tsx}',
- '**/test/frontend/components/**/*.spec.{js,jsx,ts,tsx}',
- ],
- env: {
- browser: true,
- },
- parserOptions: {
- sourceType: 'module',
- },
- plugins: ['jsx-a11y'],
- extends: [
- 'plugin:react/recommended',
- 'plugin:react-hooks/recommended',
- 'plugin:jsx-a11y/recommended',
- 'standard-jsx',
- 'prettier',
- ],
- globals: {
- __webpack_public_path__: true,
- $: true,
- ga: true,
- },
- rules: {
- // TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed.
- // START: inline standard-react rules
- // "react/jsx-no-bind": ["error", {
- // "allowArrowFunctions": true,
- // "allowBind": false,
- // "ignoreRefs": true
- // },],
- 'react/no-did-update-set-state': 'error',
- 'react/no-unused-prop-types': 'error',
- 'react/prop-types': 'error',
- // "react/react-in-jsx-scope": "error",
- // END: inline standard-react rules
- 'react/no-unknown-property': [
- 'error',
- {
- ignore: ['dnd-container', 'dropdown-toggle'],
- },
- ],
- 'react/jsx-no-target-blank': [
- 'error',
- {
- allowReferrer: true,
- },
- ],
- // Prevent usage of legacy string refs
- 'react/no-string-refs': 'error',
- // Prevent curly braces around strings (as they're unnecessary)
- 'react/jsx-curly-brace-presence': [
- 'error',
- {
- props: 'never',
- children: 'never',
- },
- ],
- // Don't import React for JSX; the JSX runtime is added by a Babel plugin
- 'react/react-in-jsx-scope': 'off',
- 'react/jsx-uses-react': 'off',
- // Allow functions as JSX props
- 'react/jsx-no-bind': 'off', // TODO: fix occurrences and re-enable this
- // Fix conflict between prettier & standard by overriding to prefer
- // double quotes
- 'jsx-quotes': ['error', 'prefer-double'],
- // Override weird behaviour of jsx-a11y label-has-for (says labels must be
- // nested *and* have for/id attributes)
- 'jsx-a11y/label-has-for': [
- 'error',
- {
- required: {
- some: ['nesting', 'id'],
- },
- },
- ],
- // Require .jsx or .tsx file extension when using JSX
- 'react/jsx-filename-extension': [
- 'error',
- {
- extensions: ['.jsx', '.tsx'],
- },
- ],
- 'no-restricted-syntax': [
- 'error',
- // prohibit direct calls to methods of window.localStorage
- {
- selector:
- "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
- message:
- 'Modify location via customLocalStorage instead of calling window.localStorage methods directly',
- },
- ],
- },
- },
- {
- // Sorting for Meta
- files: ['frontend/js/utils/meta.ts'],
- rules: {
- '@typescript-eslint/member-ordering': [
- 'error',
- { interfaces: { order: 'alphabetically' } },
- ],
- },
- },
- {
- // React component specific rules
- //
- files: [
- '**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}',
- '**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}',
- ],
- rules: {
- '@overleaf/no-unnecessary-trans': 'error',
- '@overleaf/should-unescape-trans': 'error',
- // https://astexplorer.net/
- 'no-restricted-syntax': [
- 'error',
- // prohibit direct calls to methods of window.location
- {
- selector:
- "CallExpression[callee.object.object.name='window'][callee.object.property.name='location']",
- message:
- 'Modify location via useLocation instead of calling window.location methods directly',
- },
- // prohibit assignment to window.location
- {
- selector:
- "AssignmentExpression[left.object.name='window'][left.property.name='location']",
- message:
- 'Modify location via useLocation instead of calling window.location methods directly',
- },
- // prohibit assignment to window.location.href
- {
- selector:
- "AssignmentExpression[left.object.object.name='window'][left.object.property.name='location'][left.property.name='href']",
- message:
- 'Modify location via useLocation instead of calling window.location methods directly',
- },
- // prohibit using lookbehinds due to incidents with Safari simply crashing when the script is parsed
- {
- selector: 'Literal[regex.pattern=/\\(\\?<[!=]/]',
- message: 'Lookbehind is not supported in older Safari versions.',
- },
- // prohibit direct calls to methods of window.localStorage
- // NOTE: this rule is also defined for all frontend files, but those rules are overriden by the React component-specific config
- {
- selector:
- "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
- message:
- 'Modify location via customLocalStorage instead of calling window.localStorage methods directly',
- },
- ],
- },
- },
- // React + TypeScript-specific rules
- {
- files: ['**/*.tsx'],
- rules: {
- 'react/prop-types': 'off',
- 'no-undef': 'off',
- },
- },
- // TypeScript-specific rules
- {
- files: ['**/*.ts'],
- rules: {
- 'no-undef': 'off',
- },
- },
- // JavaScript-specific rules
- {
- files: ['**/*.js'],
- rules: {
- '@typescript-eslint/no-require-imports': 'off',
- },
- },
- {
- files: ['scripts/ukamf/*.js'],
- rules: {
- // Do not allow importing of any dependencies unless specified in either
- // - web/package.json
- // - web/scripts/ukamf/package.json
- 'import/no-extraneous-dependencies': [
- 'error',
- { packageDir: ['.', 'scripts/ukamf'] },
- ],
- },
- },
- {
- files: ['scripts/learn/checkSanitize/*.js'],
- rules: {
- // The checkSanitize script is used in the dev-env only.
- 'import/no-extraneous-dependencies': [
- 'error',
- {
- devDependencies: true,
- packageDir: ['.', '../../'],
- },
- ],
- },
- },
- {
- files: [
- // Backend: Use @overleaf/logger
- // Docs: https://manual.dev-overleaf.com/development/code/logging/#structured-logging
- '**/app/**/*.{js,cjs,mjs}',
- 'app.{js,mjs}',
- 'modules/*/*.{js,mjs}',
- // Frontend: Prefer debugConsole over bare console
- // Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend
- '**/frontend/**/*.{js,jsx,ts,tsx}',
- // Tests
- '**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}',
- ],
- excludedFiles: [
- // Allow console logs in scripts
- '**/scripts/**/*.js',
- // Allow console logs in stories
- '**/stories/**/*.{js,jsx,ts,tsx}',
- // Workers do not have access to the search params for enabling ?debug=true.
- // self.location.url is the URL of the worker script.
- '*.worker.{js,ts}',
- ],
- rules: {
- 'no-console': 'error',
- },
- },
- ],
- }
|