overleaf/services/web/.eslintrc

371 lines
14 KiB
Text
Raw Normal View History

2018-02-22 14:25:26 -05:00
{
"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",
// 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
}],
"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"
}
]
}
},
{
// 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,
// 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
// 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"
}
}
]
}