@@ -23,13 +23,17 @@ const defaultStyledComponents = [
2323 'Truncate' ,
2424 'Octicon' ,
2525 'Dialog' ,
26+ 'ThemeProvider' ,
27+ 'BaseStyles' ,
2628]
2729
30+ const componentsToAlwaysImportFromStyledReact = new Set ( [ 'ThemeProvider' , 'BaseStyles' ] )
31+
2832// Default types that should be imported from @primer /styled-react
2933const defaultStyledTypes = [ 'BoxProps' , 'SxProp' , 'BetterSystemStyleObject' ]
3034
3135// Default utilities that should be imported from @primer /styled-react
32- const defaultStyledUtilities = [ 'sx' ]
36+ const defaultStyledUtilities = [ 'sx' , 'useTheme' ]
3337
3438/**
3539 * @type {import('eslint').Rule.RuleModule }
@@ -246,7 +250,11 @@ module.exports = {
246250 // Report errors for components used WITHOUT sx prop that are imported from @primer /styled-react
247251 for ( const componentName of allUsedComponents ) {
248252 // If component is used but NOT with sx prop, and it's imported from styled-react
249- if ( ! componentsWithSx . has ( componentName ) && styledReactImports . has ( componentName ) ) {
253+ if (
254+ ! componentsWithSx . has ( componentName ) &&
255+ styledReactImports . has ( componentName ) &&
256+ ! componentsToAlwaysImportFromStyledReact . has ( componentName )
257+ ) {
250258 const importInfo = styledReactImports . get ( componentName )
251259 context . report ( {
252260 node : importInfo . specifier ,
@@ -337,53 +345,67 @@ module.exports = {
337345 }
338346 }
339347
340- // Also report for types and utilities that should always be from styled-react
348+ // Also report for types, utilities and components that should always be from styled-react
341349 for ( const [ importName , importInfo ] of primerReactImports ) {
342- if ( ( styledTypes . has ( importName ) || styledUtilities . has ( importName ) ) && ! styledReactImports . has ( importName ) ) {
350+ if (
351+ ( styledTypes . has ( importName ) ||
352+ styledUtilities . has ( importName ) ||
353+ componentsToAlwaysImportFromStyledReact . has ( importName ) ) &&
354+ ! styledReactImports . has ( importName )
355+ ) {
343356 context . report ( {
344357 node : importInfo . specifier ,
345358 messageId : 'moveToStyledReact' ,
346359 data : { importName} ,
347360 fix ( fixer ) {
348361 const { node : importNode , specifier, importSource} = importInfo
349- const otherSpecifiers = importNode . specifiers . filter ( s => s !== specifier )
350362
351- // Convert @primer /react path to @primer /styled-react path
352- const styledReactPath = importSource . replace ( '@primer/react' , '@primer/styled-react' )
363+ const fixes = [ ]
353364
354- // If this is the only import, replace the whole import
355- if ( otherSpecifiers . length === 0 ) {
356- const prefix = styledTypes . has ( importName ) ? 'type ' : ''
357- return fixer . replaceText ( importNode , `import { ${ prefix } ${ importName } } from '${ styledReactPath } '` )
358- }
365+ // we consolidate all the fixes for the import in the first specifier
366+ const isFirst = importNode . specifiers [ 0 ] === specifier
367+ if ( ! isFirst ) return null
359368
360- // Otherwise, remove from current import and add new import
361- const fixes = [ ]
369+ const specifiersToMove = importNode . specifiers . filter ( specifier => {
370+ const name = specifier . imported . name
371+ return (
372+ styledUtilities . has ( name ) ||
373+ styledTypes . has ( name ) ||
374+ componentsToAlwaysImportFromStyledReact . has ( name )
375+ )
376+ } )
377+
378+ const remainingSpecifiers = importNode . specifiers . filter ( specifier => {
379+ return ! specifiersToMove . includes ( specifier )
380+ } )
381+
382+ // Convert @primer /react path to @primer /styled-react path
383+ const styledReactPath = importSource . replace ( '@primer/react' , '@primer/styled-react' )
362384
363- // Remove the specifier from current import
364- if ( importNode . specifiers . length === 1 ) {
385+ if ( remainingSpecifiers . length === 0 ) {
386+ // if there are no remaining specifiers, we can remove the whole import
365387 fixes . push ( fixer . remove ( importNode ) )
366388 } else {
367- const isFirst = importNode . specifiers [ 0 ] === specifier
368- const isLast = importNode . specifiers [ importNode . specifiers . length - 1 ] === specifier
369-
370- if ( isFirst ) {
371- const nextSpecifier = importNode . specifiers [ 1 ]
372- fixes . push ( fixer . removeRange ( [ specifier . range [ 0 ] , nextSpecifier . range [ 0 ] ] ) )
373- } else if ( isLast ) {
374- const prevSpecifier = importNode . specifiers [ importNode . specifiers . length - 2 ]
375- fixes . push ( fixer . removeRange ( [ prevSpecifier . range [ 1 ] , specifier . range [ 1 ] ] ) )
376- } else {
377- const nextSpecifier = importNode . specifiers [ importNode . specifiers . indexOf ( specifier ) + 1 ]
378- fixes . push ( fixer . removeRange ( [ specifier . range [ 0 ] , nextSpecifier . range [ 0 ] ] ) )
379- }
389+ const remainingNames = remainingSpecifiers . map ( spec =>
390+ spec . importKind === 'type' ? `type ${ spec . imported . name } ` : spec . imported . name ,
391+ )
392+ fixes . push (
393+ fixer . replaceText ( importNode , `import { ${ remainingNames . join ( ', ' ) } } from '${ importSource } '` ) ,
394+ )
380395 }
381396
382- // Add new import
383- const prefix = styledTypes . has ( importName ) ? 'type ' : ''
384- fixes . push (
385- fixer . insertTextAfter ( importNode , `\nimport { ${ prefix } ${ importName } } from '${ styledReactPath } '` ) ,
386- )
397+ if ( specifiersToMove . length > 0 ) {
398+ const movedComponents = specifiersToMove . map ( spec =>
399+ spec . importKind === 'type' ? `type ${ spec . imported . name } ` : spec . imported . name ,
400+ )
401+ const onNewLine = remainingSpecifiers . length > 0
402+ fixes . push (
403+ fixer . insertTextAfter (
404+ importNode ,
405+ `${ onNewLine ? '\n' : '' } import { ${ movedComponents . join ( ', ' ) } } from '${ styledReactPath } '` ,
406+ ) ,
407+ )
408+ }
387409
388410 return fixes
389411 } ,
0 commit comments