@mui/internal-code-infra 0.0.1 → 0.0.2-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/package.json +26 -23
- package/src/eslint/airbnb/base.mjs +18 -0
- package/src/eslint/airbnb/typescript.mjs +126 -0
- package/src/eslint/baseConfig.mjs +67 -0
- package/src/eslint/docsConfig.mjs +20 -0
- package/src/eslint/extensions.mjs +5 -0
- package/src/eslint/index.mjs +7 -0
- package/src/eslint/material-ui/config.mjs +193 -0
- package/src/eslint/material-ui/index.mjs +27 -0
- package/src/eslint/material-ui/rules/disallow-active-element-as-key-event-target.mjs +65 -0
- package/src/eslint/material-ui/rules/disallow-active-elements-as-key-event-target.test.mjs +71 -0
- package/src/eslint/material-ui/rules/disallow-react-api-in-server-components.mjs +64 -0
- package/src/eslint/material-ui/rules/docgen-ignore-before-comment.mjs +34 -0
- package/src/eslint/material-ui/rules/docgen-ignore-before-comment.test.mjs +56 -0
- package/src/eslint/material-ui/rules/mui-name-matches-component-name.mjs +161 -0
- package/src/eslint/material-ui/rules/mui-name-matches-component-name.test.mjs +257 -0
- package/src/eslint/material-ui/rules/no-empty-box.mjs +60 -0
- package/src/eslint/material-ui/rules/no-empty-box.test.mjs +42 -0
- package/src/eslint/material-ui/rules/no-restricted-resolved-imports.mjs +95 -0
- package/src/eslint/material-ui/rules/no-styled-box.mjs +53 -0
- package/src/eslint/material-ui/rules/no-styled-box.test.mjs +75 -0
- package/src/eslint/material-ui/rules/rules-of-use-theme-variants.mjs +124 -0
- package/src/eslint/material-ui/rules/rules-of-use-theme-variants.test.mjs +149 -0
- package/src/eslint/material-ui/rules/straight-quotes.mjs +43 -0
- package/src/eslint/material-ui/rules/straight-quotes.test.mjs +69 -0
- package/src/eslint/testConfig.mjs +104 -0
- package/src/estree-typescript.d.ts +21 -0
- package/src/prettier.mjs +34 -0
- package/src/setupVitest.ts +14 -0
- package/src/untyped-plugins.d.ts +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Material-UI SAS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-code-infra",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-canary.0",
|
|
4
4
|
"description": "Infra scripts and configs to be used across MUI repos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -12,42 +12,37 @@
|
|
|
12
12
|
"exports": {
|
|
13
13
|
"./package.json": "./package.json",
|
|
14
14
|
"./prettier": {
|
|
15
|
-
"types": "./src/prettier.d.ts",
|
|
16
15
|
"default": "./src/prettier.mjs"
|
|
17
16
|
},
|
|
18
17
|
"./eslint": {
|
|
19
|
-
"types": "./src/eslint/index.d.ts",
|
|
20
18
|
"default": "./src/eslint/index.mjs"
|
|
21
|
-
},
|
|
22
|
-
"./eslint-docs": {
|
|
23
|
-
"types": "./src/eslint/docs.d.ts",
|
|
24
|
-
"default": "./src/eslint/docs.mjs"
|
|
25
19
|
}
|
|
26
20
|
},
|
|
27
21
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"eslint-config-airbnb-base": "^15.0.0",
|
|
22
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
30
23
|
"eslint-config-airbnb": "^19.0.4",
|
|
24
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
31
25
|
"eslint-config-prettier": "^10.1.5",
|
|
32
|
-
"eslint-
|
|
33
|
-
"eslint-
|
|
26
|
+
"eslint-import-resolver-typescript": "^4.4.3",
|
|
27
|
+
"eslint-module-utils": "^2.12.1",
|
|
28
|
+
"eslint-plugin-import": "^2.32.0",
|
|
34
29
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
35
30
|
"eslint-plugin-mocha": "^11.1.0",
|
|
36
31
|
"eslint-plugin-react": "^7.37.5",
|
|
37
32
|
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
38
|
-
"eslint-plugin-react-hooks": "^6.0.0",
|
|
39
|
-
"eslint-plugin-testing-library": "^7.3
|
|
33
|
+
"eslint-plugin-react-hooks": "^6.0.0-rc.1",
|
|
34
|
+
"eslint-plugin-testing-library": "^7.5.3",
|
|
35
|
+
"@next/eslint-plugin-next": "^15.0.0",
|
|
40
36
|
"globals": "^16.2.0",
|
|
41
|
-
"minimatch": "^10.0.
|
|
42
|
-
"typescript-eslint": "^8.
|
|
37
|
+
"minimatch": "^10.0.3",
|
|
38
|
+
"typescript-eslint": "^8.35.0"
|
|
43
39
|
},
|
|
44
40
|
"peerDependencies": {
|
|
45
|
-
"@next/eslint-plugin-next": "^14.0.0 || ^15.0.0",
|
|
46
41
|
"eslint": "^9.0.0",
|
|
47
42
|
"prettier": "^3.0.0"
|
|
48
43
|
},
|
|
49
44
|
"peerDependenciesMeta": {
|
|
50
|
-
"
|
|
45
|
+
"eslint": {
|
|
51
46
|
"optional": true
|
|
52
47
|
},
|
|
53
48
|
"prettier": {
|
|
@@ -56,19 +51,27 @@
|
|
|
56
51
|
},
|
|
57
52
|
"devDependencies": {
|
|
58
53
|
"@next/eslint-plugin-next": "^15.3.3",
|
|
59
|
-
"@
|
|
60
|
-
"
|
|
54
|
+
"@types/eslint-plugin-jsx-a11y": "^6.10.0",
|
|
55
|
+
"@types/estree": "^1.0.8",
|
|
56
|
+
"@types/estree-jsx": "^1.0.5",
|
|
57
|
+
"@typescript-eslint/parser": "^8.35.0",
|
|
58
|
+
"@typescript-eslint/rule-tester": "^8.35.0",
|
|
59
|
+
"eslint": "^9.29.0",
|
|
61
60
|
"prettier": "^3.5.3",
|
|
62
|
-
"typescript-eslint": "^8.
|
|
61
|
+
"typescript-eslint": "^8.35.0"
|
|
63
62
|
},
|
|
64
63
|
"files": [
|
|
64
|
+
"build",
|
|
65
65
|
"src",
|
|
66
|
-
"package.json",
|
|
67
66
|
"README.md",
|
|
68
|
-
"CHANGELOG.md",
|
|
69
67
|
"LICENSE"
|
|
70
68
|
],
|
|
71
69
|
"publishConfig": {
|
|
72
70
|
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"gitSha": "8391aed9c1a47151a520e3bfea6db8c730fdadd3",
|
|
73
|
+
"scripts": {
|
|
74
|
+
"typescript": "tsc -p tsconfig.json",
|
|
75
|
+
"test": "pnpm -w test --project @mui/internal-code-infra"
|
|
73
76
|
}
|
|
74
|
-
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FlatCompat } from '@eslint/eslintrc';
|
|
2
|
+
import * as tseslint from 'typescript-eslint';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} options - Configuration options.
|
|
6
|
+
* @param {string} [options.baseDirectory] - The base directory for the configuration.
|
|
7
|
+
* @returns {import('eslint').Linter.Config[]}
|
|
8
|
+
*/
|
|
9
|
+
export function createAirbnbConfig({ baseDirectory } = {}) {
|
|
10
|
+
if (!baseDirectory) {
|
|
11
|
+
throw new Error('"baseDirectory" option is required for Airbnb configuration.');
|
|
12
|
+
}
|
|
13
|
+
const compat = new FlatCompat({
|
|
14
|
+
baseDirectory,
|
|
15
|
+
});
|
|
16
|
+
const airbnbConfig = compat.extends('eslint-config-airbnb');
|
|
17
|
+
return /** @type {import('eslint').Linter.Config[]} */ (tseslint.config(airbnbConfig));
|
|
18
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reassembles the ESLint Airbnb typescript configuration for usage with
|
|
3
|
+
* flat Eslint configuration.
|
|
4
|
+
*/
|
|
5
|
+
import baseBestPractices from 'eslint-config-airbnb-base/rules/best-practices';
|
|
6
|
+
import baseEs6 from 'eslint-config-airbnb-base/rules/es6';
|
|
7
|
+
import baseImports from 'eslint-config-airbnb-base/rules/imports';
|
|
8
|
+
import baseStyle from 'eslint-config-airbnb-base/rules/style';
|
|
9
|
+
import baseVariables from 'eslint-config-airbnb-base/rules/variables';
|
|
10
|
+
import * as tseslint from 'typescript-eslint';
|
|
11
|
+
|
|
12
|
+
const baseImportsRules = baseImports.rules;
|
|
13
|
+
|
|
14
|
+
if (!Array.isArray(baseImportsRules?.['import/extensions'])) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
'Expected `import/extensions` rule to be an array in `eslint-config-airbnb-base/rules/imports`',
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default /** @type {import('typescript-eslint').ConfigArray} */ (
|
|
21
|
+
tseslint.config(
|
|
22
|
+
{
|
|
23
|
+
settings: {
|
|
24
|
+
'import/parsers': {
|
|
25
|
+
'@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'],
|
|
26
|
+
},
|
|
27
|
+
'import/resolver': {
|
|
28
|
+
node: {
|
|
29
|
+
extensions: ['.mjs', '.js', '.jsx', '.json', '.ts', '.tsx', '.d.ts'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
// Append 'ts' extensions to Airbnb 'import/extensions' setting
|
|
33
|
+
// Original: ['.js', '.mjs', '.jsx']
|
|
34
|
+
'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'],
|
|
35
|
+
// Resolve type definition packages
|
|
36
|
+
'import/external-module-folders': ['node_modules', 'node_modules/@types'],
|
|
37
|
+
},
|
|
38
|
+
rules: {
|
|
39
|
+
camelcase: 'off',
|
|
40
|
+
// The `@typescript-eslint/naming-convention` rule allows `leadingUnderscore` and `trailingUnderscore` settings. However, the existing `no-underscore-dangle` rule already takes care of this.
|
|
41
|
+
'@typescript-eslint/naming-convention': [
|
|
42
|
+
'error',
|
|
43
|
+
// Allow camelCase variables (23.2), PascalCase variables (23.8), and UPPER_CASE variables (23.10)
|
|
44
|
+
{
|
|
45
|
+
selector: 'variable',
|
|
46
|
+
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
|
|
47
|
+
},
|
|
48
|
+
// Allow camelCase functions (23.2), and PascalCase functions (23.8)
|
|
49
|
+
{
|
|
50
|
+
selector: 'function',
|
|
51
|
+
format: ['camelCase', 'PascalCase'],
|
|
52
|
+
},
|
|
53
|
+
// Airbnb recommends PascalCase for classes (23.3), and although Airbnb does not make TypeScript recommendations, we are assuming this rule would similarly apply to anything "type like", including interfaces, type aliases, and enums
|
|
54
|
+
{
|
|
55
|
+
selector: 'typeLike',
|
|
56
|
+
format: ['PascalCase'],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
'default-param-last': 'off',
|
|
60
|
+
'@typescript-eslint/default-param-last': baseBestPractices.rules?.['default-param-last'],
|
|
61
|
+
'no-array-constructor': 'off',
|
|
62
|
+
'@typescript-eslint/no-array-constructor': baseStyle.rules?.['no-array-constructor'],
|
|
63
|
+
'no-empty-function': 'off',
|
|
64
|
+
'@typescript-eslint/no-empty-function': baseBestPractices.rules?.['no-empty-function'],
|
|
65
|
+
'no-loss-of-precision': 'error',
|
|
66
|
+
'no-loop-func': 'off',
|
|
67
|
+
'@typescript-eslint/no-loop-func': baseBestPractices.rules?.['no-loop-func'],
|
|
68
|
+
'no-magic-numbers': 'off',
|
|
69
|
+
'@typescript-eslint/no-magic-numbers': baseBestPractices.rules?.['no-magic-numbers'],
|
|
70
|
+
'no-shadow': 'off',
|
|
71
|
+
'@typescript-eslint/no-shadow': baseVariables.rules?.['no-shadow'],
|
|
72
|
+
'no-unused-expressions': 'off',
|
|
73
|
+
'@typescript-eslint/no-unused-expressions':
|
|
74
|
+
baseBestPractices.rules?.['no-unused-expressions'],
|
|
75
|
+
'no-useless-constructor': 'off',
|
|
76
|
+
'@typescript-eslint/no-useless-constructor': baseEs6.rules?.['no-useless-constructor'],
|
|
77
|
+
'require-await': 'off',
|
|
78
|
+
'@typescript-eslint/require-await': baseBestPractices.rules?.['require-await'],
|
|
79
|
+
|
|
80
|
+
// Append 'ts' and 'tsx' to Airbnb 'import/extensions' rule
|
|
81
|
+
// https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/extensions.md
|
|
82
|
+
'import/extensions': [
|
|
83
|
+
baseImportsRules['import/extensions'][0],
|
|
84
|
+
baseImportsRules['import/extensions'][1],
|
|
85
|
+
typeof baseImportsRules['import/extensions'][2] === 'object'
|
|
86
|
+
? {
|
|
87
|
+
...baseImportsRules['import/extensions'][2],
|
|
88
|
+
ts: 'never',
|
|
89
|
+
tsx: 'never',
|
|
90
|
+
}
|
|
91
|
+
: { ts: 'never', tsx: 'never' },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
97
|
+
rules: {
|
|
98
|
+
// The following rules are enabled in Airbnb config, but are already checked (more thoroughly) by the TypeScript compiler
|
|
99
|
+
// Some of the rules also fail in TypeScript files, for example: https://github.com/typescript-eslint/typescript-eslint/issues/662#issuecomment-507081586
|
|
100
|
+
// Rules are inspired by: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/eslint-recommended.ts
|
|
101
|
+
'constructor-super': 'off',
|
|
102
|
+
'getter-return': 'off',
|
|
103
|
+
'no-const-assign': 'off',
|
|
104
|
+
'no-dupe-args': 'off',
|
|
105
|
+
'no-dupe-class-members': 'off',
|
|
106
|
+
'no-dupe-keys': 'off',
|
|
107
|
+
'no-func-assign': 'off',
|
|
108
|
+
'no-import-assign': 'off',
|
|
109
|
+
'no-new-symbol': 'off',
|
|
110
|
+
'no-obj-calls': 'off',
|
|
111
|
+
'no-redeclare': 'off',
|
|
112
|
+
'no-setter-return': 'off',
|
|
113
|
+
'no-this-before-super': 'off',
|
|
114
|
+
'no-undef': 'off',
|
|
115
|
+
'no-unreachable': 'off',
|
|
116
|
+
'no-unsafe-negation': 'off',
|
|
117
|
+
'valid-typeof': 'off',
|
|
118
|
+
// The following rules are enabled in Airbnb config, but are recommended to be disabled within TypeScript projects
|
|
119
|
+
// See: https://github.com/typescript-eslint/typescript-eslint/blob/13583e65f5973da2a7ae8384493c5e00014db51b/docs/linting/TROUBLESHOOTING.md#eslint-plugin-import
|
|
120
|
+
'import/named': 'off',
|
|
121
|
+
'import/no-named-as-default-member': 'off',
|
|
122
|
+
'import/no-unresolved': 'off',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import prettier from 'eslint-config-prettier/flat';
|
|
2
|
+
import reactCompilerPlugin from 'eslint-plugin-react-compiler';
|
|
3
|
+
import { configs as reactHookConfigs } from 'eslint-plugin-react-hooks';
|
|
4
|
+
import globals from 'globals';
|
|
5
|
+
import * as tseslint from 'typescript-eslint';
|
|
6
|
+
|
|
7
|
+
import { createAirbnbConfig } from './airbnb/base.mjs';
|
|
8
|
+
import airbnbTypescript from './airbnb/typescript.mjs';
|
|
9
|
+
import { createCoreConfig } from './material-ui/config.mjs';
|
|
10
|
+
import muiPlugin from './material-ui/index.mjs';
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} [params]
|
|
13
|
+
* @param {boolean} [params.enableReactCompiler] - Whether the config is for spec files.
|
|
14
|
+
* @param {string} [params.baseDirectory] - The base directory for the configuration.
|
|
15
|
+
* @returns {import('eslint').Linter.Config[]}
|
|
16
|
+
*/
|
|
17
|
+
export function createBaseConfig({ enableReactCompiler = false, baseDirectory } = {}) {
|
|
18
|
+
return /** @type {import('eslint').Linter.Config[]} */ (
|
|
19
|
+
tseslint.config(
|
|
20
|
+
createAirbnbConfig({ baseDirectory }),
|
|
21
|
+
airbnbTypescript,
|
|
22
|
+
reactHookConfigs.recommended,
|
|
23
|
+
enableReactCompiler ? reactCompilerPlugin.configs.recommended : {},
|
|
24
|
+
prettier,
|
|
25
|
+
{
|
|
26
|
+
name: 'typescript-eslint-parser',
|
|
27
|
+
languageOptions: {
|
|
28
|
+
parser: tseslint.parser,
|
|
29
|
+
ecmaVersion: 7,
|
|
30
|
+
globals: {
|
|
31
|
+
...globals.es2020,
|
|
32
|
+
...globals.browser,
|
|
33
|
+
...globals.node,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
plugins: {
|
|
37
|
+
'@typescript-eslint': tseslint.plugin,
|
|
38
|
+
'material-ui': muiPlugin,
|
|
39
|
+
},
|
|
40
|
+
settings: {
|
|
41
|
+
'import/parsers': {
|
|
42
|
+
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
|
43
|
+
},
|
|
44
|
+
'import/resolver': {
|
|
45
|
+
typescript: {
|
|
46
|
+
project: ['tsconfig.node.json', 'apps/*/tsconfig.json', 'packages/*/tsconfig.json'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
extends: createCoreConfig({ reactCompilerEnabled: enableReactCompiler }),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
files: ['**/*.mjs'],
|
|
54
|
+
rules: {
|
|
55
|
+
'import/extensions': [
|
|
56
|
+
'error',
|
|
57
|
+
'ignorePackages',
|
|
58
|
+
{
|
|
59
|
+
js: 'always',
|
|
60
|
+
mjs: 'always',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import nextjs from '@next/eslint-plugin-next';
|
|
2
|
+
import * as tseslint from 'typescript-eslint';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @returns {import('eslint').Linter.Config[]}
|
|
6
|
+
*/
|
|
7
|
+
export function createDocsConfig() {
|
|
8
|
+
return /** @type {import('eslint').Linter.Config[]} */ (
|
|
9
|
+
tseslint.config(nextjs.flatConfig.recommended, {
|
|
10
|
+
settings: {
|
|
11
|
+
next: {
|
|
12
|
+
rootDir: 'docs',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
rules: {
|
|
16
|
+
'no-irregular-whitespace': ['error', { skipJSXText: true, skipStrings: true }],
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const restrictedMethods = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'];
|
|
2
|
+
|
|
3
|
+
const restrictedSyntaxRules = restrictedMethods.map((method) => ({
|
|
4
|
+
message: `Use global ${method} instead of window.${method}.`,
|
|
5
|
+
selector: `MemberExpression[object.name='window'][property.name='${method}']`,
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Object} [options]
|
|
10
|
+
* @param {boolean} [options.reactCompilerEnabled] - Whether the config is for spec files.
|
|
11
|
+
* @returns {import('typescript-eslint').InfiniteDepthConfigWithExtends[]}
|
|
12
|
+
*/
|
|
13
|
+
export function createCoreConfig(options = {}) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
name: 'material-ui-base',
|
|
17
|
+
rules: {
|
|
18
|
+
'no-redeclare': 'off',
|
|
19
|
+
'@typescript-eslint/no-redeclare': 'error',
|
|
20
|
+
'consistent-this': ['error', 'self'],
|
|
21
|
+
curly: ['error', 'all'],
|
|
22
|
+
'dot-notation': 'error',
|
|
23
|
+
// Just as bad as "max components per file"
|
|
24
|
+
'max-classes-per-file': 'off',
|
|
25
|
+
// Too interruptive
|
|
26
|
+
'no-alert': 'error',
|
|
27
|
+
// Stylistic opinion
|
|
28
|
+
'arrow-body-style': 'off',
|
|
29
|
+
// Allow warn and error for dev environments
|
|
30
|
+
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
31
|
+
'no-param-reassign': 'off', // It's fine.
|
|
32
|
+
// Airbnb use warn https://github.com/airbnb/javascript/blob/63098cbb6c05376dbefc9a91351f5727540c1ce1/packages/eslint-config-airbnb-base/rules/style.js#L97
|
|
33
|
+
// but eslint recommands error
|
|
34
|
+
'func-names': 'error',
|
|
35
|
+
|
|
36
|
+
'no-continue': 'off',
|
|
37
|
+
'no-constant-condition': 'error',
|
|
38
|
+
'no-implied-eval': 'error',
|
|
39
|
+
'no-throw-literal': 'error',
|
|
40
|
+
// Use the proptype inheritance chain
|
|
41
|
+
'no-prototype-builtins': 'off',
|
|
42
|
+
'no-return-await': 'error',
|
|
43
|
+
'no-underscore-dangle': 'error',
|
|
44
|
+
'nonblock-statement-body-position': 'error',
|
|
45
|
+
'prefer-arrow-callback': ['error', { allowNamedFunctions: true }],
|
|
46
|
+
// Destructuring harm grep potential.
|
|
47
|
+
'prefer-destructuring': 'off',
|
|
48
|
+
|
|
49
|
+
'no-use-before-define': 'off',
|
|
50
|
+
'@typescript-eslint/no-use-before-define': [
|
|
51
|
+
'error',
|
|
52
|
+
{
|
|
53
|
+
functions: false,
|
|
54
|
+
classes: true,
|
|
55
|
+
variables: true,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
'no-unused-vars': 'off',
|
|
59
|
+
'@typescript-eslint/no-unused-vars': [
|
|
60
|
+
'error',
|
|
61
|
+
{
|
|
62
|
+
vars: 'all',
|
|
63
|
+
args: 'after-used',
|
|
64
|
+
ignoreRestSiblings: true,
|
|
65
|
+
argsIgnorePattern: '^_',
|
|
66
|
+
caughtErrors: 'none',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
|
|
70
|
+
// Not needed in general, can be turned on for specific files
|
|
71
|
+
'import/prefer-default-export': 'off',
|
|
72
|
+
// Not sure why it doesn't work
|
|
73
|
+
'import/named': 'off',
|
|
74
|
+
'import/no-cycle': 'off',
|
|
75
|
+
// Missing yarn workspace support
|
|
76
|
+
'import/no-extraneous-dependencies': 'off',
|
|
77
|
+
// The code is already coupled to webpack. Prefer explicit coupling.
|
|
78
|
+
'import/no-webpack-loader-syntax': 'off',
|
|
79
|
+
'import/no-relative-packages': 'error',
|
|
80
|
+
|
|
81
|
+
// doesn't work?
|
|
82
|
+
'jsx-a11y/label-has-associated-control': [
|
|
83
|
+
'error',
|
|
84
|
+
{
|
|
85
|
+
// airbnb uses 'both' which requires nesting i.e. <label><input /></label>
|
|
86
|
+
// 'either' allows `htmlFor`
|
|
87
|
+
assert: 'either',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
// We are a library, we need to support it too
|
|
91
|
+
'jsx-a11y/no-autofocus': 'off',
|
|
92
|
+
|
|
93
|
+
'material-ui/docgen-ignore-before-comment': 'error',
|
|
94
|
+
'material-ui/rules-of-use-theme-variants': 'error',
|
|
95
|
+
'material-ui/no-empty-box': 'error',
|
|
96
|
+
'material-ui/no-styled-box': 'error',
|
|
97
|
+
'material-ui/straight-quotes': 'error',
|
|
98
|
+
|
|
99
|
+
'react-hooks/exhaustive-deps': ['error', { additionalHooks: 'useEnhancedEffect' }],
|
|
100
|
+
'react-hooks/rules-of-hooks': 'error',
|
|
101
|
+
|
|
102
|
+
'react/default-props-match-prop-types': [
|
|
103
|
+
'error',
|
|
104
|
+
{
|
|
105
|
+
// Otherwise the rule thinks inner props = outer props
|
|
106
|
+
// But in TypeScript we want to know that a certain prop is defined during render
|
|
107
|
+
// while it can be ommitted from the callsite.
|
|
108
|
+
// Then defaultProps (or default values) will make sure that the prop is defined during render
|
|
109
|
+
allowRequiredDefaults: true,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
// Can add verbosity to small functions making them harder to grok.
|
|
113
|
+
// Though we have to manually enforce it for function components with default values.
|
|
114
|
+
'react/destructuring-assignment': 'off',
|
|
115
|
+
'react/forbid-prop-types': 'off', // Too strict, no time for that
|
|
116
|
+
'react/jsx-curly-brace-presence': 'off', // broken
|
|
117
|
+
// airbnb is using .jsx
|
|
118
|
+
'react/jsx-filename-extension': ['error', { extensions: ['.js', '.tsx'] }],
|
|
119
|
+
// Prefer <React.Fragment> over <>.
|
|
120
|
+
'react/jsx-fragments': ['error', 'element'],
|
|
121
|
+
// Enforces premature optimization
|
|
122
|
+
'react/jsx-no-bind': 'off',
|
|
123
|
+
// We are a UI library.
|
|
124
|
+
'react/jsx-props-no-spreading': 'off',
|
|
125
|
+
// This rule is great for raising people awareness of what a key is and how it works.
|
|
126
|
+
'react/no-array-index-key': 'off',
|
|
127
|
+
'react/no-danger': 'error',
|
|
128
|
+
'react/no-unknown-property': ['error', { ignore: ['sx'] }],
|
|
129
|
+
'react/no-direct-mutation-state': 'error',
|
|
130
|
+
// Not always relevant
|
|
131
|
+
'react/require-default-props': 'off',
|
|
132
|
+
'react/sort-prop-types': 'error',
|
|
133
|
+
// This depends entirely on what you're doing. There's no universal pattern
|
|
134
|
+
'react/state-in-constructor': 'off',
|
|
135
|
+
// stylistic opinion. For conditional assignment we want it outside, otherwise as static
|
|
136
|
+
'react/static-property-placement': 'off',
|
|
137
|
+
// noopener is enough
|
|
138
|
+
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-target-blank.md#rule-options
|
|
139
|
+
'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
|
|
140
|
+
|
|
141
|
+
'no-restricted-syntax': [
|
|
142
|
+
'error',
|
|
143
|
+
{
|
|
144
|
+
message:
|
|
145
|
+
"Do not import default or named exports from React. Use a namespace import (import * as React from 'react';) instead.",
|
|
146
|
+
selector:
|
|
147
|
+
'ImportDeclaration[source.value="react"] ImportDefaultSpecifier, ImportDeclaration[source.value="react"] ImportSpecifier',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
message:
|
|
151
|
+
"Do not import default or named exports from ReactDOM. Use a namespace import (import * as ReactDOM from 'react-dom';) instead.",
|
|
152
|
+
selector:
|
|
153
|
+
'ImportDeclaration[source.value="react-dom"] ImportDefaultSpecifier, ImportDeclaration[source.value="react-dom"] ImportSpecifier',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
message:
|
|
157
|
+
"Do not import default or named exports from ReactDOM. Use a namespace import (import * as ReactDOM from 'react-dom/client';) instead.",
|
|
158
|
+
selector:
|
|
159
|
+
'ImportDeclaration[source.value="react-dom/client"] ImportDefaultSpecifier, ImportDeclaration[source.value="react-dom/client"] ImportSpecifier',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
message:
|
|
163
|
+
"Do not import default or named exports from ReactDOMServer. Use a namespace import (import * as ReactDOM from 'react-dom/server';) instead.",
|
|
164
|
+
selector:
|
|
165
|
+
'ImportDeclaration[source.value="react-dom/server"] ImportDefaultSpecifier, ImportDeclaration[source.value="react-dom/server"] ImportSpecifier',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
message:
|
|
169
|
+
"The 'use client' pragma can't be used with export * in the same module. This is not supported by Next.js.",
|
|
170
|
+
selector: 'ExpressionStatement[expression.value="use client"] ~ ExportAllDeclaration',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
message: 'Do not call `Error(...)` without `new`. Use `new Error(...)` instead.',
|
|
174
|
+
selector: "CallExpression[callee.name='Error']",
|
|
175
|
+
},
|
|
176
|
+
...restrictedSyntaxRules,
|
|
177
|
+
],
|
|
178
|
+
|
|
179
|
+
// We re-export default in many places, remove when https://github.com/airbnb/javascript/issues/2500 gets resolved
|
|
180
|
+
'no-restricted-exports': 'off',
|
|
181
|
+
// Avoid accidental auto-"fixes" https://github.com/jsx-eslint/eslint-plugin-react/issues/3458
|
|
182
|
+
'react/no-invalid-html-attribute': 'off',
|
|
183
|
+
|
|
184
|
+
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }],
|
|
185
|
+
'lines-around-directive': 'off',
|
|
186
|
+
...(options.reactCompilerEnabled ? { 'react-compiler/react-compiler': 'error' } : {}),
|
|
187
|
+
// Prevent the use of `e` as a shorthand for `event`, `error`, etc.
|
|
188
|
+
'id-denylist': ['error', 'e'],
|
|
189
|
+
'@typescript-eslint/return-await': 'off',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import disallowActiveElementAsKeyEventTarget from './rules/disallow-active-element-as-key-event-target.mjs';
|
|
2
|
+
import disallowReactApiInServerComponents from './rules/disallow-react-api-in-server-components.mjs';
|
|
3
|
+
import docgenIgnoreBeforeComment from './rules/docgen-ignore-before-comment.mjs';
|
|
4
|
+
import muiNameMatchesComponentName from './rules/mui-name-matches-component-name.mjs';
|
|
5
|
+
import noEmptyBox from './rules/no-empty-box.mjs';
|
|
6
|
+
import noRestrictedResolvedImports from './rules/no-restricted-resolved-imports.mjs';
|
|
7
|
+
import noStyledBox from './rules/no-styled-box.mjs';
|
|
8
|
+
import rulesOfUseThemeVariants from './rules/rules-of-use-theme-variants.mjs';
|
|
9
|
+
import straightQuotes from './rules/straight-quotes.mjs';
|
|
10
|
+
|
|
11
|
+
export default /** @type {import('eslint').ESLint.Plugin} */ ({
|
|
12
|
+
meta: {
|
|
13
|
+
name: '@mui/eslint-plugin-material-ui',
|
|
14
|
+
version: '0.1.0',
|
|
15
|
+
},
|
|
16
|
+
rules: {
|
|
17
|
+
'disallow-active-element-as-key-event-target': disallowActiveElementAsKeyEventTarget,
|
|
18
|
+
'docgen-ignore-before-comment': docgenIgnoreBeforeComment,
|
|
19
|
+
'mui-name-matches-component-name': muiNameMatchesComponentName,
|
|
20
|
+
'rules-of-use-theme-variants': rulesOfUseThemeVariants,
|
|
21
|
+
'no-empty-box': noEmptyBox,
|
|
22
|
+
'no-styled-box': noStyledBox,
|
|
23
|
+
'straight-quotes': straightQuotes,
|
|
24
|
+
'disallow-react-api-in-server-components': disallowReactApiInServerComponents,
|
|
25
|
+
'no-restricted-resolved-imports': noRestrictedResolvedImports,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @type {import('eslint').Rule.RuleModule}
|
|
3
|
+
*/
|
|
4
|
+
const rule = {
|
|
5
|
+
meta: {
|
|
6
|
+
messages: {
|
|
7
|
+
'keyboard-target':
|
|
8
|
+
"Don't use document.activeElement as a target for keyboard events. Prefer the actual element.",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
/**
|
|
13
|
+
* @param {import('estree').Node} node
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
function isDocumentActiveElementNode(node) {
|
|
17
|
+
return (
|
|
18
|
+
node.type === 'MemberExpression' &&
|
|
19
|
+
/** @type {import('estree').MemberExpression} */ (node).object.type === 'Identifier' &&
|
|
20
|
+
/** @type {import('estree').Identifier} */ (
|
|
21
|
+
/** @type {import('estree').MemberExpression} */ (node).object
|
|
22
|
+
).name === 'document' &&
|
|
23
|
+
/** @type {import('estree').MemberExpression} */ (node).property.type === 'Identifier' &&
|
|
24
|
+
/** @type {import('estree').Identifier} */ (
|
|
25
|
+
/** @type {import('estree').MemberExpression} */ (node).property
|
|
26
|
+
).name === 'activeElement'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
CallExpression(node) {
|
|
32
|
+
/** @type {string[]} */
|
|
33
|
+
const keyboardEventDispatchers = ['keyDown', 'keyUp'];
|
|
34
|
+
const {
|
|
35
|
+
arguments: [firstArgument],
|
|
36
|
+
callee,
|
|
37
|
+
} = node;
|
|
38
|
+
const isFireKeyboardEvent =
|
|
39
|
+
callee.type === 'MemberExpression' &&
|
|
40
|
+
/** @type {import('estree').MemberExpression} */ (callee).property.type ===
|
|
41
|
+
'Identifier' &&
|
|
42
|
+
keyboardEventDispatchers.includes(
|
|
43
|
+
/** @type {import('estree').Identifier} */ (
|
|
44
|
+
/** @type {import('estree').MemberExpression} */ (callee).property
|
|
45
|
+
).name,
|
|
46
|
+
) &&
|
|
47
|
+
/** @type {import('estree').MemberExpression} */ (callee).object.type === 'Identifier' &&
|
|
48
|
+
/** @type {import('estree').Identifier} */ (
|
|
49
|
+
/** @type {import('estree').MemberExpression} */ (callee).object
|
|
50
|
+
).name === 'fireEvent';
|
|
51
|
+
const targetsDocumentActiveElement =
|
|
52
|
+
firstArgument !== undefined &&
|
|
53
|
+
(firstArgument.type === 'TSNonNullExpression'
|
|
54
|
+
? isDocumentActiveElementNode(firstArgument.expression)
|
|
55
|
+
: isDocumentActiveElementNode(firstArgument));
|
|
56
|
+
|
|
57
|
+
if (isFireKeyboardEvent && targetsDocumentActiveElement) {
|
|
58
|
+
context.report({ messageId: 'keyboard-target', node: firstArgument });
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default rule;
|