2023-01-03 11:55:46 -08:00
// Copyright 2018 Signal Messenger, LLC
2020-10-30 15:34:04 -05:00
// SPDX-License-Identifier: AGPL-3.0-only
2018-01-08 13:19:25 -08:00
// For reference: https://github.com/airbnb/javascript
2020-09-03 07:59:24 -07:00
const rules = {
'comma-dangle' : [
'error' ,
{
arrays : 'always-multiline' ,
objects : 'always-multiline' ,
imports : 'always-multiline' ,
exports : 'always-multiline' ,
functions : 'never' ,
} ,
] ,
2022-09-13 14:48:09 -07:00
// No omitting braces, keep on the same line
'brace-style' : [ 'error' , '1tbs' , { allowSingleLine : false } ] ,
curly : [ 'error' , 'all' ] ,
2023-10-03 20:12:57 -04:00
// Immer support
'no-param-reassign' : [
'error' ,
{
props : true ,
ignorePropertyModificationsForRegex : [ '^draft' ] ,
ignorePropertyModificationsFor : [ 'acc' , 'ctx' , 'context' ] ,
} ,
] ,
2022-09-14 14:40:44 -07:00
// Always use === and !== except when directly comparing to null
// (which only will equal null or undefined)
eqeqeq : [ 'error' , 'always' , { null : 'never' } ] ,
2020-09-03 07:59:24 -07:00
// prevents us from accidentally checking in exclusive tests (`.only`):
'mocha/no-exclusive-tests' : 'error' ,
// encourage consistent use of `async` / `await` instead of `then`
'more/no-then' : 'error' ,
// it helps readability to put public API at top,
'no-use-before-define' : 'off' ,
2021-12-13 19:15:24 -06:00
'@typescript-eslint/no-use-before-define' : 'off' ,
2020-09-03 07:59:24 -07:00
// useful for unused or internal fields
'no-underscore-dangle' : 'off' ,
2022-09-15 12:17:15 -07:00
// Temp: We have because TypeScript's `allowUnreachableCode` option is on.
'no-unreachable' : 'error' ,
2020-09-03 07:59:24 -07:00
// though we have a logger, we still remap console to log to disk
'no-console' : 'error' ,
// consistently place operators at end of line except ternaries
'operator-linebreak' : [
'error' ,
'after' ,
{ overrides : { '?' : 'ignore' , ':' : 'ignore' } } ,
] ,
quotes : [
'error' ,
'single' ,
{ avoidEscape : true , allowTemplateLiterals : false } ,
] ,
2020-10-27 20:00:28 -05:00
'no-continue' : 'off' ,
2021-08-30 14:32:56 -07:00
'lines-between-class-members' : 'off' ,
2021-11-04 16:04:51 -05:00
'class-methods-use-this' : 'off' ,
2020-10-27 20:00:28 -05:00
2020-09-03 07:59:24 -07:00
// Prettier overrides:
'arrow-parens' : 'off' ,
'function-paren-newline' : 'off' ,
'max-len' : [
'error' ,
{
// Prettier generally limits line length to 80 but sometimes goes over.
// The `max-len` plugin doesn’ t let us omit `code` so we set it to a
// high value as a buffer to let Prettier control the line length:
code : 999 ,
// We still want to limit comments as before:
comments : 90 ,
ignoreUrls : true ,
} ,
] ,
2020-09-11 17:46:52 -07:00
'react/jsx-props-no-spreading' : 'off' ,
// Updated to reflect future airbnb standard
// Allows for declaring defaultProps inside a class
'react/static-property-placement' : [ 'error' , 'static public field' ] ,
// JIRA: DESKTOP-657
'react/sort-comp' : 'off' ,
// We don't have control over the media we're sharing, so can't require
// captions.
'jsx-a11y/media-has-caption' : 'off' ,
// We prefer named exports
2020-09-03 07:59:24 -07:00
'import/prefer-default-export' : 'off' ,
2025-09-16 17:39:03 -07:00
'import/enforce-node-protocol-usage' : [ 'error' , 'always' ] ,
'import/extensions' : [
'error' ,
'ignorePackages' ,
{
checkTypeImports : true ,
} ,
] ,
2020-09-11 17:46:52 -07:00
// Prefer functional components with default params
'react/require-default-props' : 'off' ,
2020-09-14 14:56:35 -07:00
2025-08-12 06:55:29 +10:00
// Empty fragments are used in adapters between models and react views.
2022-12-02 14:09:13 -08:00
'react/jsx-no-useless-fragment' : [
'error' ,
{
allowExpressions : true ,
} ,
] ,
2022-11-17 16:45:19 -08:00
// Our code base has tons of arrow functions passed directly to components.
'react/jsx-no-bind' : 'off' ,
// Does not support forwardRef
'react/no-unused-prop-types' : 'off' ,
// Not useful for us as we have lots of complicated types.
'react/destructuring-assignment' : 'off' ,
2022-12-02 14:09:13 -08:00
'react/function-component-definition' : [
'error' ,
{
namedComponents : 'function-declaration' ,
unnamedComponents : 'arrow-function' ,
} ,
] ,
2022-11-17 16:45:19 -08:00
'react/display-name' : 'error' ,
2023-02-23 11:38:09 -07:00
'react/jsx-pascal-case' : [ 'error' , { allowNamespace : true } ] ,
2023-01-25 16:51:08 -07:00
2022-11-17 16:45:19 -08:00
// Allow returning values from promise executors for brevity.
'no-promise-executor-return' : 'off' ,
// Redux ducks use this a lot
'default-param-last' : 'off' ,
2020-09-14 14:56:35 -07:00
'jsx-a11y/label-has-associated-control' : [ 'error' , { assert : 'either' } ] ,
2020-09-16 07:26:06 -07:00
2024-01-23 16:11:12 -08:00
'jsx-a11y/no-static-element-interactions' : 'error' ,
'@typescript-eslint/no-non-null-assertion' : [ 'error' ] ,
'@typescript-eslint/no-empty-interface' : [ 'error' ] ,
'no-empty-function' : 'off' ,
'@typescript-eslint/no-empty-function' : 'error' ,
2021-01-14 12:07:05 -06:00
'no-restricted-syntax' : [
'error' ,
{
selector : 'TSInterfaceDeclaration' ,
message :
'Prefer `type`. Interfaces are mutable and less powerful, so we prefer `type` for simplicity.' ,
} ,
// Defaults
{
selector : 'ForInStatement' ,
message :
'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.' ,
} ,
{
selector : 'LabeledStatement' ,
message :
'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.' ,
} ,
{
selector : 'WithStatement' ,
message :
'`with` is disallowed in strict mode because it makes code impossible to predict and optimize.' ,
} ,
] ,
2024-08-13 15:19:34 -07:00
'react-hooks/exhaustive-deps' : [
'error' ,
{
additionalHooks : '^(useSpring|useSprings)$' ,
} ,
] ,
2025-08-11 12:55:09 -07:00
'local-rules/license-comments' : 'error' ,
2020-09-03 07:59:24 -07:00
} ;
2021-07-02 14:09:34 -05:00
const typescriptRules = {
... rules ,
2022-09-13 14:48:09 -07:00
// Override brace style to enable typescript-specific syntax
'brace-style' : 'off' ,
'@typescript-eslint/brace-style' : [
'error' ,
'1tbs' ,
{ allowSingleLine : false } ,
] ,
2021-07-08 16:55:48 -05:00
'@typescript-eslint/array-type' : [ 'error' , { default : 'generic' } ] ,
2021-12-17 16:24:54 -06:00
'no-restricted-imports' : 'off' ,
'@typescript-eslint/no-restricted-imports' : [
'error' ,
{
paths : [
2022-01-19 13:19:08 -06:00
{
name : 'chai' ,
importNames : [ 'expect' , 'should' , 'Should' ] ,
message : 'Please use assert' ,
allowTypeImports : true ,
} ,
2021-12-17 16:24:54 -06:00
] ,
} ,
] ,
2021-07-02 14:09:34 -05:00
// Overrides recommended by typescript-eslint
// https://github.com/typescript-eslint/typescript-eslint/releases/tag/v4.0.0
'@typescript-eslint/no-redeclare' : 'error' ,
'@typescript-eslint/no-shadow' : 'error' ,
'@typescript-eslint/no-useless-constructor' : [ 'error' ] ,
2022-12-21 10:41:48 -08:00
'@typescript-eslint/no-misused-promises' : [
'error' ,
{
2023-01-03 11:55:46 -08:00
checksVoidReturn : false ,
} ,
2022-12-21 10:41:48 -08:00
] ,
'@typescript-eslint/no-floating-promises' : 'error' ,
// We allow "void promise", but new call-sites should use `drop(promise)`.
2023-01-03 11:55:46 -08:00
'no-void' : [ 'error' , { allowAsStatement : true } ] ,
2022-12-21 10:41:48 -08:00
2021-07-02 14:09:34 -05:00
'no-shadow' : 'off' ,
'no-useless-constructor' : 'off' ,
// useful for unused parameters
'@typescript-eslint/no-unused-vars' : [ 'error' , { argsIgnorePattern : '^_' } ] ,
// Upgrade from a warning
'@typescript-eslint/explicit-module-boundary-types' : 'error' ,
2021-10-26 14:15:33 -05:00
'@typescript-eslint/consistent-type-imports' : 'error' ,
2023-03-28 11:26:46 -07:00
// Future: Maybe switch to never and always use `satisfies`
'@typescript-eslint/consistent-type-assertions' : [
'error' ,
{
assertionStyle : 'as' ,
// Future: Maybe switch to allow-as-parameter or never
objectLiteralTypeAssertions : 'allow' ,
} ,
] ,
2021-07-02 14:09:34 -05:00
// Already enforced by TypeScript
'consistent-return' : 'off' ,
2022-12-02 14:09:13 -08:00
// TODO: DESKTOP-4655
'import/no-cycle' : 'off' ,
2021-07-02 14:09:34 -05:00
} ;
2025-09-10 13:25:46 -07:00
const TAILWIND _REPLACEMENTS = [
// inset
{ pattern : 'left-*' , fix : 'start-*' } ,
{ pattern : 'right-*' , fix : 'end-*' } ,
// margin
{ pattern : 'ml-*' , fix : 'ms-*' } ,
{ pattern : 'mr-*' , fix : 'me-*' } ,
// padding
{ pattern : 'pl-*' , fix : 'ps-*' } ,
{ pattern : 'pr-*' , fix : 'pe-*' } ,
// border
{ pattern : 'border-l-*' , fix : 'border-s-*' } ,
{ pattern : 'border-r-*' , fix : 'border-e-*' } ,
// border-radius
{ pattern : 'rounded-l' , fix : 'rounded-s' } ,
{ pattern : 'rounded-r' , fix : 'rounded-e' } ,
{ pattern : 'rounded-tl' , fix : 'rounded-ss' } ,
{ pattern : 'rounded-tr' , fix : 'rounded-se' } ,
{ pattern : 'rounded-bl' , fix : 'rounded-es' } ,
{ pattern : 'rounded-br' , fix : 'rounded-ee' } ,
{ pattern : 'rounded-l-*' , fix : 'rounded-s-*' } ,
{ pattern : 'rounded-r-*' , fix : 'rounded-e-*' } ,
{ pattern : 'rounded-tl-*' , fix : 'rounded-ss-*' } ,
{ pattern : 'rounded-tr-*' , fix : 'rounded-se-*' } ,
{ pattern : 'rounded-bl-*' , fix : 'rounded-es-*' } ,
{ pattern : 'rounded-br-*' , fix : 'rounded-ee-*' } ,
// text-align
{ pattern : 'text-left' , fix : 'text-start' } ,
{ pattern : 'text-right' , fix : 'text-end' } ,
// float
{ pattern : 'float-left' , fix : 'float-start' } ,
{ pattern : 'float-right' , fix : 'float-end' } ,
// clear
{ pattern : 'clear-left' , fix : 'clear-start' } ,
{ pattern : 'clear-right' , fix : 'clear-end' } ,
] ;
2018-01-08 13:19:25 -08:00
module . exports = {
2020-08-31 17:09:28 -07:00
root : true ,
2018-01-08 13:19:25 -08:00
settings : {
2020-08-31 17:09:28 -07:00
react : {
version : 'detect' ,
} ,
2018-04-27 17:25:04 -04:00
'import/core-modules' : [ 'electron' ] ,
2018-01-08 13:19:25 -08:00
} ,
2018-04-27 17:25:04 -04:00
extends : [ 'airbnb-base' , 'prettier' ] ,
2018-01-08 13:19:25 -08:00
2023-01-05 14:43:33 -08:00
plugins : [ 'mocha' , 'more' , 'local-rules' ] ,
2018-02-22 13:21:53 -05:00
2020-08-31 17:09:28 -07:00
overrides : [
{
2022-06-13 14:39:35 -07:00
files : [
'ts/**/*.ts' ,
'ts/**/*.tsx' ,
'app/**/*.ts' ,
2022-10-03 14:19:54 -07:00
'build/intl-linter/**/*.ts' ,
2020-08-31 17:09:28 -07:00
] ,
2020-09-14 14:56:35 -07:00
parser : '@typescript-eslint/parser' ,
parserOptions : {
2022-06-13 14:39:35 -07:00
project : 'tsconfig.json' ,
2020-09-14 14:56:35 -07:00
ecmaFeatures : {
jsx : true ,
} ,
ecmaVersion : 2018 ,
sourceType : 'module' ,
} ,
plugins : [ '@typescript-eslint' ] ,
extends : [
'eslint:recommended' ,
'plugin:@typescript-eslint/recommended' ,
'plugin:react/recommended' ,
'airbnb-typescript-prettier' ,
] ,
2021-07-02 14:09:34 -05:00
rules : typescriptRules ,
2020-09-14 14:56:35 -07:00
} ,
2020-08-31 17:09:28 -07:00
{
2022-10-03 14:19:54 -07:00
files : [
'**/*.stories.tsx' ,
'ts/build/**' ,
'ts/test-*/**' ,
'build/intl-linter/**/*.ts' ,
] ,
2020-08-31 17:09:28 -07:00
rules : {
2021-07-02 14:09:34 -05:00
... typescriptRules ,
2020-08-31 17:09:28 -07:00
'import/no-extraneous-dependencies' : 'off' ,
2020-09-14 12:51:27 -07:00
'react/no-array-index-key' : 'off' ,
2020-08-31 17:09:28 -07:00
} ,
} ,
2023-01-13 12:07:26 -08:00
{
files : [ 'ts/state/ducks/**/*.ts' ] ,
rules : {
'local-rules/type-alias-readonlydeep' : 'error' ,
} ,
} ,
2023-10-11 12:06:43 -07:00
{
files : [ 'ts/**/*_test.{ts,tsx}' ] ,
rules : {
'func-names' : 'off' ,
} ,
} ,
2025-08-04 13:35:20 -07:00
{
2025-08-11 16:46:23 -07:00
files : [ 'ts/**/*.tsx' ] ,
2025-08-04 13:35:20 -07:00
plugins : [ 'better-tailwindcss' ] ,
settings : {
'better-tailwindcss' : {
2025-08-11 16:46:23 -07:00
entryPoint : './stylesheets/tailwind-config.css' ,
callees : [ 'tw' ] ,
attributes : [ ] ,
variables : [ ] ,
2025-08-04 13:35:20 -07:00
} ,
} ,
rules : {
2025-08-11 16:46:23 -07:00
'local-rules/enforce-tw' : 'error' ,
2025-08-04 13:35:20 -07:00
// stylistic: Enforce consistent line wrapping for tailwind classes. (recommended, autofix)
'better-tailwindcss/enforce-consistent-line-wrapping' : 'off' ,
// stylistic: Enforce a consistent order for tailwind classes. (recommended, autofix)
'better-tailwindcss/enforce-consistent-class-order' : 'error' ,
// stylistic: Enforce consistent variable syntax. (autofix)
'better-tailwindcss/enforce-consistent-variable-syntax' : 'error' ,
// stylistic: Enforce consistent position of the important modifier. (autofix)
'better-tailwindcss/enforce-consistent-important-position' : 'error' ,
// stylistic: Enforce shorthand class names. (autofix)
'better-tailwindcss/enforce-shorthand-classes' : 'error' ,
// stylistic: Remove duplicate classes. (autofix)
'better-tailwindcss/no-duplicate-classes' : 'error' ,
// stylistic: Remove deprecated classes. (autofix)
'better-tailwindcss/no-deprecated-classes' : 'off' ,
// stylistic: Disallow unnecessary whitespace in tailwind classes. (autofix)
'better-tailwindcss/no-unnecessary-whitespace' : 'error' ,
// correctness: Report classes not registered with tailwindcss. (recommended)
'better-tailwindcss/no-unregistered-classes' : 'error' ,
// correctness: Report classes that produce conflicting styles.
'better-tailwindcss/no-conflicting-classes' : 'error' ,
// correctness: Disallow restricted classes. (autofix)
'better-tailwindcss/no-restricted-classes' : [
'error' ,
{
restrict : [
{
pattern : '\\[#[a-fA-F0-9]{3,8}?\\]' , // ex: "text-[#fff]"
message : 'No arbitrary hex values' ,
} ,
{
pattern : '\\[rgba?\\(.*\\)\\]' , // ex: "text-[rgb(255,255,255)]"
message : 'No arbitrary rgb values' ,
} ,
{
pattern : '\\[hsla?\\(.*\\)\\]' , // ex: "text-[hsl(255,255,255)]"
message : 'No arbitrary hsl values' ,
} ,
{
pattern : '^.*!$' , // ex: "p-4!"
message : 'No !important modifiers' ,
} ,
{
pattern : '^\\*+:.*' , // ex: "*:mx-0",
message : 'No child variants' ,
} ,
2025-09-10 13:25:46 -07:00
... TAILWIND _REPLACEMENTS . map ( item => {
const pattern = item . pattern . replace ( '*' , '(.*)' ) ;
const fix = item . fix . replace ( '*' , '$2' ) ;
return {
message : ` Use logical property ${ item . fix } instead of ${ item . pattern } ` ,
pattern : ` ^(.*:)? ${ pattern } $ ` ,
fix : ` $ 1 ${ fix } ` ,
} ;
} ) ,
2025-08-04 13:35:20 -07:00
] ,
} ,
] ,
} ,
} ,
2020-08-31 17:09:28 -07:00
] ,
2022-12-02 14:09:13 -08:00
rules : {
... rules ,
'import/no-unresolved' : 'off' ,
'import/extensions' : 'off' ,
} ,
2022-06-03 21:07:51 +00:00
reportUnusedDisableDirectives : true ,
2018-01-08 13:19:25 -08:00
} ;