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.
- package/.eslintignore +2 -0
- package/CHANGELOG.md +6 -0
- package/docs/rules/no-wildcard-imports.md +25 -0
- package/package-lock.json +14002 -0
- package/package.json +3 -3
- package/src/index.js +1 -0
- package/src/rules/__tests__/no-wildcard-imports.test.js +363 -0
- package/src/rules/no-wildcard-imports.js +368 -0
|
@@ -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
|
+
}
|