eslint-plugin-primer-react 6.0.2 → 6.1.0-rc.b2a68e1

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.
@@ -0,0 +1,368 @@
1
+ 'use strict'
2
+
3
+ const url = require('../url')
4
+
5
+ const wildcardImports = new Map([
6
+ // Components
7
+ [
8
+ '@primer/react/lib-esm/Button/ButtonBase',
9
+ [
10
+ {
11
+ type: 'type',
12
+ name: 'ButtonBaseProps',
13
+ from: '@primer/react',
14
+ },
15
+ {
16
+ name: 'ButtonBase',
17
+ from: '@primer/react',
18
+ },
19
+ ],
20
+ ],
21
+ [
22
+ '@primer/react/lib-esm/Button/types',
23
+ [
24
+ {
25
+ type: 'type',
26
+ name: 'ButtonBaseProps',
27
+ from: '@primer/react',
28
+ },
29
+ ],
30
+ ],
31
+ [
32
+ '@primer/react/lib-esm/Dialog/Dialog',
33
+ [
34
+ {
35
+ name: 'Dialog',
36
+ from: '@primer/react/experimental',
37
+ },
38
+ ],
39
+ ],
40
+ [
41
+ '@primer/react/lib-esm/SelectPanel/SelectPanel',
42
+ [
43
+ {
44
+ name: 'SelectPanel',
45
+ from: '@primer/react/experimental',
46
+ },
47
+ {
48
+ type: 'type',
49
+ name: 'SelectPanelProps',
50
+ from: '@primer/react/experimental',
51
+ },
52
+ ],
53
+ ],
54
+ [
55
+ '@primer/react/lib-esm/Label/Label',
56
+ [
57
+ {
58
+ type: 'type',
59
+ name: 'LabelColorOptions',
60
+ from: '@primer/react',
61
+ },
62
+ ],
63
+ ],
64
+ [
65
+ '@primer/react/lib-esm/_VisuallyHidden',
66
+ [
67
+ {
68
+ name: 'default',
69
+ from: '@primer/react',
70
+ as: 'VisuallyHidden',
71
+ },
72
+ ],
73
+ ],
74
+ [
75
+ '@primer/react/lib-esm/Token/IssueLabelToken',
76
+ [
77
+ {
78
+ type: 'type',
79
+ name: 'IssueLabelTokenProps',
80
+ from: '@primer/react',
81
+ },
82
+ ],
83
+ ],
84
+ [
85
+ '@primer/react/lib-esm/Token/TokenBase',
86
+ [
87
+ {
88
+ type: 'type',
89
+ name: 'TokenSizeKeys',
90
+ from: '@primer/react',
91
+ },
92
+ ],
93
+ ],
94
+ [
95
+ '@primer/react/lib-esm/deprecated/ActionList',
96
+ [
97
+ {
98
+ type: 'type',
99
+ name: 'ItemProps',
100
+ from: '@primer/react/deprecated',
101
+ as: 'ActionListItemProps',
102
+ },
103
+ ],
104
+ ],
105
+ [
106
+ '@primer/react/lib-esm/deprecated/ActionList/List',
107
+ [
108
+ {
109
+ type: 'type',
110
+ name: 'GroupedListProps',
111
+ from: '@primer/react/deprecated',
112
+ as: 'ActionListGroupedListProps',
113
+ },
114
+ {
115
+ name: 'ItemInput',
116
+ from: '@primer/react/deprecated',
117
+ as: 'ActionListItemInput',
118
+ },
119
+ ],
120
+ ],
121
+ [
122
+ '@primer/react/lib-esm/deprecated/ActionList/Item',
123
+ [
124
+ {
125
+ type: 'type',
126
+ name: 'ItemProps',
127
+ from: '@primer/react/deprecated',
128
+ as: 'ActionListItemProps',
129
+ },
130
+ ],
131
+ ],
132
+
133
+ // Hooks
134
+ [
135
+ '@primer/react/lib-esm/useIsomorphicLayoutEffect',
136
+ [
137
+ {
138
+ name: 'default',
139
+ from: '@primer/react',
140
+ as: 'useIsomorphicLayoutEffect',
141
+ },
142
+ ],
143
+ ],
144
+ [
145
+ '@primer/react/lib-esm/hooks/useResizeObserver',
146
+ [
147
+ {
148
+ name: 'default',
149
+ from: '@primer/react',
150
+ as: 'useResizeObserver',
151
+ },
152
+ ],
153
+ ],
154
+ [
155
+ '@primer/react/lib-esm/hooks/useProvidedRefOrCreate',
156
+ [
157
+ {
158
+ name: 'default',
159
+ from: '@primer/react',
160
+ as: 'useProvidedRefOrCreate',
161
+ },
162
+ ],
163
+ ],
164
+ [
165
+ '@primer/react/lib-esm/hooks/useResponsiveValue',
166
+ [
167
+ {
168
+ name: 'default',
169
+ from: '@primer/react',
170
+ as: 'useResponsiveValue',
171
+ },
172
+ ],
173
+ ],
174
+
175
+ // Utilities
176
+ [
177
+ '@primer/react/lib-esm/sx',
178
+ [
179
+ {
180
+ type: 'type',
181
+ name: 'BetterSystemStyleObject',
182
+ from: '@primer/react',
183
+ },
184
+ {
185
+ type: 'type',
186
+ name: 'SxProp',
187
+ from: '@primer/react',
188
+ },
189
+ {
190
+ type: 'type',
191
+ name: 'BetterCssProperties',
192
+ from: '@primer/react',
193
+ },
194
+ ],
195
+ ],
196
+ [
197
+ '@primer/react/lib-esm/FeatureFlags/DefaultFeatureFlags',
198
+ [
199
+ {
200
+ name: 'DefaultFeatureFlags',
201
+ from: '@primer/react/experimental',
202
+ },
203
+ ],
204
+ ],
205
+ [
206
+ '@primer/react/lib-esm/theme',
207
+ [
208
+ {
209
+ name: 'default',
210
+ from: '@primer/react',
211
+ as: 'theme',
212
+ },
213
+ ],
214
+ ],
215
+ ])
216
+
217
+ /**
218
+ * @type {import('eslint').Rule.RuleModule}
219
+ */
220
+ module.exports = {
221
+ meta: {
222
+ type: 'problem',
223
+ docs: {
224
+ description: 'Wildcard imports are discouraged. Import from a main entrypoint instead',
225
+ recommended: true,
226
+ url: url(module),
227
+ },
228
+ fixable: true,
229
+ schema: [],
230
+ messages: {
231
+ unknownWildcardImport:
232
+ 'Wildcard imports from @primer/react are not allowed. Import from @primer/react, @primer/react/experimental, or @primer/react/deprecated instead',
233
+ knownWildcardImport:
234
+ 'Wildcard import {{ specifier }} from {{ wildcardEntrypoint }} are not allowed. Import from @primer/react, @primer/react/experimental, or @primer/react/deprecated instead',
235
+ wildcardMigration:
236
+ 'Wildcard imports from {{ wildcardEntrypoint }} are not allowed. Import from @primer/react, @primer/react/experimental, or @primer/react/deprecated instead',
237
+ },
238
+ },
239
+ create(context) {
240
+ return {
241
+ ImportDeclaration(node) {
242
+ if (!node.source.value.startsWith('@primer/react/lib-esm')) {
243
+ return
244
+ }
245
+
246
+ const wildcardImportMigrations = wildcardImports.get(node.source.value)
247
+ if (!wildcardImportMigrations) {
248
+ context.report({
249
+ node,
250
+ messageId: 'unknownWildcardImport',
251
+ })
252
+ return
253
+ }
254
+
255
+ /**
256
+ * Maps entrypoint to array of changes. This tuple contains the new
257
+ * imported name from the entrypoint along with the existing local name
258
+ * @type {Map<string, Array<[string, string]>>}
259
+ */
260
+ const changes = new Map()
261
+
262
+ for (const specifier of node.specifiers) {
263
+ const migration = wildcardImportMigrations.find(migration => {
264
+ if (specifier.type === 'ImportDefaultSpecifier') {
265
+ return migration.name === 'default'
266
+ }
267
+ return specifier.imported.name === migration.name
268
+ })
269
+
270
+ // If we do not have a migration, we should report an error even if we
271
+ // cannot autofix it
272
+ if (!migration) {
273
+ context.report({
274
+ node,
275
+ messageId: 'unknownWildcardImport',
276
+ })
277
+ return
278
+ }
279
+
280
+ if (!changes.has(migration.from)) {
281
+ changes.set(migration.from, [])
282
+ }
283
+
284
+ if (migration.as) {
285
+ changes.get(migration.from).push([migration.as, migration.as, migration.type])
286
+ } else {
287
+ changes.get(migration.from).push([migration.name, specifier.local.name, migration.type])
288
+ }
289
+ }
290
+
291
+ if (changes.length === 0) {
292
+ return
293
+ }
294
+
295
+ context.report({
296
+ node,
297
+ messageId: 'wildcardMigration',
298
+ data: {
299
+ wildcardEntrypoint: node.source.value,
300
+ },
301
+ *fix(fixer) {
302
+ for (const [entrypoint, importSpecifiers] of changes) {
303
+ const typeSpecifiers = importSpecifiers.filter(([, , type]) => {
304
+ return type === 'type'
305
+ })
306
+
307
+ // If all imports are type imports, emit emit as `import type {specifier} from '...'`
308
+ if (typeSpecifiers.length === importSpecifiers.length) {
309
+ const namedSpecifiers = typeSpecifiers.filter(([imported]) => {
310
+ return imported !== 'default'
311
+ })
312
+ const defaultSpecifier = typeSpecifiers.find(([imported]) => {
313
+ return imported === 'default'
314
+ })
315
+
316
+ if (namedSpecifiers.length > 0 && !defaultSpecifier) {
317
+ const specifiers = namedSpecifiers.map(([imported, local]) => {
318
+ if (imported !== local) {
319
+ return `${imported} as ${local}`
320
+ }
321
+ return imported
322
+ })
323
+ yield fixer.replaceText(node, `import type {${specifiers.join(', ')}} from '${entrypoint}'`)
324
+ } else if (namedSpecifiers.length > 0 && defaultSpecifier) {
325
+ yield fixer.replaceText(
326
+ node,
327
+ `import type ${defaultSpecifier[1]}, ${specifiers.join(', ')} from '${entrypoint}'`,
328
+ )
329
+ } else if (defaultSpecifier && namedSpecifiers.length === 0) {
330
+ yield fixer.replaceText(node, `import type ${defaultSpecifier[1]} from '${entrypoint}'`)
331
+ }
332
+
333
+ return
334
+ }
335
+
336
+ // Otherwise, we have a mix of type and value imports to emit
337
+ const valueSpecifiers = importSpecifiers.filter(([, , type]) => {
338
+ return type !== 'type'
339
+ })
340
+
341
+ if (valueSpecifiers.length === 0) {
342
+ return
343
+ }
344
+
345
+ const specifiers = valueSpecifiers.map(([imported, local]) => {
346
+ if (imported !== local) {
347
+ return `${imported} as ${local}`
348
+ }
349
+ return imported
350
+ })
351
+ yield fixer.replaceText(node, `import {${specifiers.join(', ')}} from '${entrypoint}'`)
352
+
353
+ if (typeSpecifiers.length > 0) {
354
+ const specifiers = valueSpecifiers.map(([imported, local]) => {
355
+ if (imported !== local) {
356
+ return `${imported} as ${local}`
357
+ }
358
+ return imported
359
+ })
360
+ yield fixer.insertTextAfter(node, `import type {${specifiers.join(', ')}} from '${entrypoint}'`)
361
+ }
362
+ }
363
+ },
364
+ })
365
+ },
366
+ }
367
+ },
368
+ }