@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.
Files changed (32) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/package.json +26 -23
  4. package/src/eslint/airbnb/base.mjs +18 -0
  5. package/src/eslint/airbnb/typescript.mjs +126 -0
  6. package/src/eslint/baseConfig.mjs +67 -0
  7. package/src/eslint/docsConfig.mjs +20 -0
  8. package/src/eslint/extensions.mjs +5 -0
  9. package/src/eslint/index.mjs +7 -0
  10. package/src/eslint/material-ui/config.mjs +193 -0
  11. package/src/eslint/material-ui/index.mjs +27 -0
  12. package/src/eslint/material-ui/rules/disallow-active-element-as-key-event-target.mjs +65 -0
  13. package/src/eslint/material-ui/rules/disallow-active-elements-as-key-event-target.test.mjs +71 -0
  14. package/src/eslint/material-ui/rules/disallow-react-api-in-server-components.mjs +64 -0
  15. package/src/eslint/material-ui/rules/docgen-ignore-before-comment.mjs +34 -0
  16. package/src/eslint/material-ui/rules/docgen-ignore-before-comment.test.mjs +56 -0
  17. package/src/eslint/material-ui/rules/mui-name-matches-component-name.mjs +161 -0
  18. package/src/eslint/material-ui/rules/mui-name-matches-component-name.test.mjs +257 -0
  19. package/src/eslint/material-ui/rules/no-empty-box.mjs +60 -0
  20. package/src/eslint/material-ui/rules/no-empty-box.test.mjs +42 -0
  21. package/src/eslint/material-ui/rules/no-restricted-resolved-imports.mjs +95 -0
  22. package/src/eslint/material-ui/rules/no-styled-box.mjs +53 -0
  23. package/src/eslint/material-ui/rules/no-styled-box.test.mjs +75 -0
  24. package/src/eslint/material-ui/rules/rules-of-use-theme-variants.mjs +124 -0
  25. package/src/eslint/material-ui/rules/rules-of-use-theme-variants.test.mjs +149 -0
  26. package/src/eslint/material-ui/rules/straight-quotes.mjs +43 -0
  27. package/src/eslint/material-ui/rules/straight-quotes.test.mjs +69 -0
  28. package/src/eslint/testConfig.mjs +104 -0
  29. package/src/estree-typescript.d.ts +21 -0
  30. package/src/prettier.mjs +34 -0
  31. package/src/setupVitest.ts +14 -0
  32. 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
@@ -0,0 +1,3 @@
1
+ # @mui/internal-code-infra
2
+
3
+ Build scripts and configs to be used across MUI repos.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.0.1",
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
- "emoji-regex": "^10.4.0",
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-module-utils": "^2.12.0",
33
- "eslint-plugin-import": "^2.31.0",
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.0",
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.1",
42
- "typescript-eslint": "^8.33.1"
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
- "@next/eslint-plugin-next": {
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
- "@typescript-eslint/parser": "^8.33.1",
60
- "eslint": "^9.28.0",
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.33.1"
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,5 @@
1
+ export const EXTENSION_JS = '?(c|m)js?(x)';
2
+ export const EXTENSION_TS = '?(c|m)[jt]s?(x)';
3
+ export const EXTENSION_TS_ONLY = '?(c|m)ts?(x)';
4
+ export const EXTENSION_DTS = `.d.${EXTENSION_TS_ONLY}`;
5
+ export const EXTENSION_TEST_FILE = `.test.${EXTENSION_TS}`;
@@ -0,0 +1,7 @@
1
+ /// <reference types="../estree-typescript" />
2
+ /// <reference types="../untyped-plugins" />
3
+
4
+ export * from './baseConfig.mjs';
5
+ export * from './docsConfig.mjs';
6
+ export * from './testConfig.mjs';
7
+ export * from './extensions.mjs';
@@ -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;