2024-05-21 15:07:24 -04:00
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' ,
2024-06-21 03:23:57 -04:00
'@overleaf/prefer-kebab-url' : 'error' ,
2024-05-21 15:07:24 -04:00
// 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' ] ,
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" ,
} ,
] ,
'@typescript-eslint/no-floating-promises' : 'error' ,
} ,
} ,
{
// Backend tests and scripts specific rules
files : [ '**/test/**/*.*' , '**/scripts/*.*' ] ,
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 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 ,
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' ,
// 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
// 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' ,
} ,
] ,
} ,
} ,
// 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}' ,
// 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' ,
} ,
} ,
] ,
}