eslint-plugin-mui-v7 1.0.0 → 1.2.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 (4) hide show
  1. package/README.md +124 -95
  2. package/index.cjs +104 -121
  3. package/index.js +104 -121
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,17 +1,21 @@
1
1
  # eslint-plugin-mui-v7
2
2
 
3
- > ESLint plugin for Material-UI v7 with educational and friendly error messages
3
+ > ESLint plugin focused on Material-UI V6 to V7 **breaking changes** with educational error messages
4
4
 
5
- Automatically detect incorrect usage of Material-UI V7 and teach developers the right way through helpful messages with emojis and examples!
5
+ Automatically detect code that **BREAKS** when migrating from MUI V6 to V7 and teach developers the correct way through helpful messages with emojis and examples!
6
+
7
+ ## 🎯 Philosophy
8
+
9
+ This plugin focuses on **breaking changes only** - code that will actually break when upgrading to V7. We don't warn about best practices or style preferences, just things that will cause errors.
6
10
 
7
11
  ## ✨ Features
8
12
 
9
- - **Detect deprecated deep imports** - No more `import createTheme from '@mui/material/styles/createTheme'`
10
- - **Catch Grid2 usage** - Grid2 was renamed to Grid in V7
11
- - **Find moved @mui/lab components** - Alert, Skeleton, Rating, etc. are now in @mui/material
12
- - **Detect deprecated props** - onBackdropClick, size="normal", Hidden component
13
- - **Grid item prop detection** - Grid doesn't use `item` prop anymore, use `size` instead
14
- - ⚠️ **Theme variables suggestion** - Use `theme.vars.*` for automatic dark mode support
13
+ - 🚀 **Detect Unstable_Grid2 usage** - Now promoted to stable Grid
14
+ - ⚠️ **Catch Grid2 usage** - Grid2 was renamed to Grid in V7
15
+ - 🎯 **Grid item prop detection** - Grid doesn't use `item` prop anymore, use `size` instead
16
+ - **Find moved @mui/lab components** - Alert, Skeleton, Rating, etc. are now in @mui/material
17
+ - 🔄 **Detect deprecated props** - onBackdropClick, size="normal", Hidden component
18
+ - 💡 **Theme variables suggestion** - Use `theme.vars.*` for automatic dark mode support (optional)
15
19
  - 🔧 **Auto-fix available** for most rules!
16
20
 
17
21
  ## 📦 Installation
@@ -20,9 +24,20 @@ Automatically detect incorrect usage of Material-UI V7 and teach developers the
20
24
  npm install --save-dev eslint-plugin-mui-v7
21
25
  ```
22
26
 
23
- ## 🚀 Usage
27
+ ## 🚀 Quick Start
28
+
29
+ ### ESLint 9+ (Flat Config) - Recommended
30
+
31
+ ```javascript
32
+ // eslint.config.js
33
+ import muiV7Plugin from 'eslint-plugin-mui-v7'
34
+
35
+ export default [
36
+ muiV7Plugin.configs.recommended, // ✅ Apply all recommended rules
37
+ ]
38
+ ```
24
39
 
25
- ### ESLint 9+ (Flat Config)
40
+ ### Manual Configuration
26
41
 
27
42
  ```javascript
28
43
  // eslint.config.js
@@ -34,15 +49,14 @@ export default [
34
49
  'mui-v7': muiV7Plugin,
35
50
  },
36
51
  rules: {
37
- // Errors (block code)
38
- 'mui-v7/no-deep-imports': 'error',
52
+ // Breaking changes - ERRORS (código quebra)
53
+ 'mui-v7/no-unstable-grid': 'error',
39
54
  'mui-v7/no-grid2-import': 'error',
40
- 'mui-v7/no-lab-imports': 'error',
41
55
  'mui-v7/no-grid-item-prop': 'error',
56
+ 'mui-v7/no-lab-imports': 'error',
42
57
  'mui-v7/no-deprecated-props': 'error',
43
58
 
44
- // Warnings (suggest improvements)
45
- 'mui-v7/no-old-grid-import': 'warn',
59
+ // Best practices - WARNINGS (sugestões)
46
60
  'mui-v7/prefer-theme-vars': 'warn',
47
61
  },
48
62
  },
