2018-02-22 14:25:26 -05:00
{
2020-12-15 05:23:54 -05:00
"root": true,
2022-03-15 09:17:43 -04:00
"parser": "@typescript-eslint/parser",
2018-10-30 06:03:05 -04:00
"extends": [
2020-12-15 05:23:54 -05:00
"eslint:recommended",
2022-03-15 09:17:43 -04:00
"plugin:@typescript-eslint/recommended",
2018-10-30 06:03:05 -04:00
"standard",
2021-03-03 07:52:13 -05:00
"prettier"
2018-10-30 06:03:05 -04:00
],
2023-10-19 04:27:45 -04:00
"plugins": ["@overleaf"],
2018-02-27 06:19:40 -05:00
"env": {
2020-12-15 05:23:54 -05:00
"es2020": true
},
2018-10-30 06:03:05 -04:00
"settings": {
2019-01-17 04:52:07 -05:00
// Tell eslint-plugin-react to detect which version of React we are using
2018-10-30 06:03:05 -04:00
"react": {
2019-01-17 04:52:07 -05:00
"version": "detect"
2018-10-30 06:03:05 -04:00
}
},
2018-02-28 06:45:30 -05:00
"rules": {
2024-01-15 05:03:47 -05:00
"no-constant-binary-expression": "error",
2021-05-05 09:06:07 -04:00
// do not allow importing of implicit dependencies.
2022-03-15 09:17:43 -04:00
"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",
2022-05-27 08:32:27 -04:00
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/ban-ts-comment": "off"
2019-08-07 10:04:04 -04:00
},
"overrides": [
2020-07-02 11:51:38 -04:00
// NOTE: changing paths may require updating them in the Makefile too.
2021-10-06 07:24:20 -04:00
{
// Node
"files": [
"**/app/src/**/*.js",
"app.js",
"i18next-scanner.config.js",
"scripts/**/*.js",
"webpack.config*.js"
],
"env": {
"node": true
}
},
2019-08-07 10:04:04 -04:00
{
2019-10-14 09:17:30 -04:00
// Test specific rules
2022-03-15 11:53:25 -04:00
"files": ["**/test/**/*.*"],
2021-10-06 07:24:20 -04:00
"plugins": [
"mocha",
"chai-expect",
"chai-friendly"
],
"env": {
"mocha": true
2019-10-14 09:17:30 -04:00
},
2019-08-07 10:04:04 -04:00
"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",
2021-10-06 07:24:20 -04:00
// Swap the no-unused-expressions rule with a more chai-friendly one
"no-unused-expressions": "off",
"chai-friendly/no-unused-expressions": "error",
2019-08-07 10:04:04 -04:00
// 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"
}
2019-08-09 05:39:26 -04:00
},
2019-10-14 09:17:30 -04:00
{
// Backend specific rules
2021-10-06 07:24:20 -04:00
"files": ["**/app/src/**/*.js", "app.js"],
2019-08-09 05:39:26 -04:00
"rules": {
2021-05-05 09:06:07 -04:00
// do not allow importing of implicit dependencies.
"import/no-extraneous-dependencies": ["error", {
// do not allow importing of devDependencies.
"devDependencies": false
2023-09-08 08:35:48 -04:00
}],
"no-restricted-syntax": [
"error",
2024-04-11 09:27:17 -04:00
// do not allow node-fetch in backend code
2023-09-08 08:35:48 -04:00
{
"selector": "CallExpression[callee.name='require'] > .arguments[value='node-fetch']",
"message": "Requiring node-fetch is not allowed in production services, please use fetch-utils."
2024-04-11 09:27:17 -04:00
},
// 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"
2023-09-08 08:35:48 -04:00
}
]
2019-08-09 05:39:26 -04:00
}
2019-10-14 09:17:30 -04:00
},
2023-12-18 05:54:01 -05:00
{
2024-04-11 09:27:17 -04:00
// Backend tests and scripts specific rules
"files": ["**/test/**/*.*", "**/scripts/*.*"],
2023-12-18 05:54:01 -05:00
"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"
2024-05-16 09:34:58 -04:00
},
// 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."
2023-12-18 05:54:01 -05:00
}
]
}
},
2022-03-30 05:49:41 -04:00
{
// Cypress specific rules
2023-09-28 11:32:24 -04:00
"files": ["cypress/**/*.{js,jsx,ts,tsx}", "**/test/frontend/**/*.spec.{js,jsx,ts,tsx}"],
2022-03-30 05:49:41 -04:00
"extends": [
"plugin:cypress/recommended"
]
},
2019-10-14 09:17:30 -04:00
{
// Frontend specific rules
2023-09-28 11:32:24 -04:00
"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}"],
2021-10-06 07:24:20 -04:00
"env": {
"browser": true
},
"parserOptions": {
2023-09-28 11:32:24 -04:00
"sourceType": "module"
2021-10-06 07:24:20 -04:00
},
"plugins": [
"jsx-a11y"
],
"extends": [
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"standard-jsx",
"prettier"
],
2019-10-14 09:17:30 -04:00
"globals": {
2021-07-12 06:40:29 -04:00
"__webpack_public_path__": true,
2019-10-14 09:17:30 -04:00
"$": true,
"angular": true,
"ga": true,
// Injected in layout.pug
2020-05-12 10:23:53 -04:00
"user_id": true,
"ExposedSettings": true
2020-07-20 09:21:36 -04:00
},
"rules": {
2022-02-07 06:46:51 -05:00
// 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
2023-05-24 06:05:09 -04:00
"react/no-unknown-property": ["error", {
"ignore": ["dnd-container", "dropdown-toggle"]
}],
2021-09-14 06:43:57 -04:00
"react/jsx-no-target-blank": ["error", {
"allowReferrer": true
}],
2020-07-20 09:21:36 -04:00
// Prevent usage of legacy string refs
"react/no-string-refs": "error",
2021-01-05 05:56:15 -05:00
// Prevent curly braces around strings (as they're unnecessary)
"react/jsx-curly-brace-presence": ["error", {
"props": "never",
"children": "never"
}],
2021-06-23 05:37:08 -04:00
// 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",
2021-12-07 06:30:08 -05:00
// Allow functions as JSX props
"react/jsx-no-bind": "off", // TODO: fix occurrences and re-enable this
2020-07-20 09:21:36 -04:00
// 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"
]
}
}
2023-09-28 11:32:24 -04:00
],
// Require .jsx or .tsx file extension when using JSX
"react/jsx-filename-extension": ["error", {
"extensions": [".jsx", ".tsx"]
2023-10-17 04:36:46 -04:00
}],
"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) {}]"
2024-03-26 05:05:06 -04:00
},
2023-10-17 04:36:46 -04:00
// End: Make sure angular can withstand minification
2024-03-26 05:05:06 -04:00
// 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"
}
]
}
},
{
// 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"
}
2023-10-17 04:36:46 -04:00
]
2019-10-14 09:17:30 -04:00
}
2021-05-05 09:06:07 -04:00
},
2022-03-15 09:17:43 -04:00
// React + TypeScript-specific rules
{
"files": ["**/*.tsx"],
"rules": {
"react/prop-types": "off",
"no-undef": "off"
}
},
// TypeScript-specific rules
{
"files": ["**/*.ts"],
"rules": {
"no-undef": "off"
}
},
2021-05-05 09:06:07 -04:00
{
"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"]}]
}
2022-05-16 10:25:49 -04:00
},
{
"files": ["scripts/learn/checkSanitize/*.js"],
"rules": {
// The checkSanitize script is used in the dev-env only.
"import/no-extraneous-dependencies": ["error", {
"devDependencies": true,
"packageDir": [".", "../../"]
}]
}
2023-09-27 05:45:49 -04:00
},
{
"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
2023-11-10 08:40:13 -05:00
"**/frontend/**/*.{js,jsx,ts,tsx}",
// Tests
"**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}"
2023-09-27 05:45:49 -04:00
],
"excludedFiles": [
// Allow console logs in scripts
"**/scripts/**/*.js",
// Allow console logs in stories
2023-09-28 11:32:24 -04:00
"**/stories/**/*.{js,jsx,ts,tsx}",
2023-09-27 05:45:49 -04:00
// 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"
}
2019-08-07 10:04:04 -04:00
}
]
2018-03-02 11:06:48 -05:00
}