{ "root": true, "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "standard", "prettier" ], "env": { "es2020": true }, "settings": { // Tell eslint-plugin-react to detect which version of React we are using "react": { "version": "detect" } }, "rules": { // do not allow importing of implicit dependencies. "import/no-extraneous-dependencies": "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" }, "overrides": [ // NOTE: changing paths may require updating them in the Makefile too. { // Node "files": [ "**/app/src/**/*.js", "app.js", "i18next-scanner.config.js", "scripts/**/*.js", "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" } }, { // Backend specific rules "files": ["**/app/src/**/*.js", "app.js"], "rules": { // do not allow importing of implicit dependencies. "import/no-extraneous-dependencies": ["error", { // do not allow importing of devDependencies. "devDependencies": false }], // do not allow node-fetch in backend code "no-restricted-syntax": [ "error", { "selector": "CallExpression[callee.name='require'] > .arguments[value='node-fetch']", "message": "Requiring node-fetch is not allowed in production services, please use fetch-utils." } ] } }, { // Cypress specific rules "files": ["cypress/**/*.{js,jsx,ts,tsx}", "**/test/frontend/**/*.spec.{js,jsx,ts,tsx}"], "extends": [ "plugin:cypress/recommended" ] }, { // React component specific rules // "files": ["**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}", "**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}"], "rules": { // 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." } ] } }, { // 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, "angular": true, "ace": true, "ga": true, // Injected in layout.pug "user_id": true, "ExposedSettings": 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", // Begin: Make sure angular can withstand minification { "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]", "message": "Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]" }, { "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression", "message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}" }, { "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression", "message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}" }, { "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child", "message": "Last element of the array must be a function. E.g ['param1', function(param1) {}]" }, { "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]", "message": "Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]" } // End: Make sure angular can withstand minification ] } }, // React + TypeScript-specific rules { "files": ["**/*.tsx"], "rules": { "react/prop-types": "off", "no-undef": "off" } }, // TypeScript-specific rules { "files": ["**/*.ts"], "rules": { "no-undef": "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", "modules/*/*.js", // Frontend: Prefer debugConsole over bare console // Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend "**/frontend/**/*.{js,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" } } ] }