@@ -56,42 +70,33 @@ export default [
56
70
  module.exports = {
57
71
  plugins: ['mui-v7'],
58
72
  rules: {
59
- 'mui-v7/no-deep-imports': 'error',
73
+ 'mui-v7/no-unstable-grid': 'error',
60
74
  'mui-v7/no-grid2-import': 'error',
61
- 'mui-v7/no-lab-imports': 'error',
62
75
  'mui-v7/no-grid-item-prop': 'error',
76
+ 'mui-v7/no-lab-imports': 'error',
63
77
  'mui-v7/no-deprecated-props': 'error',
64
- 'mui-v7/no-old-grid-import': 'warn',
65
78
  'mui-v7/prefer-theme-vars': 'warn',
66
79
  },
67
80
  }
68
81
  ```
69
82
 
70
- ### Using Recommended Config
71
-
72
- ```javascript
73
- // eslint.config.js
74
- import muiV7Plugin from 'eslint-plugin-mui-v7'
75
-
76
- export default [
77
- muiV7Plugin.configs.recommended, // Applies all recommended rules
78
- ]
79
- ```
80
-
81
83
  ## 📋 Rules
82
84
 
83
- ### 🚨 Error Rules (Must Fix)
85
+ ### 🚨 Breaking Changes (Errors)
86
+
87
+ These rules detect code that **WILL BREAK** in MUI V7.
84
88
 
85
- #### `mui-v7/no-deep-imports`
89
+ #### `mui-v7/no-unstable-grid` ✨ NEW in v1.1.0
86
90
 
87
- Deep imports with more than one level are not supported in MUI V7.
91
+ Unstable_Grid2 was promoted to stable Grid in V7.
88
92
 
89
93
  ```typescript
90
- // ❌ Bad
91
- import createTheme from '@mui/material/styles/createTheme'
94
+ // ❌ Breaks in V7
95
+ import Grid from '@mui/material/Unstable_Grid2'
96
+ import Grid2 from '@mui/material/Unstable_Grid2'
92
97
 
93
- // ✅ Good
94
- import { createTheme } from '@mui/material/styles'
98
+ // ✅ Recommended
99
+ import { Grid } from '@mui/material'
95
100
  ```
96
101
 
97
102
  #### `mui-v7/no-grid2-import`
@@ -99,102 +104,94 @@ import { createTheme } from '@mui/material/styles'
99
104
  Grid2 was renamed to Grid in V7.
100
105
 
101
106
  ```typescript
102
- // ❌ Bad
107
+ // ❌ Breaks in V7
103
108
  import Grid2 from '@mui/material/Grid2'
109
+ import { grid2Classes } from '@mui/material/Grid2'
104
110
 
105
- // ✅ Good
106
- import Grid from '@mui/material/Grid'
111
+ // ✅ Recommended
112
+ import { Grid } from '@mui/material'
113
+ import { gridClasses } from '@mui/material'
107
114
  ```
108
115
 
109
- #### `mui-v7/no-lab-imports`
110
-
111
- Components moved from @mui/lab to @mui/material.
112
-
113
- ```typescript
114
- // ❌ Bad
115
- import Alert from '@mui/lab/Alert'
116
- import Skeleton from '@mui/lab/Skeleton'
117
-
118
- // ✅ Good
119
- import Alert from '@mui/material/Alert'
120
- import Skeleton from '@mui/material/Skeleton'
121
- ```
122
-
123
- **Moved components:** Alert, AlertTitle, Autocomplete, AvatarGroup, Pagination, PaginationItem, Rating, Skeleton, SpeedDial, SpeedDialAction, SpeedDialIcon, TabContext, TabList, TabPanel, Timeline*, ToggleButton, ToggleButtonGroup, TreeView, TreeItem
124
-
125
116
  #### `mui-v7/no-grid-item-prop`
126
117
 
127
118
  Grid doesn't use `item` prop anymore, use `size` instead.
128
119
 
129
120
  ```typescript
130
- // ❌ Bad
131
- <Grid item xs={12} md={6}>
121
+ // ❌ Breaks in V7
122
+ <Grid item xs={12} sm={6} md={4}>
132
123
  Content
133
124
  </Grid>
134
125
 
135
- // ✅ Good
136
- <Grid size={{ xs: 12, md: 6 }}>
126
+ // ✅ Works in V7
127
+ <Grid size={{ xs: 12, sm: 6, md: 4 }}>
137
128
  Content
138
129
  </Grid>
139
130
  ```
140
131
 
132
+ #### `mui-v7/no-lab-imports`
133
+
134
+ Components moved from @mui/lab to @mui/material.
135
+
136
+ ```typescript
137
+ // ❌ Breaks in V7
138
+ import { Alert } from '@mui/lab'
139
+ import { Skeleton } from '@mui/lab'
140
+
141
+ // ✅ Recommended
142
+ import { Alert } from '@mui/material'
143
+ import { Skeleton } from '@mui/material'
144
+ ```
145
+
146
+ **Moved components:** Alert, AlertTitle, Autocomplete, AvatarGroup, Pagination, PaginationItem, Rating, Skeleton, SpeedDial, SpeedDialAction, SpeedDialIcon, TabContext, TabList, TabPanel, Timeline*, ToggleButton, ToggleButtonGroup, TreeView, TreeItem
147
+
141
148
  #### `mui-v7/no-deprecated-props`
142
149
 
143
- Detects deprecated props in various components.
150
+ Detects props removed in V7.
144
151
 
145
152
  ```typescript
146
- // ❌ Bad: Dialog.onBackdropClick
153
+ // ❌ Dialog.onBackdropClick - REMOVED
147
154
  <Dialog onBackdropClick={handleClick}>
148
155
 
149
- // ✅ Good
156
+ // ✅ Use onClose with reason check
150
157
  <Dialog onClose={(event, reason) => {
151
158
  if (reason === 'backdropClick') {
152
159
  // Your logic here
153
160
  }
154
161
  }}>
155
162
 
156
- // ❌ Bad: InputLabel size="normal"
163
+ // ❌ InputLabel size="normal" - RENAMED
157
164
  <InputLabel size="normal">
158
165
 
159
- // ✅ Good
166
+ // ✅ Use size="medium"
160
167
  <InputLabel size="medium">
161
168
 
162
- // ❌ Bad: Hidden component
169
+ // ❌ Hidden component - REMOVED
163
170
  <Hidden xlUp><Paper /></Hidden>
164
171
 
165
- // ✅ Good: Use sx prop
172
+ // ✅ Use sx prop
166
173
  <Paper sx={{ display: { xl: 'none' } }} />
167
174
 
168
- // ✅ Good: Use useMediaQuery
175
+ // ✅ Or use useMediaQuery
169
176
  const hidden = useMediaQuery(theme => theme.breakpoints.up('xl'))
170
177
  return hidden ? null : <Paper />
171
178
  ```
172
179
 
173
- ### ⚠️ Warning Rules (Suggestions)
174
-
175
- #### `mui-v7/no-old-grid-import`
180
+ ### 💡 Best Practices (Warnings)
176
181
 
177
- Suggests migrating from old Grid to the new one.
178
-
179
- ```typescript
180
- // ⚠️ If you want to keep old Grid
181
- import Grid from '@mui/material/GridLegacy'
182
-
183
- // ✅ Recommended: Migrate to new Grid
184
- import Grid from '@mui/material/Grid'
185
- ```
182
+ These are suggestions, not breaking changes.
186
183
 
187
184
  #### `mui-v7/prefer-theme-vars`
188
185
 
189
- When using `cssVariables: true`, use `theme.vars.*` for automatic dark mode support.
186
+ When using `cssVariables: true`, use `theme.vars.*` for better performance and automatic dark mode.
190
187
 
191
188
  ```typescript
192
- // ⚠️ Warning (doesn't change with dark mode)
189
+ // ⚠️ Works but doesn't change with dark mode automatically
193
190
  const Custom = styled('div')(({ theme }) => ({
194
191
  color: theme.palette.text.primary,
195
192
  }))
196
193
 
197
- // ✅ Good (changes automatically with dark mode)
194
+ // ✅ Better: Changes automatically with dark mode
198
195
  const Custom = styled('div')(({ theme }) => ({
199
196
  color: theme.vars.palette.text.primary,
200
197
  }))
@@ -214,41 +211,73 @@ The plugin provides educational messages with emojis and examples:
214
211
  <Grid size={{ xs: 12, sm: 6, md: 4 }}>
215
212
 
216
213
  💡 A nova sintaxe é mais limpa e poderosa!
217
- Você pode usar offset, push, pull e mais.
214
+ Você pode usar: size, offset, spacing responsivo e mais.
218
215
  ```
219
216
 
220
- ## 🔧 Configuration Options
217
+ ## 🔧 Configuration Presets
218
+
219
+ ### `recommended` - Balanced (Default)
221
220
 
222
- ### Severity Levels
221
+ Breaking changes as **errors**, best practices as **warnings**.
223
222
 
224
223
  ```javascript
225
- rules: {
226
- 'mui-v7/no-deep-imports': 'error', // Blocks code
227
- 'mui-v7/no-deep-imports': 'warn', // Shows warning
228
- 'mui-v7/no-deep-imports': 'off', // Disables rule
229
- }
224
+ import muiV7Plugin from 'eslint-plugin-mui-v7'
225
+
226
+ export default [
227
+ muiV7Plugin.configs.recommended,
228
+ ]
230
229
  ```
231
230
 
232
- ### Recommended Config
231
+ ### `strict` - Strict Mode
232
+
233
+ Everything as **errors** (including best practices).
233
234
 
234
235
  ```javascript
235
236
  import muiV7Plugin from 'eslint-plugin-mui-v7'
236
237
 
237
238
  export default [
238
- muiV7Plugin.configs.recommended, // All errors + warnings
239
+ muiV7Plugin.configs.strict,
239
240
  ]
240
241
  ```
241
242
 
242
- ### Strict Config
243
+ ## 🆕 What's New in v1.1.0
244
+
245
+ ### Added
246
+ - ✨ New rule `no-unstable-grid` - Detects Unstable_Grid2 usage
243
247
 
248
+ ### Changed
249
+ - 📝 All import examples now show recommended style: `import { Grid } from '@mui/material'`
250
+ - 🎯 Refocused on breaking changes only (removed non-breaking rules)
251
+ - 📦 Updated plugin description and categories
252
+
253
+ ### Removed
254
+ - ❌ `no-deep-imports` - Not a breaking change in V7
255
+ - ❌ `no-old-grid-import` - Confusing and not a breaking change
256
+
257
+ ## 📚 Migration Guide
258
+
259
+ 1. Install the plugin:
260
+ ```bash
261
+ npm install --save-dev eslint-plugin-mui-v7
262
+ ```
263
+
264
+ 2. Add to your ESLint config:
244
265
  ```javascript
266
+ // eslint.config.js
245
267
  import muiV7Plugin from 'eslint-plugin-mui-v7'
246
268
 
247
269
  export default [
248
- muiV7Plugin.configs.strict, // Everything as error
270
+ muiV7Plugin.configs.recommended,
249
271
  ]
250
272
  ```
251
273
 
274
+ 3. Run ESLint:
275
+ ```bash
276
+ npx eslint . --fix
277
+ ```
278
+
279
+ 4. Fix remaining issues manually (the plugin will guide you!)
280
+
252
281
  ## 🤝 Contributing
253
282
 
254
283
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -269,4 +298,4 @@ Created by **Matheus Pimenta** (Koda AI Studio) + **Claude Code**
269
298
 
270
299
  ---
271
300
 
272
- **Keywords:** eslint, mui, material-ui, mui-v7, react, typescript, linter, code-quality
301
+ **Keywords:** eslint, mui, material-ui, mui-v7, react, typescript, linter, code-quality, migration, breaking-changes
package/index.cjs CHANGED
@@ -1,28 +1,32 @@
1
1
  /**
2
- * ESLint Plugin Customizado para MUI V7 (CommonJS version)
2
+ * ESLint Plugin para MUI V7 - Foca em Breaking Changes (CommonJS version)
3
3
  *
4
- * Detecta automaticamente usos incorretos do Material-UI V7 e fornece
5
- * mensagens educativas para ensinar a forma correta.
4
+ * Detecta automaticamente código que QUEBRA na migração V6 → V7
5
+ * e fornece mensagens educativas para corrigir.
6
6
  *
7
+ * @version 1.2.0
7
8
  * @created 2025-01-26
9
+ * @updated 2025-01-29
8
10
  * @author Matheus (Koda AI Studio) + Claude Code
9
11
  */
10
12
 
11
13
  const muiV7Rules = {
12
- 'no-deep-imports': {
14
+ 'no-unstable-grid': {
13
15
  meta: {
14
16
  type: 'problem',
15
17
  docs: {
16
- description: 'Proíbe deep imports (mais de um nível) do MUI V7',
17
- category: 'Best Practices',
18
+ description: 'Unstable_Grid2 foi promovido para Grid no MUI V7',
19
+ category: 'Breaking Changes',
18
20
  recommended: true,
19
21
  },
20
22
  messages: {
21
- deepImport: ' Deep imports não são mais suportados no MUI V7.\n\n' +
22
- '🔧 Forma incorreta:\n' +
23
- ' import createTheme from "@mui/material/styles/createTheme"\n\n' +
24
- ' Forma correta:\n' +
25
- ' import { createTheme } from "@mui/material/styles"',
23
+ unstableGrid: '🚀 Unstable_Grid2 foi promovido para Grid estável no MUI V7!\n\n' +
24
+ '🔧 Forma antiga (V6):\n' +
25
+ ' import Grid from "@mui/material/Unstable_Grid2"\n' +
26
+ ' import Grid2 from "@mui/material/Unstable_Grid2"\n\n' +
27
+ ' Forma nova (V7):\n' +
28
+ ' import { Grid } from "@mui/material"\n\n' +
29
+ '💡 O Grid agora é estável e usa a prop `size`!',
26
30
  },
27
31
  schema: [],
28
32
  fixable: 'code',
@@ -32,30 +36,14 @@ const muiV7Rules = {
32
36
  ImportDeclaration(node) {
33
37
  const source = node.source.value;
34
38
 
35
- // Detecta imports com mais de um nível (ex: @mui/material/styles/createTheme)
36
- if (source.startsWith('@mui/')) {
37
- const parts = source.split('/');
38
- // @mui/material/styles/createTheme -> 4 partes (deep import)
39
- // @mui/material/styles -> 3 partes (OK)
40
- if (parts.length > 3) {
41
- context.report({
42
- node,
43
- messageId: 'deepImport',
44
- fix(fixer) {
45
- // Tenta converter para named import
46
- const specifier = node.specifiers[0];
47
- if (specifier && specifier.type === 'ImportDefaultSpecifier') {
48
- const importName = specifier.local.name;
49
- const newPath = parts.slice(0, 3).join('/'); // Remove último nível
50
- return fixer.replaceText(
51
- node,
52
- `import { ${importName} } from "${newPath}"`
53
- );
54
- }
55
- return null;
56
- },
57
- });
58
- }
39
+ if (source === '@mui/material/Unstable_Grid2') {
40
+ context.report({
41
+ node,
42
+ messageId: 'unstableGrid',
43
+ fix(fixer) {
44
+ return fixer.replaceText(node.source, '"@mui/material"');
45
+ },
46
+ });
59
47
  }
60
48
  },
61
49
  };
@@ -67,7 +55,7 @@ const muiV7Rules = {
67
55
  type: 'problem',
68
56
  docs: {
69
57
  description: 'Grid2 foi renomeado para Grid no MUI V7',
70
- category: 'Best Practices',
58
+ category: 'Breaking Changes',
71
59
  recommended: true,
72
60
  },
73
61
  messages: {
@@ -75,10 +63,10 @@ const muiV7Rules = {
75
63
  '🔧 Forma antiga (V6):\n' +
76
64
  ' import Grid2 from "@mui/material/Grid2"\n' +
77
65
  ' import { grid2Classes } from "@mui/material/Grid2"\n\n' +
78
- '✅ Forma nova (V7):\n' +
79
- ' import Grid from "@mui/material/Grid"\n' +
80
- ' import { gridClasses } from "@mui/material/Grid"\n\n' +
81
- '💡 Dica: O novo Grid é mais poderoso e responsivo!',
66
+ '✅ Recomendado:\n' +
67
+ ' import { Grid } from "@mui/material"\n' +
68
+ ' import { gridClasses } from "@mui/material"\n\n' +
69
+ '💡 O novo Grid é mais poderoso e usa a prop `size`!',
82
70
  },
83
71
  schema: [],
84
72
  fixable: 'code',
@@ -93,23 +81,7 @@ const muiV7Rules = {
93
81
  node,
94
82
  messageId: 'grid2Import',
95
83
  fix(fixer) {
96
- const fixes = [
97
- fixer.replaceText(node.source, '"@mui/material/Grid"')
98
- ];
99
-
100
- // Renomeia Grid2 -> Grid e grid2Classes -> gridClasses
101
- node.specifiers.forEach(spec => {
102
- if (spec.imported) {
103
- const name = spec.imported.name;
104
- if (name.includes('grid2')) {
105
- const newName = name.replace('grid2', 'grid');
106
- // Nota: Isso só funciona bem para casos simples
107
- // Para casos complexos, o usuário precisa fazer manual
108
- }
109
- }
110
- });
111
-
112
- return fixes;
84
+ return fixer.replaceText(node.source, '"@mui/material"');
113
85
  },
114
86
  });
115
87
  }
@@ -118,67 +90,20 @@ const muiV7Rules = {
118
90
  },
119
91
  },
120
92
 
121
- 'no-old-grid-import': {
122
- meta: {
123
- type: 'suggestion',
124
- docs: {
125
- description: 'Sugere migração do Grid antigo para o novo',
126
- category: 'Best Practices',
127
- recommended: false,
128
- },
129
- messages: {
130
- oldGrid: '💡 O Grid antigo agora é GridLegacy. Considere migrar para o novo Grid!\n\n' +
131
- '🔧 Se quiser manter o Grid antigo:\n' +
132
- ' import Grid from "@mui/material/GridLegacy"\n' +
133
- ' import { gridLegacyClasses } from "@mui/material/GridLegacy"\n\n' +
134
- '✅ Recomendado: Migrar para o novo Grid:\n' +
135
- ' import Grid from "@mui/material/Grid"\n\n' +
136
- '📚 O novo Grid usa `size` em vez de `xs/sm/md`:\n' +
137
- ' <Grid size={{ xs: 12, md: 6 }}>Conteúdo</Grid>',
138
- },
139
- schema: [],
140
- },
141
- create(context) {
142
- const sourceCode = context.getSourceCode();
143
-
144
- return {
145
- ImportDeclaration(node) {
146
- const source = node.source.value;
147
-
148
- // Detecta import Grid from '@mui/material/Grid'
149
- if (source === '@mui/material/Grid') {
150
- const defaultImport = node.specifiers.find(
151
- spec => spec.type === 'ImportDefaultSpecifier'
152
- );
153
-
154
- if (defaultImport) {
155
- // Verifica se está usando props antigas (xs, sm, md) no código
156
- // Isso é apenas um aviso suave, não um erro
157
- context.report({
158
- node,
159
- messageId: 'oldGrid',
160
- });
161
- }
162
- }
163
- },
164
- };
165
- },
166
- },
167
-
168
93
  'no-lab-imports': {
169
94
  meta: {
170
95
  type: 'problem',
171
96
  docs: {
172
97
  description: 'Componentes movidos de @mui/lab para @mui/material',
173
- category: 'Best Practices',
98
+ category: 'Breaking Changes',
174
99
  recommended: true,
175
100
  },
176
101
  messages: {
177
102
  labImport: '✨ Este componente foi movido para @mui/material no V7!\n\n' +
178
103
  '🔧 Forma antiga (V6):\n' +
179
- ' import {{ component }} from "@mui/lab/{{ component }}"\n\n' +
180
- '✅ Forma nova (V7):\n' +
181
- ' import {{ component }} from "@mui/material/{{ component }}"\n\n' +
104
+ ' import {{ component }} from "@mui/lab"\n\n' +
105
+ '✅ Recomendado:\n' +
106
+ ' import { {{ component }} } from "@mui/material"\n\n' +
182
107
  '📦 Componentes movidos: Alert, Autocomplete, Pagination, Rating,\n' +
183
108
  ' Skeleton, SpeedDial, ToggleButton, AvatarGroup, e mais!',
184
109
  },
@@ -216,10 +141,7 @@ const muiV7Rules = {
216
141
  messageId: 'labImport',
217
142
  data: { component: componentName },
218
143
  fix(fixer) {
219
- return fixer.replaceText(
220
- node.source,
221
- `"@mui/material/${componentName}"`
222
- );
144
+ return fixer.replaceText(node.source, '"@mui/material"');
223
145
  },
224
146
  });
225
147
  }
@@ -235,7 +157,7 @@ const muiV7Rules = {
235
157
  type: 'problem',
236
158
  docs: {
237
159
  description: 'Grid não usa mais a prop `item`, agora usa `size`',
238
- category: 'Best Practices',
160
+ category: 'Breaking Changes',
239
161
  recommended: true,
240
162
  },
241
163
  messages: {
@@ -245,7 +167,7 @@ const muiV7Rules = {
245
167
  '✅ Forma nova (V7):\n' +
246
168
  ' <Grid size={{ xs: 12, sm: 6, md: 4 }}>\n\n' +
247
169
  '💡 A nova sintaxe é mais limpa e poderosa!\n' +
248
- ' Você pode usar offset, push, pull e mais.',
170
+ ' Você pode usar: size, offset, spacing responsivo e mais.',
249
171
  },
250
172
  schema: [],
251
173
  },
@@ -279,7 +201,7 @@ const muiV7Rules = {
279
201
  type: 'problem',
280
202
  docs: {
281
203
  description: 'Detecta props depreciadas no MUI V7',
282
- category: 'Best Practices',
204
+ category: 'Breaking Changes',
283
205
  recommended: true,
284
206
  },
285
207
  messages: {
@@ -379,6 +301,55 @@ const muiV7Rules = {
379
301
  create(context) {
380
302
  const sourceCode = context.getSourceCode();
381
303
 
304
+ /**
305
+ * Verifica se o node está dentro de um ternário que checa theme.vars
306
+ * Exemplo: theme.vars ? `${theme.vars.palette.primary.main}` : `${theme.palette.primary.main}`
307
+ */
308
+ function isInsideThemeVarsConditional(node) {
309
+ let current = node;
310
+
311
+ // Sobe até 10 níveis na árvore AST procurando por ConditionalExpression
312
+ for (let i = 0; i < 10; i++) {
313
+ if (!current.parent) break;
314
+ current = current.parent;
315
+
316
+ // Se encontrar um ternário (ConditionalExpression)
317
+ if (current.type === 'ConditionalExpression') {
318
+ // Verifica se o teste é "theme.vars"
319
+ const test = current.test;
320
+ if (
321
+ test &&
322
+ test.type === 'MemberExpression' &&
323
+ test.object &&
324
+ test.object.name === 'theme' &&
325
+ test.property &&
326
+ test.property.name === 'vars'
327
+ ) {
328
+ // Se estamos no consequent (parte do `?`), não reportar
329
+ // Apenas reportar se estamos no alternate (parte do `:`)
330
+ return true; // Ignora warnings quando dentro de ternário com theme.vars
331
+ }
332
+ }
333
+ }
334
+
335
+ return false;
336
+ }
337
+
338
+ /**
339
+ * Verifica se está dentro de uma função sx que usa theme.vars!
340
+ * Exemplo: sx={(theme) => ({ background: `${theme.vars!.palette...}` })}
341
+ */
342
+ function isUsingNonNullAssertion(node) {
343
+ const sourceText = sourceCode.getText(node.parent);
344
+
345
+ // Procura por theme.vars! (non-null assertion)
346
+ if (sourceText.includes('theme.vars!')) {
347
+ return true;
348
+ }
349
+
350
+ return false;
351
+ }
352
+
382
353
  return {
383
354
  MemberExpression(node) {
384
355
  // Detecta theme.palette.* (sem .vars)
@@ -393,6 +364,16 @@ const muiV7Rules = {
393
364
  // Verifica se não é theme.vars.palette
394
365
  const parent = node.object.object;
395
366
  if (parent.type === 'Identifier' && parent.name === 'theme') {
367
+ // Ignora se estiver dentro de um ternário que checa theme.vars
368
+ if (isInsideThemeVarsConditional(node)) {
369
+ return;
370
+ }
371
+
372
+ // Ignora se já está usando theme.vars! (non-null assertion)
373
+ if (isUsingNonNullAssertion(node)) {
374
+ return;
375
+ }
376
+
396
377
  context.report({
397
378
  node,
398
379
  messageId: 'useThemeVars',
@@ -412,24 +393,26 @@ const plugin = {
412
393
  recommended: {
413
394
  plugins: ['mui-v7'],
414
395
  rules: {
415
- 'mui-v7/no-deep-imports': 'error',
396
+ // Breaking changes - ERRORS (código quebra)
397
+ 'mui-v7/no-unstable-grid': 'error',
416
398
  'mui-v7/no-grid2-import': 'error',
417
- 'mui-v7/no-lab-imports': 'error',
418
399
  'mui-v7/no-grid-item-prop': 'error',
400
+ 'mui-v7/no-lab-imports': 'error',
419
401
  'mui-v7/no-deprecated-props': 'error',
420
- 'mui-v7/no-old-grid-import': 'warn',
402
+ // Best practices - WARNINGS (sugestões)
421
403
  'mui-v7/prefer-theme-vars': 'warn',
422
404
  },
423
405
  },
424
406
  strict: {
425
407
  plugins: ['mui-v7'],
426
408
  rules: {
427
- 'mui-v7/no-deep-imports': 'error',
409
+ // Breaking changes - ERRORS
410
+ 'mui-v7/no-unstable-grid': 'error',
428
411
  'mui-v7/no-grid2-import': 'error',
429
- 'mui-v7/no-lab-imports': 'error',
430
412
  'mui-v7/no-grid-item-prop': 'error',
413
+ 'mui-v7/no-lab-imports': 'error',
431
414
  'mui-v7/no-deprecated-props': 'error',
432
- 'mui-v7/no-old-grid-import': 'error',
415
+ // Best practices - ERRORS também no strict
433
416
  'mui-v7/prefer-theme-vars': 'error',
434
417
  },
435
418
  },
package/index.js CHANGED
@@ -1,28 +1,32 @@
1
1
  /**
2
- * ESLint Plugin Customizado para MUI V7
2
+ * ESLint Plugin para MUI V7 - Foca em Breaking Changes
3
3
  *
4
- * Detecta automaticamente usos incorretos do Material-UI V7 e fornece
5
- * mensagens educativas para ensinar a forma correta.
4
+ * Detecta automaticamente código que QUEBRA na migração V6 → V7
5
+ * e fornece mensagens educativas para corrigir.
6
6
  *
7
+ * @version 1.2.0
7
8
  * @created 2025-01-26
9
+ * @updated 2025-01-29
8
10
  * @author Matheus (Koda AI Studio) + Claude Code
9
11
  */
10
12
 
11
13
  const muiV7Rules = {
12
- 'no-deep-imports': {
14
+ 'no-unstable-grid': {
13
15
  meta: {
14
16
  type: 'problem',
15
17
  docs: {
16
- description: 'Proíbe deep imports (mais de um nível) do MUI V7',
17
- category: 'Best Practices',
18
+ description: 'Unstable_Grid2 foi promovido para Grid no MUI V7',
19
+ category: 'Breaking Changes',
18
20
  recommended: true,
19
21
  },
20
22
  messages: {
21
- deepImport: ' Deep imports não são mais suportados no MUI V7.\n\n' +
22
- '🔧 Forma incorreta:\n' +
23
- ' import createTheme from "@mui/material/styles/createTheme"\n\n' +
24
- ' Forma correta:\n' +
25
- ' import { createTheme } from "@mui/material/styles"',
23
+ unstableGrid: '🚀 Unstable_Grid2 foi promovido para Grid estável no MUI V7!\n\n' +
24
+ '🔧 Forma antiga (V6):\n' +
25
+ ' import Grid from "@mui/material/Unstable_Grid2"\n' +
26
+ ' import Grid2 from "@mui/material/Unstable_Grid2"\n\n' +
27
+ ' Forma nova (V7):\n' +
28
+ ' import { Grid } from "@mui/material"\n\n' +
29
+ '💡 O Grid agora é estável e usa a prop `size`!',
26
30
  },
27
31
  schema: [],
28
32
  fixable: 'code',
@@ -32,30 +36,14 @@ const muiV7Rules = {
32
36
  ImportDeclaration(node) {
33
37
  const source = node.source.value;
34
38
 
35
- // Detecta imports com mais de um nível (ex: @mui/material/styles/createTheme)
36
- if (source.startsWith('@mui/')) {
37
- const parts = source.split('/');
38
- // @mui/material/styles/createTheme -> 4 partes (deep import)
39
- // @mui/material/styles -> 3 partes (OK)
40
- if (parts.length > 3) {
41
- context.report({
42
- node,
43
- messageId: 'deepImport',
44
- fix(fixer) {
45
- // Tenta converter para named import
46
- const specifier = node.specifiers[0];
47
- if (specifier && specifier.type === 'ImportDefaultSpecifier') {
48
- const importName = specifier.local.name;
49
- const newPath = parts.slice(0, 3).join('/'); // Remove último nível
50
- return fixer.replaceText(
51
- node,
52
- `import { ${importName} } from "${newPath}"`
53
- );
54
- }
55
- return null;
56
- },
57
- });
58
- }
39
+ if (source === '@mui/material/Unstable_Grid2') {
40
+ context.report({
41
+ node,
42
+ messageId: 'unstableGrid',
43
+ fix(fixer) {
44
+ return fixer.replaceText(node.source, '"@mui/material"');
45
+ },
46
+ });
59
47
  }
60
48
  },
61
49
  };
@@ -67,7 +55,7 @@ const muiV7Rules = {
67
55
  type: 'problem',
68
56
  docs: {
69
57
  description: 'Grid2 foi renomeado para Grid no MUI V7',
70
- category: 'Best Practices',
58
+ category: 'Breaking Changes',
71
59
  recommended: true,
72
60
  },
73
61
  messages: {
@@ -75,10 +63,10 @@ const muiV7Rules = {
75
63
  '🔧 Forma antiga (V6):\n' +
76
64
  ' import Grid2 from "@mui/material/Grid2"\n' +
77
65
  ' import { grid2Classes } from "@mui/material/Grid2"\n\n' +
78
- '✅ Forma nova (V7):\n' +
79
- ' import Grid from "@mui/material/Grid"\n' +
80
- ' import { gridClasses } from "@mui/material/Grid"\n\n' +
81
- '💡 Dica: O novo Grid é mais poderoso e responsivo!',
66
+ '✅ Recomendado:\n' +
67
+ ' import { Grid } from "@mui/material"\n' +
68
+ ' import { gridClasses } from "@mui/material"\n\n' +
69
+ '💡 O novo Grid é mais poderoso e usa a prop `size`!',
82
70
  },
83
71
  schema: [],
84
72
  fixable: 'code',
@@ -93,23 +81,7 @@ const muiV7Rules = {
93
81
  node,
94
82
  messageId: 'grid2Import',
95
83
  fix(fixer) {
96
- const fixes = [
97
- fixer.replaceText(node.source, '"@mui/material/Grid"')
98
- ];
99
-
100
- // Renomeia Grid2 -> Grid e grid2Classes -> gridClasses
101
- node.specifiers.forEach(spec => {
102
- if (spec.imported) {
103
- const name = spec.imported.name;
104
- if (name.includes('grid2')) {
105
- const newName = name.replace('grid2', 'grid');
106
- // Nota: Isso só funciona bem para casos simples
107
- // Para casos complexos, o usuário precisa fazer manual
108
- }
109
- }
110
- });
111
-
112
- return fixes;
84
+ return fixer.replaceText(node.source, '"@mui/material"');
113
85
  },
114
86
  });
115
87
  }
@@ -118,67 +90,20 @@ const muiV7Rules = {
118
90
  },
119
91
  },
120
92
 
121
- 'no-old-grid-import': {
122
- meta: {
123
- type: 'suggestion',
124
- docs: {
125
- description: 'Sugere migração do Grid antigo para o novo',
126
- category: 'Best Practices',
127
- recommended: false,
128
- },
129
- messages: {
130
- oldGrid: '💡 O Grid antigo agora é GridLegacy. Considere migrar para o novo Grid!\n\n' +
131
- '🔧 Se quiser manter o Grid antigo:\n' +
132
- ' import Grid from "@mui/material/GridLegacy"\n' +
133
- ' import { gridLegacyClasses } from "@mui/material/GridLegacy"\n\n' +
134
- '✅ Recomendado: Migrar para o novo Grid:\n' +
135
- ' import Grid from "@mui/material/Grid"\n\n' +
136
- '📚 O novo Grid usa `size` em vez de `xs/sm/md`:\n' +
137
- ' <Grid size={{ xs: 12, md: 6 }}>Conteúdo</Grid>',
138
- },
139
- schema: [],
140
- },
141
- create(context) {
142
- const sourceCode = context.getSourceCode();
143
-
144
- return {
145
- ImportDeclaration(node) {
146
- const source = node.source.value;
147
-
148
- // Detecta import Grid from '@mui/material/Grid'
149
- if (source === '@mui/material/Grid') {
150
- const defaultImport = node.specifiers.find(
151
- spec => spec.type === 'ImportDefaultSpecifier'
152
- );
153
-
154
- if (defaultImport) {
155
- // Verifica se está usando props antigas (xs, sm, md) no código
156
- // Isso é apenas um aviso suave, não um erro
157
- context.report({
158
- node,
159
- messageId: 'oldGrid',
160
- });
161
- }
162
- }
163
- },
164
- };
165
- },
166
- },
167
-
168
93
  'no-lab-imports': {
169
94
  meta: {
170
95
  type: 'problem',
171
96
  docs: {
172
97
  description: 'Componentes movidos de @mui/lab para @mui/material',
173
- category: 'Best Practices',
98
+ category: 'Breaking Changes',
174
99
  recommended: true,
175
100
  },
176
101
  messages: {
177
102
  labImport: '✨ Este componente foi movido para @mui/material no V7!\n\n' +
178
103
  '🔧 Forma antiga (V6):\n' +
179
- ' import {{ component }} from "@mui/lab/{{ component }}"\n\n' +
180
- '✅ Forma nova (V7):\n' +
181
- ' import {{ component }} from "@mui/material/{{ component }}"\n\n' +
104
+ ' import {{ component }} from "@mui/lab"\n\n' +
105
+ '✅ Recomendado:\n' +
106
+ ' import { {{ component }} } from "@mui/material"\n\n' +
182
107
  '📦 Componentes movidos: Alert, Autocomplete, Pagination, Rating,\n' +
183
108
  ' Skeleton, SpeedDial, ToggleButton, AvatarGroup, e mais!',
184
109
  },
@@ -216,10 +141,7 @@ const muiV7Rules = {
216
141
  messageId: 'labImport',
217
142
  data: { component: componentName },
218
143
  fix(fixer) {
219
- return fixer.replaceText(
220
- node.source,
221
- `"@mui/material/${componentName}"`
222
- );
144
+ return fixer.replaceText(node.source, '"@mui/material"');
223
145
  },
224
146
  });
225
147
  }
@@ -235,7 +157,7 @@ const muiV7Rules = {
235
157
  type: 'problem',
236
158
  docs: {
237
159
  description: 'Grid não usa mais a prop `item`, agora usa `size`',
238
- category: 'Best Practices',
160
+ category: 'Breaking Changes',
239
161
  recommended: true,
240
162
  },
241
163
  messages: {
@@ -245,7 +167,7 @@ const muiV7Rules = {
245
167
  '✅ Forma nova (V7):\n' +
246
168
  ' <Grid size={{ xs: 12, sm: 6, md: 4 }}>\n\n' +
247
169
  '💡 A nova sintaxe é mais limpa e poderosa!\n' +
248
- ' Você pode usar offset, push, pull e mais.',
170
+ ' Você pode usar: size, offset, spacing responsivo e mais.',
249
171
  },
250
172
  schema: [],
251
173
  },
@@ -279,7 +201,7 @@ const muiV7Rules = {
279
201
  type: 'problem',
280
202
  docs: {
281
203
  description: 'Detecta props depreciadas no MUI V7',
282
- category: 'Best Practices',
204
+ category: 'Breaking Changes',
283
205
  recommended: true,
284
206
  },
285
207
  messages: {
@@ -379,6 +301,55 @@ const muiV7Rules = {
379
301
  create(context) {
380
302
  const sourceCode = context.getSourceCode();
381
303
 
304
+ /**
305
+ * Verifica se o node está dentro de um ternário que checa theme.vars
306
+ * Exemplo: theme.vars ? `${theme.vars.palette.primary.main}` : `${theme.palette.primary.main}`
307
+ */
308
+ function isInsideThemeVarsConditional(node) {
309
+ let current = node;
310
+
311
+ // Sobe até 10 níveis na árvore AST procurando por ConditionalExpression
312
+ for (let i = 0; i < 10; i++) {
313
+ if (!current.parent) break;
314
+ current = current.parent;
315
+
316
+ // Se encontrar um ternário (ConditionalExpression)
317
+ if (current.type === 'ConditionalExpression') {
318
+ // Verifica se o teste é "theme.vars"
319
+ const test = current.test;
320
+ if (
321
+ test &&
322
+ test.type === 'MemberExpression' &&
323
+ test.object &&
324
+ test.object.name === 'theme' &&
325
+ test.property &&
326
+ test.property.name === 'vars'
327
+ ) {
328
+ // Se estamos no consequent (parte do `?`), não reportar
329
+ // Apenas reportar se estamos no alternate (parte do `:`)
330
+ return true; // Ignora warnings quando dentro de ternário com theme.vars
331
+ }
332
+ }
333
+ }
334
+
335
+ return false;
336
+ }
337
+
338
+ /**
339
+ * Verifica se está dentro de uma função sx que usa theme.vars!
340
+ * Exemplo: sx={(theme) => ({ background: `${theme.vars!.palette...}` })}
341
+ */
342
+ function isUsingNonNullAssertion(node) {
343
+ const sourceText = sourceCode.getText(node.parent);
344
+
345
+ // Procura por theme.vars! (non-null assertion)
346
+ if (sourceText.includes('theme.vars!')) {
347
+ return true;
348
+ }
349
+
350
+ return false;
351
+ }
352
+
382
353
  return {
383
354
  MemberExpression(node) {
384
355
  // Detecta theme.palette.* (sem .vars)
@@ -393,6 +364,16 @@ const muiV7Rules = {
393
364
  // Verifica se não é theme.vars.palette
394
365
  const parent = node.object.object;
395
366
  if (parent.type === 'Identifier' && parent.name === 'theme') {
367
+ // Ignora se estiver dentro de um ternário que checa theme.vars
368
+ if (isInsideThemeVarsConditional(node)) {
369
+ return;
370
+ }
371
+
372
+ // Ignora se já está usando theme.vars! (non-null assertion)
373
+ if (isUsingNonNullAssertion(node)) {
374
+ return;
375
+ }
376
+
396
377
  context.report({
397
378
  node,
398
379
  messageId: 'useThemeVars',
@@ -412,24 +393,26 @@ const plugin = {
412
393
  recommended: {
413
394
  plugins: ['mui-v7'],
414
395
  rules: {
415
- 'mui-v7/no-deep-imports': 'error',
396
+ // Breaking changes - ERRORS (código quebra)
397
+ 'mui-v7/no-unstable-grid': 'error',
416
398
  'mui-v7/no-grid2-import': 'error',
417
- 'mui-v7/no-lab-imports': 'error',
418
399
  'mui-v7/no-grid-item-prop': 'error',
400
+ 'mui-v7/no-lab-imports': 'error',
419
401
  'mui-v7/no-deprecated-props': 'error',
420
- 'mui-v7/no-old-grid-import': 'warn',
402
+ // Best practices - WARNINGS (sugestões)
421
403
  'mui-v7/prefer-theme-vars': 'warn',
422
404
  },
423
405
  },
424
406
  strict: {
425
407
  plugins: ['mui-v7'],
426
408
  rules: {
427
- 'mui-v7/no-deep-imports': 'error',
409
+ // Breaking changes - ERRORS
410
+ 'mui-v7/no-unstable-grid': 'error',
428
411
  'mui-v7/no-grid2-import': 'error',
429
- 'mui-v7/no-lab-imports': 'error',
430
412
  'mui-v7/no-grid-item-prop': 'error',
413
+ 'mui-v7/no-lab-imports': 'error',
431
414
  'mui-v7/no-deprecated-props': 'error',
432
- 'mui-v7/no-old-grid-import': 'error',
415
+ // Best practices - ERRORS também no strict
433
416
  'mui-v7/prefer-theme-vars': 'error',
434
417
  },
435
418
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-mui-v7",
3
- "version": "1.0.0",
4
- "description": "ESLint plugin for Material-UI v7 with educational error messages",
3
+ "version": "1.2.0",
4
+ "description": "ESLint plugin focused on Material-UI V6 to V7 breaking changes with educational error messages",
5
5
  "keywords": [
6
6
  "eslint",
7
7
  "eslintplugin",