eslint.config.mjs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. // @ts-check
  2. /**
  3. * Understanding & making changes to this file:
  4. *
  5. * This is your friend:
  6. * `npx eslint --inspect-config`
  7. */
  8. import * as emotion from '@emotion/eslint-plugin';
  9. import {fixupPluginRules} from '@eslint/compat';
  10. import importPlugin from 'eslint-plugin-import';
  11. import jest from 'eslint-plugin-jest';
  12. import jestDom from 'eslint-plugin-jest-dom';
  13. import prettier from 'eslint-plugin-prettier/recommended';
  14. import react from 'eslint-plugin-react';
  15. import reactHooks from 'eslint-plugin-react-hooks';
  16. import sentry from 'eslint-plugin-sentry';
  17. import simpleImportSort from 'eslint-plugin-simple-import-sort';
  18. import testingLibrary from 'eslint-plugin-testing-library';
  19. import typescriptSortKeys from 'eslint-plugin-typescript-sort-keys';
  20. import globals from 'globals';
  21. import invariant from 'invariant';
  22. // biome-ignore lint/correctness/noNodejsModules: Need to get the list of things!
  23. import {builtinModules} from 'node:module';
  24. import typescript from 'typescript-eslint';
  25. invariant(react.configs.flat, 'For typescript');
  26. const baseRules = {
  27. /**
  28. * Strict mode
  29. */
  30. // https://eslint.org/docs/rules/strict
  31. strict: ['error', 'global'],
  32. /**
  33. * Variables
  34. */
  35. // https://eslint.org/docs/rules/no-shadow-restricted-names
  36. 'no-shadow-restricted-names': ['error'],
  37. /**
  38. * Possible errors
  39. */
  40. // https://eslint.org/docs/rules/no-cond-assign
  41. 'no-cond-assign': ['error', 'always'],
  42. // https://eslint.org/docs/rules/no-alert
  43. 'no-alert': ['error'],
  44. // https://eslint.org/docs/rules/no-constant-condition
  45. 'no-constant-condition': ['warn'],
  46. // https://eslint.org/docs/rules/no-empty
  47. 'no-empty': ['error'],
  48. // https://eslint.org/docs/rules/no-ex-assign
  49. 'no-ex-assign': ['error'],
  50. // https://eslint.org/docs/rules/no-extra-boolean-cast
  51. 'no-extra-boolean-cast': ['error'],
  52. // https://eslint.org/docs/rules/no-func-assign
  53. 'no-func-assign': ['error'],
  54. // https://eslint.org/docs/rules/no-inner-declarations
  55. 'no-inner-declarations': ['error'],
  56. // https://eslint.org/docs/rules/no-invalid-regexp
  57. 'no-invalid-regexp': ['error'],
  58. // https://eslint.org/docs/rules/no-irregular-whitespace
  59. 'no-irregular-whitespace': ['error'],
  60. // https://eslint.org/docs/rules/no-obj-calls
  61. 'no-obj-calls': ['error'],
  62. // https://eslint.org/docs/rules/no-sparse-arrays
  63. 'no-sparse-arrays': ['error'],
  64. // https://eslint.org/docs/rules/block-scoped-var
  65. 'block-scoped-var': ['error'],
  66. /**
  67. * Best practices
  68. */
  69. // https://eslint.org/docs/rules/consistent-return
  70. 'consistent-return': ['error'],
  71. // https://eslint.org/docs/rules/default-case
  72. 'default-case': ['error'],
  73. // https://eslint.org/docs/rules/dot-notation
  74. 'dot-notation': [
  75. 'error',
  76. {
  77. allowKeywords: true,
  78. },
  79. ],
  80. // https://eslint.org/docs/rules/guard-for-in [REVISIT ME]
  81. 'guard-for-in': ['off'],
  82. // https://eslint.org/docs/rules/no-caller
  83. 'no-caller': ['error'],
  84. // https://eslint.org/docs/rules/no-eval
  85. 'no-eval': ['error'],
  86. // https://eslint.org/docs/rules/no-extend-native
  87. 'no-extend-native': ['error'],
  88. // https://eslint.org/docs/rules/no-extra-bind
  89. 'no-extra-bind': ['error'],
  90. // https://eslint.org/docs/rules/no-fallthrough
  91. 'no-fallthrough': ['error'],
  92. // https://eslint.org/docs/rules/no-floating-decimal
  93. 'no-floating-decimal': ['error'],
  94. // https://eslint.org/docs/rules/no-implied-eval
  95. 'no-implied-eval': ['error'],
  96. // https://eslint.org/docs/rules/no-lone-blocks
  97. 'no-lone-blocks': ['error'],
  98. // https://eslint.org/docs/rules/no-loop-func
  99. 'no-loop-func': ['error'],
  100. // https://eslint.org/docs/rules/no-multi-str
  101. 'no-multi-str': ['error'],
  102. // https://eslint.org/docs/rules/no-native-reassign
  103. 'no-native-reassign': ['error'],
  104. // https://eslint.org/docs/rules/no-new
  105. 'no-new': ['error'],
  106. // https://eslint.org/docs/rules/no-new-func
  107. 'no-new-func': ['error'],
  108. // https://eslint.org/docs/rules/no-new-wrappers
  109. 'no-new-wrappers': ['error'],
  110. // https://eslint.org/docs/rules/no-octal
  111. 'no-octal': ['error'],
  112. // https://eslint.org/docs/rules/no-octal-escape
  113. 'no-octal-escape': ['error'],
  114. // https://eslint.org/docs/rules/no-param-reassign [REVISIT ME]
  115. 'no-param-reassign': ['off'],
  116. // https://eslint.org/docs/rules/no-proto
  117. 'no-proto': ['error'],
  118. // https://eslint.org/docs/rules/no-return-assign
  119. 'no-return-assign': ['error'],
  120. // https://eslint.org/docs/rules/no-script-url
  121. 'no-script-url': ['error'],
  122. // https://eslint.org/docs/rules/no-self-compare
  123. 'no-self-compare': ['error'],
  124. // https://eslint.org/docs/rules/no-sequences
  125. 'no-sequences': ['error'],
  126. // https://eslint.org/docs/rules/no-throw-literal
  127. 'no-throw-literal': ['error'],
  128. // https://eslint.org/docs/rules/no-with
  129. 'no-with': ['error'],
  130. // https://eslint.org/docs/rules/radix
  131. radix: ['error'],
  132. // https://eslint.org/docs/rules/object-shorthand
  133. 'object-shorthand': ['error', 'properties'],
  134. // https://eslint.org/docs/rules/vars-on-top
  135. 'vars-on-top': ['off'],
  136. // https://eslint.org/docs/rules/wrap-iife
  137. 'wrap-iife': ['error', 'any'],
  138. // https://eslint.org/docs/rules/array-callback-return
  139. 'array-callback-return': ['error'],
  140. // https://eslint.org/docs/rules/yoda
  141. yoda: ['error'],
  142. // https://eslint.org/docs/rules/no-else-return
  143. 'no-else-return': ['error', {allowElseIf: false}],
  144. // https://eslint.org/docs/rules/require-await
  145. 'require-await': ['error'],
  146. // https://eslint.org/docs/rules/multiline-comment-style
  147. 'multiline-comment-style': ['error', 'separate-lines'],
  148. // https://eslint.org/docs/rules/spaced-comment
  149. 'spaced-comment': [
  150. 'error',
  151. 'always',
  152. {
  153. line: {markers: ['/'], exceptions: ['-', '+']},
  154. block: {exceptions: ['*'], balanced: true},
  155. },
  156. ],
  157. };
  158. const reactReactRules = {
  159. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
  160. 'react/display-name': ['off'],
  161. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md
  162. 'react/no-multi-comp': [
  163. 'off',
  164. {
  165. ignoreStateless: true,
  166. },
  167. ],
  168. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-fragments.md
  169. 'react/jsx-fragments': ['error', 'element'],
  170. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
  171. // Ensures that any component or prop methods used to handle events are correctly prefixed.
  172. 'react/jsx-handler-names': [
  173. 'off',
  174. {
  175. eventHandlerPrefix: 'handle',
  176. eventHandlerPropPrefix: 'on',
  177. },
  178. ],
  179. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
  180. 'react/jsx-key': ['error'],
  181. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
  182. 'react/jsx-no-undef': ['error'],
  183. // Disabled as we use the newer JSX transform babel plugin.
  184. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
  185. 'react/jsx-uses-react': ['off'],
  186. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
  187. 'react/jsx-uses-vars': ['error'],
  188. /**
  189. * Deprecation related rules
  190. */
  191. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md
  192. 'react/no-deprecated': ['error'],
  193. // Prevent usage of the return value of React.render
  194. // deprecation: https://facebook.github.io/react/docs/react-dom.html#render
  195. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md
  196. 'react/no-render-return-value': ['error'],
  197. // Children should always be actual children, not passed in as a prop.
  198. // When using JSX, the children should be nested between the opening and closing tags. When not using JSX, the children should be passed as additional arguments to React.createElement.
  199. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md
  200. 'react/no-children-prop': ['error'],
  201. // This rule helps prevent problems caused by using children and the dangerouslySetInnerHTML prop at the same time.
  202. // React will throw a warning if this rule is ignored.
  203. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md
  204. 'react/no-danger-with-children': ['error'],
  205. // Prevent direct mutation of this.state
  206. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md
  207. 'react/no-direct-mutation-state': ['error'],
  208. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
  209. 'react/no-did-mount-set-state': ['error'],
  210. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md"
  211. 'react/no-did-update-set-state': ['error'],
  212. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-redundant-should-component-update.md
  213. 'react/no-redundant-should-component-update': ['error'],
  214. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-typos.md
  215. 'react/no-typos': ['error'],
  216. // Prevent invalid characters from appearing in markup
  217. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md
  218. 'react/no-unescaped-entities': ['off'],
  219. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
  220. 'react/no-unknown-property': ['error', {ignore: ['css']}],
  221. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md
  222. // Disabled since this currently fails to correctly detect a lot of
  223. // typescript prop type usage.
  224. 'react/no-unused-prop-types': ['off'],
  225. // We do not need proptypes since we're using typescript
  226. 'react/prop-types': ['off'],
  227. // When writing the render method in a component it is easy to forget to return the JSX content.
  228. // This rule will warn if the return statement is missing.
  229. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md
  230. 'react/require-render-return': ['error'],
  231. // Disabled as we are using the newer JSX transform babel plugin.
  232. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
  233. 'react/react-in-jsx-scope': ['off'],
  234. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
  235. 'react/self-closing-comp': ['error'],
  236. // This also causes issues with typescript
  237. // See: https://github.com/yannickcr/eslint-plugin-react/issues/2066
  238. //
  239. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
  240. 'react/sort-comp': ['warn'],
  241. // Consistent <Component booleanProp /> (never add ={true})
  242. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md
  243. 'react/jsx-boolean-value': ['error', 'never'],
  244. // Consistent function component declaration styles
  245. // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/function-component-definition.md
  246. 'react/function-component-definition': [
  247. 'error',
  248. {namedComponents: 'function-declaration'},
  249. ],
  250. };
  251. const reactImportRules = {
  252. // Not recommended to be enabled with typescript-eslint
  253. // https://typescript-eslint.io/linting/troubleshooting/performance-troubleshooting/#eslint-plugin-import
  254. 'import/no-unresolved': ['off'],
  255. 'import/named': ['off'],
  256. 'import/default': ['off'],
  257. 'import/export': ['off'],
  258. 'import/no-named-as-default-member': ['off'],
  259. // Redflags
  260. // do not allow a default import name to match a named export (airbnb: error)
  261. // Issue with `DefaultIssuePlugin` and `app/plugins/index`
  262. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default.md
  263. 'import/no-named-as-default': ['off'],
  264. // disallow use of jsdoc-marked-deprecated imports
  265. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-deprecated.md
  266. 'import/no-deprecated': ['off'],
  267. // Forbid mutable exports (airbnb: error)
  268. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md
  269. // TODO: enable?
  270. 'import/no-mutable-exports': ['off'],
  271. // disallow require()
  272. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-commonjs.md
  273. 'import/no-commonjs': ['off'],
  274. // disallow AMD require/define
  275. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-amd.md
  276. 'import/no-amd': ['error'],
  277. // disallow duplicate imports
  278. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md
  279. 'import/no-duplicates': ['error'],
  280. // disallow namespace imports
  281. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-namespace.md
  282. 'import/no-namespace': ['off'],
  283. // Ensure consistent use of file extension within the import path
  284. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md
  285. // TODO this fucks up getsentry
  286. 'import/extensions': [
  287. 'off',
  288. 'always',
  289. {
  290. js: 'never',
  291. jsx: 'never',
  292. },
  293. ],
  294. // Require a newline after the last import/require in a group
  295. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/newline-after-import.md
  296. 'import/newline-after-import': ['error'],
  297. // Require modules with a single export to use a default export (airbnb: error)
  298. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/prefer-default-export.md
  299. 'import/prefer-default-export': ['off'],
  300. // Restrict which files can be imported in a given folder
  301. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-restricted-paths.md
  302. 'import/no-restricted-paths': ['off'],
  303. // Forbid modules to have too many dependencies
  304. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/max-dependencies.md
  305. 'import/max-dependencies': ['off', {max: 10}],
  306. // Forbid import of modules using absolute paths
  307. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-absolute-path.md
  308. 'import/no-absolute-path': ['error'],
  309. // Forbid require() calls with expressions (airbnb: error)
  310. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-dynamic-require.md
  311. 'import/no-dynamic-require': ['off'],
  312. // Use webpack default chunk names
  313. 'import/dynamic-import-chunkname': ['off'],
  314. // prevent importing the submodules of other modules
  315. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-internal-modules.md
  316. 'import/no-internal-modules': [
  317. 'off',
  318. {
  319. allow: [],
  320. },
  321. ],
  322. // Warn if a module could be mistakenly parsed as a script by a consumer
  323. // leveraging Unambiguous JavaScript Grammar
  324. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/unambiguous.md
  325. // this should not be enabled until this proposal has at least been *presented* to TC39.
  326. // At the moment, it"s not a thing.
  327. 'import/unambiguous': ['off'],
  328. // Forbid Webpack loader syntax in imports
  329. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md
  330. 'import/no-webpack-loader-syntax': ['error'],
  331. // Prevent unassigned imports
  332. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unassigned-import.md
  333. // importing for side effects is perfectly acceptable, if you need side effects.
  334. 'import/no-unassigned-import': ['off'],
  335. // Prevent importing the default as if it were named
  336. // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-default.md
  337. 'import/no-named-default': ['error'],
  338. // Reports if a module"s default export is unnamed
  339. // https://github.com/benmosher/eslint-plugin-import/blob/d9b712ac7fd1fddc391f7b234827925c160d956f/docs/rules/no-anonymous-default-export.md
  340. 'import/no-anonymous-default-export': [
  341. 'error',
  342. {
  343. allowArray: false,
  344. allowArrowFunction: false,
  345. allowAnonymousClass: false,
  346. allowAnonymousFunction: false,
  347. allowCallExpression: true,
  348. allowLiteral: false,
  349. allowObject: false,
  350. },
  351. ],
  352. };
  353. const reactRules = {
  354. ...reactReactRules,
  355. ...reactImportRules,
  356. /**
  357. * React hooks
  358. */
  359. 'react-hooks/exhaustive-deps': [
  360. 'error',
  361. {additionalHooks: '(useEffectAfterFirstRender|useMemoWithPrevious)'},
  362. ],
  363. // Biome not yet enforcing all parts of this rule https://github.com/biomejs/biome/issues/1984
  364. 'react-hooks/rules-of-hooks': 'error',
  365. /**
  366. * Custom
  367. */
  368. // highlights literals in JSX components w/o translation tags
  369. 'getsentry/jsx-needs-il8n': ['off'],
  370. 'typescript-sort-keys/interface': [
  371. 'error',
  372. 'asc',
  373. {caseSensitive: true, natural: false, requiredFirst: true},
  374. ],
  375. };
  376. const appRules = {
  377. /**
  378. * emotion rules for v10
  379. *
  380. * This probably aren't as necessary anymore, but let's remove when we move to v11
  381. */
  382. '@emotion/jsx-import': 'off',
  383. '@emotion/no-vanilla': 'error',
  384. '@emotion/import-from-emotion': 'error',
  385. '@emotion/styled-import': 'error',
  386. // no-undef is redundant with typescript as tsc will complain
  387. // A downside is that we won't get eslint errors about it, but your editors should
  388. // support tsc errors so....
  389. // https://eslint.org/docs/rules/no-undef
  390. 'no-undef': 'off',
  391. // Let formatter handle this
  392. 'arrow-body-style': 'off',
  393. /**
  394. * Need to use typescript version of these rules
  395. * https://eslint.org/docs/rules/no-shadow
  396. */
  397. 'no-shadow': 'off',
  398. '@typescript-eslint/no-shadow': 'error',
  399. // This only override the `args` rule (which is "none"). There are too many errors and it's difficult to manually
  400. // fix them all, so we'll have to incrementally update.
  401. // https://eslint.org/docs/rules/no-unused-vars
  402. 'no-unused-vars': 'off',
  403. '@typescript-eslint/no-unused-vars': [
  404. 'error',
  405. {
  406. vars: 'all',
  407. args: 'all',
  408. // TODO(scttcper): We could enable this to enforce catch (error)
  409. // https://eslint.org/docs/latest/rules/no-unused-vars#caughterrors
  410. caughtErrors: 'none',
  411. // Ignore vars that start with an underscore
  412. // e.g. if you want to omit a property using object spread:
  413. //
  414. // const {name: _name, ...props} = this.props;
  415. //
  416. varsIgnorePattern: '^_',
  417. argsIgnorePattern: '^_',
  418. destructuredArrayIgnorePattern: '^_',
  419. },
  420. ],
  421. // https://eslint.org/docs/rules/no-use-before-define
  422. 'no-use-before-define': 'off',
  423. // This seems to have been turned on while previously it had been off
  424. '@typescript-eslint/no-use-before-define': ['off'],
  425. /**
  426. * Restricted imports, e.g. deprecated libraries, etc
  427. *
  428. * See: https://eslint.org/docs/rules/no-restricted-imports
  429. */
  430. 'no-restricted-imports': [
  431. 'error',
  432. {
  433. patterns: [
  434. {
  435. group: ['sentry/components/devtoolbar/*'],
  436. message: 'Do not depend on toolbar internals',
  437. },
  438. {
  439. group: ['*.spec*'],
  440. message:
  441. 'Do not import from test files. This causes tests to be executed multiple times.',
  442. },
  443. ],
  444. paths: [
  445. {
  446. name: '@testing-library/react',
  447. message:
  448. 'Please import from `sentry-test/reactTestingLibrary` instead so that we can ensure consistency throughout the codebase',
  449. },
  450. {
  451. name: '@testing-library/react-hooks',
  452. message:
  453. 'Please import from `sentry-test/reactTestingLibrary` instead so that we can ensure consistency throughout the codebase',
  454. },
  455. {
  456. name: '@testing-library/user-event',
  457. message:
  458. 'Please import from `sentry-test/reactTestingLibrary` instead so that we can ensure consistency throughout the codebase',
  459. },
  460. {
  461. name: '@sentry/browser',
  462. message:
  463. 'Please import from `@sentry/react` to ensure consistency throughout the codebase.',
  464. },
  465. {
  466. name: 'marked',
  467. message:
  468. "Please import marked from 'app/utils/marked' so that we can ensure sanitation of marked output",
  469. },
  470. {
  471. name: 'lodash',
  472. message:
  473. "Please import lodash utilities individually. e.g. `import isEqual from 'lodash/isEqual';`. See https://github.com/getsentry/frontend-handbook#lodash from for information",
  474. },
  475. {
  476. name: 'lodash/get',
  477. message:
  478. 'Optional chaining `?.` and nullish coalescing operators `??` are available and preferred over using `lodash/get`. See https://github.com/getsentry/frontend-handbook#new-syntax for more information',
  479. },
  480. {
  481. name: 'sentry/utils/theme',
  482. importNames: ['lightColors', 'darkColors'],
  483. message:
  484. "'lightColors' and 'darkColors' exports intended for use in Storybook only. Instead, use theme prop from emotion or the useTheme hook.",
  485. },
  486. {
  487. name: 'react-router',
  488. importNames: ['withRouter'],
  489. message:
  490. "Use 'useLocation', 'useParams', 'useNavigate', 'useRoutes' from sentry/utils instead.",
  491. },
  492. {
  493. name: 'sentry/utils/withSentryRouter',
  494. message:
  495. "Use 'useLocation', 'useParams', 'useNavigate', 'useRoutes' from sentry/utils instead.",
  496. },
  497. {
  498. name: 'qs',
  499. message: 'Please use query-string instead of qs',
  500. },
  501. {
  502. name: 'moment',
  503. message: 'Please import moment-timezone instead of moment',
  504. },
  505. ],
  506. },
  507. ],
  508. /**
  509. * Better import sorting
  510. */
  511. 'sort-imports': 'off',
  512. 'import/order': 'off',
  513. 'simple-import-sort/imports': [
  514. 'error',
  515. {
  516. groups: [
  517. // Side effect imports.
  518. ['^\\u0000'],
  519. // Node.js builtins.
  520. [`^(${builtinModules.join('|')})(/|$)`],
  521. // Packages. `react` related packages come first.
  522. ['^react', '^@?\\w'],
  523. // Test should be separate from the app
  524. ['^(sentry-test|getsentry-test)(/.*|$)'],
  525. // Internal packages.
  526. ['^(sentry-locale|sentry-images)(/.*|$)'],
  527. ['^(getsentry-images)(/.*|$)'],
  528. ['^(app|sentry)(/.*|$)'],
  529. // Getsentry packages.
  530. ['^(admin|getsentry)(/.*|$)'],
  531. // Style imports.
  532. ['^.+\\.less$'],
  533. // Parent imports. Put `..` last.
  534. ['^\\.\\.(?!/?$)', '^\\.\\./?$'],
  535. // Other relative imports. Put same-folder imports and `.` last.
  536. ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
  537. ],
  538. },
  539. ],
  540. 'sentry/no-digits-in-tn': ['error'],
  541. 'sentry/no-dynamic-translations': ['error'],
  542. // https://github.com/xojs/eslint-config-xo-typescript/blob/9791a067d6a119a21a4db72c02f1da95e25ffbb6/index.js#L95
  543. '@typescript-eslint/no-restricted-types': [
  544. 'error',
  545. {
  546. types: {
  547. // TODO(scttcper): Turn object on to make our types more strict
  548. // object: {
  549. // message: 'The `object` type is hard to use. Use `Record<string, unknown>` instead. See: https://github.com/typescript-eslint/typescript-eslint/pull/848',
  550. // fixWith: 'Record<string, unknown>'
  551. // },
  552. Buffer: {
  553. message:
  554. 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer',
  555. suggest: ['Uint8Array'],
  556. },
  557. '[]': "Don't use the empty array type `[]`. It only allows empty arrays. Use `SomeType[]` instead.",
  558. '[[]]':
  559. "Don't use `[[]]`. It only allows an array with a single element which is an empty array. Use `SomeType[][]` instead.",
  560. '[[[]]]': "Don't use `[[[]]]`. Use `SomeType[][][]` instead.",
  561. },
  562. },
  563. ],
  564. // TODO(scttcper): Turn no-empty-object-type on to make our types more strict
  565. // '@typescript-eslint/no-empty-object-type': 'error',
  566. // TODO(scttcper): Turn no-function on to make our types more strict
  567. // '@typescript-eslint/no-unsafe-function-type': 'error',
  568. '@typescript-eslint/no-wrapper-object-types': 'error',
  569. // Naming convention enforcements
  570. '@typescript-eslint/naming-convention': [
  571. 'error',
  572. {
  573. selector: 'typeLike',
  574. format: ['PascalCase'],
  575. leadingUnderscore: 'allow',
  576. },
  577. {
  578. selector: 'enumMember',
  579. format: ['UPPER_CASE'],
  580. },
  581. ],
  582. };
  583. const strictRules = {
  584. // https://eslint.org/docs/rules/no-console
  585. 'no-console': ['error'],
  586. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md
  587. 'react/no-is-mounted': ['error'],
  588. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md
  589. // Recommended to use callback refs instead
  590. 'react/no-find-dom-node': ['error'],
  591. // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md
  592. // This is now considered legacy, callback refs preferred
  593. 'react/no-string-refs': ['error'],
  594. 'sentry/no-styled-shortcut': ['error'],
  595. };
  596. // Used by both: `languageOptions` & `parserOptions`
  597. const ecmaVersion = 6; // TODO(ryan953): change to 'latest'
  598. /**
  599. * To get started with this ESLint Configuration list be sure to read at least
  600. * these sections of the docs:
  601. * - https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores
  602. * - https://eslint.org/docs/latest/use/configure/configuration-files#cascading-configuration-objects
  603. */
  604. export default typescript.config([
  605. {
  606. // Main parser & linter options
  607. // Rules are defined below and inherit these properties
  608. // https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects
  609. name: 'main',
  610. languageOptions: {
  611. ecmaVersion,
  612. sourceType: 'module',
  613. globals: {
  614. // TODO(ryan953): globals.browser seems to have a bug with trailing whitespace
  615. ...Object.fromEntries(
  616. Object.keys(globals.browser).map(key => [key.trim(), false])
  617. ),
  618. ...globals.jest,
  619. MockApiClient: true,
  620. tick: true,
  621. },
  622. parser: typescript.parser,
  623. parserOptions: {
  624. ecmaFeatures: {
  625. globalReturn: false,
  626. },
  627. ecmaVersion,
  628. // https://typescript-eslint.io/packages/parser/#emitdecoratormetadata
  629. emitDecoratorMetadata: undefined,
  630. // https://typescript-eslint.io/packages/parser/#experimentaldecorators
  631. experimentalDecorators: undefined,
  632. // https://typescript-eslint.io/packages/parser/#jsdocparsingmode
  633. jsDocParsingMode: process.env.SENTRY_DETECT_DEPRECATIONS ? 'all' : 'none',
  634. // https://typescript-eslint.io/packages/parser/#project
  635. project: './tsconfig.json',
  636. // https://typescript-eslint.io/packages/parser/#projectservice
  637. // `projectService` is recommended, but slower, with our current tsconfig files.
  638. // projectService: true,
  639. // tsconfigRootDir: import.meta.dirname,
  640. },
  641. },
  642. linterOptions: {
  643. noInlineConfig: false,
  644. reportUnusedDisableDirectives: 'error',
  645. },
  646. // TODO: move these potential overrides and plugin-specific rules into the
  647. // corresponding configuration object where the plugin is initially included
  648. plugins: {
  649. ...react.configs.flat.plugins,
  650. ...react.configs.flat['jsx-runtime'].plugins,
  651. '@emotion': emotion,
  652. '@typescript-eslint': typescript.plugin,
  653. 'react-hooks': fixupPluginRules(reactHooks),
  654. 'simple-import-sort': simpleImportSort,
  655. 'typescript-sort-keys': typescriptSortKeys,
  656. sentry,
  657. },
  658. settings: {
  659. react: {
  660. version: '18.2.0',
  661. defaultVersion: '18.2',
  662. },
  663. 'import/parsers': {
  664. '@typescript-eslint/parser': ['.ts', '.tsx'],
  665. },
  666. 'import/resolver': {
  667. typescript: {},
  668. },
  669. 'import/extensions': ['.js', '.jsx'],
  670. },
  671. },
  672. {
  673. // Default file selection
  674. // https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores
  675. files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx'],
  676. },
  677. {
  678. // Global ignores
  679. // https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores
  680. ignores: [
  681. '.devenv/**/*',
  682. '.github/**/*',
  683. '.mypy_cache/**/*',
  684. '.pytest_cache/**/*',
  685. '.venv/**/*',
  686. '**/*.benchmark.ts',
  687. '**/*.d.ts',
  688. '**/dist/**/*',
  689. '**/tests/**/fixtures/**/*',
  690. '**/vendor/**/*',
  691. 'build-utils/**/*',
  692. 'config/chartcuterie/config.js', // TODO: see if this file exists
  693. 'fixtures/artifact_bundle/**/*',
  694. 'fixtures/artifact_bundle_debug_ids/**/*',
  695. 'fixtures/artifact_bundle_duplicated_debug_ids/**/*',
  696. 'fixtures/profiles/embedded.js',
  697. 'jest.config.ts',
  698. 'api-docs/**/*',
  699. 'src/sentry/static/sentry/js/**/*',
  700. 'src/sentry/templates/sentry/**/*',
  701. 'stylelint.config.js',
  702. ],
  703. },
  704. /**
  705. * Global Rules
  706. * Any ruleset that does not include `files` or `ignores` fields
  707. *
  708. * Plugins are configured within each configuration object.
  709. * https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects
  710. *
  711. * Rules are grouped by plugin. If you want to override a specific rule inside
  712. * the recommended set, then it's recommended to spread the new rule on top
  713. * of the predefined ones.
  714. *
  715. * For example: if you want to enable a new plugin in the codebase and their
  716. * recommended rules (or a new rule that's part of an existing plugin)
  717. * First you'd setup a configuration object for that plugin:
  718. * {
  719. * name: 'my-plugin/recommended',
  720. * ...myPlugin.configs.recommended,
  721. * },
  722. * Second you'd override the rule you want to deal with, maybe making it a
  723. * warning to start:
  724. * {
  725. * name: 'my-plugin/recommended',
  726. * ...myPlugin.configs.recommended,
  727. * rules: {
  728. * ...myPlugin.configs.recommended.rules,
  729. * ['the-rule']: 'warning',
  730. * }
  731. * },
  732. * Finally, once all warnings are fixed, update from 'warning' to 'error', or
  733. * remove the override and rely on the recommended rules again.
  734. */
  735. {
  736. name: 'import/recommended',
  737. ...importPlugin.flatConfigs.recommended,
  738. },
  739. {
  740. name: 'deprecations',
  741. rules: {
  742. '@typescript-eslint/no-deprecated': process.env.SENTRY_DETECT_DEPRECATIONS
  743. ? 'error'
  744. : 'off',
  745. },
  746. },
  747. {
  748. name: 'getsentry/sentry/custom',
  749. rules: {
  750. ...baseRules,
  751. ...reactRules,
  752. ...appRules,
  753. ...strictRules,
  754. },
  755. },
  756. {
  757. name: 'devtoolbar',
  758. files: ['static/app/components/devtoolbar/**/*.{ts,tsx}'],
  759. rules: {
  760. 'no-restricted-imports': [
  761. 'error',
  762. {
  763. paths: [
  764. // @ts-ignore
  765. ...appRules['no-restricted-imports'][1].paths,
  766. {
  767. name: 'sentry/utils/queryClient',
  768. message:
  769. 'Import from `@tanstack/react-query` and `./hooks/useFetchApiData` or `./hooks/useFetchInfiniteApiData` instead.',
  770. },
  771. ],
  772. },
  773. ],
  774. },
  775. },
  776. {
  777. name: 'jest',
  778. files: ['**/*.spec.{ts,js,tsx,jsx}', 'tests/js/**/*.{ts,js,tsx,jsx}'],
  779. plugins: jest.configs['flat/recommended'].plugins,
  780. rules: {
  781. 'jest/no-disabled-tests': 'error',
  782. // Disabled as we have many tests which render as simple validations
  783. 'jest/expect-expect': 'off',
  784. // Disabled as we have some comment out tests that cannot be
  785. // uncommented due to typescript errors.
  786. 'jest/no-commented-out-tests': 'off',
  787. // Disabled as we do sometimes have conditional expects
  788. 'jest/no-conditional-expect': 'off',
  789. // Useful for exporting some test utilities
  790. 'jest/no-export': 'off',
  791. // We don't recommend snapshots, but if there are any keep it small
  792. 'jest/no-large-snapshots': ['error', {maxSize: 2000}],
  793. },
  794. },
  795. {
  796. name: 'jest-dom',
  797. files: ['**/*.spec.{ts,js,tsx,jsx}', 'tests/js/**/*.{ts,js,tsx,jsx}'],
  798. plugins: jestDom.configs['flat/recommended'].plugins,
  799. },
  800. {
  801. name: 'testing-library/react - ts files',
  802. files: ['**/*.spec.{ts,js,tsx,jsx}', 'tests/js/**/*.{ts,js,tsx,jsx}'],
  803. ...testingLibrary.configs['flat/react'],
  804. rules: {
  805. ...testingLibrary.configs['flat/react'].rules,
  806. 'testing-library/render-result-naming-convention': 'off',
  807. 'testing-library/no-unnecessary-act': 'off',
  808. },
  809. },
  810. {
  811. name: 'testing-library/react - tsx files',
  812. files: ['**/*.spec.{tsx,jsx}', 'tests/js/**/*.{tsx,jsx}'],
  813. ...testingLibrary.configs['flat/react'],
  814. rules: {
  815. 'testing-library/no-await-sync-events': 'warn', // TODO(ryan953): Fix the violations, then delete this line
  816. 'testing-library/no-container': 'warn', // TODO(ryan953): Fix the violations, then delete this line
  817. 'testing-library/no-node-access': 'warn', // TODO(ryan953): Fix the violations, then delete this line
  818. 'testing-library/no-render-in-lifecycle': 'warn', // TODO(ryan953): Fix the violations, then delete this line
  819. 'testing-library/prefer-query-by-disappearance': 'warn', // TODO(ryan953): Fix the violations, then delete this line
  820. 'testing-library/prefer-screen-queries': 'warn', // TODO(ryan953): Fix the violations, then delete this line
  821. },
  822. },
  823. {
  824. // We specify rules explicitly for the sdk-loader here so we do not have
  825. // eslint ignore comments included in the source file, which is consumed
  826. // by users.
  827. name: 'js-sdk-loader.ts',
  828. files: ['**/js-sdk-loader.ts'],
  829. rules: {
  830. 'no-console': 'off',
  831. },
  832. },
  833. {
  834. name: 'prettier/recommended',
  835. ...prettier,
  836. },
  837. ]);