2025-03-09 21:18:32 +08:00
const OFF = 0 ;
const WARNING = 1 ;
const ERROR = 2 ;
// Prevent importing lodash, usually for browser bundle size reasons
const LodashImportPatterns = [ "lodash" , "lodash.**" , "lodash/**" ] ;
2023-09-27 16:00:26 +08:00
module . exports = {
root : true ,
env : {
browser : true ,
commonjs : true ,
node : true ,
} ,
parser : "@typescript-eslint/parser" ,
2025-03-09 21:18:32 +08:00
parserOptions : { } ,
2023-09-27 16:00:26 +08:00
globals : {
JSX : true ,
} ,
extends : [
"eslint:recommended" ,
"plugin:react-hooks/recommended" ,
2025-03-09 21:18:32 +08:00
"airbnb" ,
2023-09-27 16:00:26 +08:00
"plugin:@typescript-eslint/recommended" ,
2025-03-09 21:18:32 +08:00
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 'plugin:@typescript-eslint/strict',
2023-09-27 16:00:26 +08:00
"plugin:regexp/recommended" ,
2025-03-09 21:18:32 +08:00
"prettier" ,
"plugin:@docusaurus/all" ,
2023-09-27 16:00:26 +08:00
] ,
settings : {
"import/resolver" : {
node : {
extensions : [ ".js" , ".jsx" , ".ts" , ".tsx" ] ,
} ,
} ,
} ,
2025-03-09 21:18:32 +08:00
reportUnusedDisableDirectives : true ,
plugins : [ "react-hooks" , "@typescript-eslint" , "regexp" , "@docusaurus" ] ,
rules : {
"react/jsx-uses-react" : OFF , // JSX runtime: automatic
"react/react-in-jsx-scope" : OFF , // JSX runtime: automatic
"array-callback-return" : WARNING ,
camelcase : WARNING ,
"class-methods-use-this" : OFF , // It's a way of allowing private variables.
curly : [ WARNING , "all" ] ,
"global-require" : WARNING ,
"lines-between-class-members" : OFF ,
"max-classes-per-file" : OFF ,
"max-len" : [
WARNING ,
{
code : Infinity , // Code width is already enforced by Prettier
tabWidth : 2 ,
comments : 80 ,
ignoreUrls : true ,
ignorePattern : "(eslint-disable|@)" ,
2023-09-27 16:00:26 +08:00
} ,
2025-03-09 21:18:32 +08:00
] ,
"arrow-body-style" : OFF ,
"no-await-in-loop" : OFF ,
"no-case-declarations" : WARNING ,
"no-console" : OFF ,
"no-constant-binary-expression" : ERROR ,
"no-continue" : OFF ,
"no-control-regex" : WARNING ,
"no-else-return" : OFF ,
"no-empty" : [ WARNING , { allowEmptyCatch : true } ] ,
"no-lonely-if" : WARNING ,
"no-nested-ternary" : WARNING ,
"no-param-reassign" : [ WARNING , { props : false } ] ,
"no-prototype-builtins" : WARNING ,
"no-restricted-exports" : OFF ,
"no-restricted-properties" : [
ERROR ,
... /** @type {[string, string][]} */ ( [
// TODO: TS doesn't make Boolean a narrowing function yet,
// so filter(Boolean) is problematic type-wise
// ['compact', 'Array#filter(Boolean)'],
[ "concat" , "Array#concat" ] ,
[ "drop" , "Array#slice(n)" ] ,
[ "dropRight" , "Array#slice(0, -n)" ] ,
[ "fill" , "Array#fill" ] ,
[ "filter" , "Array#filter" ] ,
[ "find" , "Array#find" ] ,
[ "findIndex" , "Array#findIndex" ] ,
[ "first" , "foo[0]" ] ,
[ "flatten" , "Array#flat" ] ,
[ "flattenDeep" , "Array#flat(Infinity)" ] ,
[ "flatMap" , "Array#flatMap" ] ,
[ "fromPairs" , "Object.fromEntries" ] ,
[ "head" , "foo[0]" ] ,
[ "indexOf" , "Array#indexOf" ] ,
[ "initial" , "Array#slice(0, -1)" ] ,
[ "join" , "Array#join" ] ,
// Unfortunately there's no great alternative to _.last yet
// Candidates: foo.slice(-1)[0]; foo[foo.length - 1]
// Array#at is ES2022; could replace _.nth as well
// ['last'],
[ "map" , "Array#map" ] ,
[ "reduce" , "Array#reduce" ] ,
[ "reverse" , "Array#reverse" ] ,
[ "slice" , "Array#slice" ] ,
[ "take" , "Array#slice(0, n)" ] ,
[ "takeRight" , "Array#slice(-n)" ] ,
[ "tail" , "Array#slice(1)" ] ,
] ) . map ( ( [ property , alternative ] ) => ( {
object : "_" ,
property ,
message : ` Use ${ alternative } instead. ` ,
} ) ) ,
... [
"readdirSync" ,
"readFileSync" ,
"statSync" ,
"lstatSync" ,
"existsSync" ,
"pathExistsSync" ,
"realpathSync" ,
"mkdirSync" ,
"mkdirpSync" ,
"mkdirsSync" ,
"writeFileSync" ,
"writeJsonSync" ,
"outputFileSync" ,
"outputJsonSync" ,
"moveSync" ,
"copySync" ,
"copyFileSync" ,
"ensureFileSync" ,
"ensureDirSync" ,
"ensureLinkSync" ,
"ensureSymlinkSync" ,
"unlinkSync" ,
"removeSync" ,
"emptyDirSync" ,
] . map ( ( property ) => ( {
object : "fs" ,
property ,
message : "Do not use sync fs methods." ,
} ) ) ,
] ,
"no-restricted-syntax" : [
WARNING ,
// Copied from airbnb, removed for...of statement, added export all
{
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." ,
2023-09-27 16:00:26 +08:00
} ,
2025-03-09 21:18:32 +08:00
{
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." ,
} ,
{
selector : "ExportAllDeclaration" ,
message :
"Export all does't work well if imported in ESM due to how they are transpiled, and they can also lead to unexpected exposure of internal methods." ,
} ,
// TODO make an internal plugin to ensure this
// {
// selector:
// @ 'ExportDefaultDeclaration > Identifier, ExportNamedDeclaration[source=null] > ExportSpecifier',
// message: 'Export in one statement'
// },
... [ "path" , "fs-extra" , "webpack" , "lodash" ] . map ( ( m ) => ( {
selector : ` ImportDeclaration[importKind=value]:has(Literal[value= ${ m } ]) > ImportSpecifier[importKind=value] ` ,
message :
"Default-import this, both for readability and interoperability with ESM" ,
} ) ) ,
] ,
"no-template-curly-in-string" : WARNING ,
"no-unused-expressions" : [
WARNING ,
{ allowTaggedTemplates : true , allowShortCircuit : true } ,
] ,
"no-useless-escape" : WARNING ,
"no-void" : [ ERROR , { allowAsStatement : true } ] ,
"prefer-destructuring" : WARNING ,
"prefer-named-capture-group" : WARNING ,
"prefer-template" : WARNING ,
yoda : WARNING ,
"import/extensions" : OFF ,
// This rule doesn't yet support resolving .js imports when the actual file
// is .ts. Plus it's not all that useful when our code is fully TS-covered.
"import/no-unresolved" : [
OFF ,
{
// Ignore certain webpack aliases because they can't be resolved
ignore : [
"^@theme" ,
"^@docusaurus" ,
"^@generated" ,
"^@site" ,
"^@testing-utils" ,
] ,
} ,
] ,
2023-09-27 16:00:26 +08:00
"import/order" : [
2025-03-09 21:18:32 +08:00
WARNING ,
2023-09-27 16:00:26 +08:00
{
groups : [
"builtin" ,
"external" ,
"internal" ,
2025-03-09 21:18:32 +08:00
[ "parent" , "sibling" , "index" ] ,
"type" ,
2023-09-27 16:00:26 +08:00
] ,
2025-03-09 21:18:32 +08:00
"newlines-between" : "always" ,
2023-09-27 16:00:26 +08:00
pathGroups : [
2025-03-09 21:18:32 +08:00
// always put css import to the last, ref:
// https://github.com/import-js/eslint-plugin-import/issues/1239
{
pattern : "*.+(css|sass|less|scss|pcss|styl)" ,
group : "unknown" ,
patternOptions : { matchBase : true } ,
position : "after" ,
} ,
2023-09-27 16:00:26 +08:00
{ pattern : "react" , group : "builtin" , position : "before" } ,
2025-03-09 21:18:32 +08:00
{ pattern : "react-dom" , group : "builtin" , position : "before" } ,
{ pattern : "react-dom/**" , group : "builtin" , position : "before" } ,
{ pattern : "stream" , group : "builtin" , position : "before" } ,
2023-09-27 16:00:26 +08:00
{ pattern : "fs-extra" , group : "builtin" } ,
{ pattern : "lodash" , group : "external" , position : "before" } ,
{ pattern : "clsx" , group : "external" , position : "before" } ,
2025-03-09 21:18:32 +08:00
// 'Bit weird to not use the `import/internal-regex` option, but this
// way, we can make `import type { Props } from "@theme/*"` appear
// before `import styles from "styles.module.css"`, which is what we
// always did. This should be removable once we stop using ambient
// module declarations for theme aliases.
2023-09-27 16:00:26 +08:00
{ pattern : "@theme/**" , group : "internal" } ,
{ pattern : "@site/**" , group : "internal" } ,
{ pattern : "@theme-init/**" , group : "internal" } ,
{ pattern : "@theme-original/**" , group : "internal" } ,
2025-03-09 21:18:32 +08:00
{ pattern : "@/components/**" , group : "internal" } ,
{ pattern : "@/libs/**" , group : "internal" } ,
{ pattern : "@/types/**" , group : "type" } ,
2023-09-27 16:00:26 +08:00
] ,
pathGroupsExcludedImportTypes : [ ] ,
2025-03-09 21:18:32 +08:00
// example: let `import './nprogress.css';` after importing others
// in `packages/docusaurus-theme-classic/src/nprogress.ts`
// see more: https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md#warnonunassignedimports-truefalse
warnOnUnassignedImports : true ,
} ,
] ,
"import/prefer-default-export" : OFF ,
"jsx-a11y/click-events-have-key-events" : WARNING ,
"jsx-a11y/no-noninteractive-element-interactions" : WARNING ,
"jsx-a11y/html-has-lang" : OFF ,
"react-hooks/rules-of-hooks" : ERROR ,
"react-hooks/exhaustive-deps" : ERROR ,
// Sometimes we do need the props as a whole, e.g. when spreading
"react/destructuring-assignment" : OFF ,
"react/function-component-definition" : [
WARNING ,
{
namedComponents : "function-declaration" ,
unnamedComponents : "arrow-function" ,
} ,
] ,
"react/jsx-filename-extension" : OFF ,
"react/jsx-key" : [ ERROR , { checkFragmentShorthand : true } ] ,
"react/jsx-no-useless-fragment" : [ ERROR , { allowExpressions : true } ] ,
"react/jsx-props-no-spreading" : OFF ,
"react/no-array-index-key" : OFF , // We build a static site, and nearly all components don't change.
"react/no-unstable-nested-components" : [ WARNING , { allowAsProps : true } ] ,
"react/prefer-stateless-function" : WARNING ,
"react/prop-types" : OFF ,
"react/require-default-props" : [
ERROR ,
{ ignoreFunctionalComponents : true } ,
] ,
"@typescript-eslint/consistent-type-definitions" : OFF ,
"@typescript-eslint/require-await" : OFF ,
"@typescript-eslint/ban-ts-comment" : [
ERROR ,
{ "ts-expect-error" : "allow-with-description" } ,
] ,
"@typescript-eslint/consistent-indexed-object-style" : OFF ,
"@typescript-eslint/consistent-type-imports" : [
WARNING ,
{ disallowTypeAnnotations : false } ,
] ,
"@typescript-eslint/explicit-module-boundary-types" : WARNING ,
"@typescript-eslint/method-signature-style" : ERROR ,
"@typescript-eslint/no-empty-function" : OFF ,
"@typescript-eslint/no-empty-interface" : [
ERROR ,
{
allowSingleExtends : true ,
} ,
] ,
"@typescript-eslint/no-inferrable-types" : OFF ,
"@typescript-eslint/no-namespace" : [ WARNING , { allowDeclarations : true } ] ,
"no-use-before-define" : OFF ,
"@typescript-eslint/no-use-before-define" : [
ERROR ,
{ functions : false , classes : false , variables : true } ,
] ,
"@typescript-eslint/no-non-null-assertion" : OFF ,
"no-redeclare" : OFF ,
"@typescript-eslint/no-redeclare" : ERROR ,
"no-shadow" : OFF ,
"@typescript-eslint/no-shadow" : ERROR ,
"no-unused-vars" : OFF ,
// We don't provide any escape hatches for this rule. Rest siblings and
// function placeholder params are always ignored, and any other unused
// locals must be justified with a disable comment.
"@typescript-eslint/no-unused-vars" : [ ERROR , { ignoreRestSiblings : true } ] ,
"@typescript-eslint/prefer-optional-chain" : ERROR ,
"@docusaurus/no-html-links" : ERROR ,
"@docusaurus/prefer-docusaurus-heading" : ERROR ,
"@docusaurus/no-untranslated-text" : [
WARNING ,
{
ignoredStrings : [
"·" ,
"-" ,
"—" ,
"× " ,
" " , // zwj: ​
"@" ,
"WebContainers" ,
"Twitter" ,
"GitHub" ,
"Dev.to" ,
"1.x" ,
] ,
2023-09-27 16:00:26 +08:00
} ,
] ,
} ,
2025-03-09 21:18:32 +08:00
overrides : [
{
files : [ "packages/*/src/theme/**/*.{js,ts,tsx}" ] ,
excludedFiles : "*.test.{js,ts,tsx}" ,
rules : {
"no-restricted-imports" : [
"error" ,
{
patterns : LodashImportPatterns . concat (
// Prevents relative imports between React theme components
[
"../**" ,
"./**" ,
// Allows relative styles module import with consistent filename
"!./styles.module.css" ,
// Allows relative tailwind css import with consistent filename
"!./styles.css" ,
]
) ,
} ,
] ,
} ,
} ,
{
files : [ "packages/*/src/theme/**/*.{js,ts,tsx}" ] ,
rules : {
"import/no-named-export" : ERROR ,
} ,
} ,
{
files : [ "*.d.ts" ] ,
rules : {
"import/no-duplicates" : OFF ,
} ,
} ,
{
files : [ "*.{ts,tsx}" ] ,
rules : {
"no-undef" : OFF ,
"import/no-import-module-exports" : OFF ,
} ,
} ,
{
files : [ "*.{js,mjs,cjs}" ] ,
rules : {
// Make JS code directly runnable in Node.
"@typescript-eslint/no-var-requires" : OFF ,
"@typescript-eslint/explicit-module-boundary-types" : OFF ,
} ,
} ,
{
// Internal files where extraneous deps don't matter much at long as
// they run
files : [ "website/**" ] ,
rules : {
"import/no-extraneous-dependencies" : OFF ,
} ,
} ,
] ,
2023-09-27 16:00:26 +08:00
} ;