Repository: styled-components/babel-plugin-styled-components
Pull request #11 (2016-11-28 11:23:35)

Import declaration detection enhancements
Make importedVariableName value local to processed file.
Look for imports in separate traverse to catch hoisted imports.
Support `import {default as s} from ‘styled-components’`


Patch:
diff --git a/src/index.js b/src/index.js
index 93902b7..93521d8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -9,97 +9,102 @@ const buildStyledCall = template(`STYLED({
   identifier: IDENTIFIER
 })`)
 
-// Default imported variable name to "styled", adjust based on import below
-let importedVariableName = 'styled'
-
-const isStyled = (tag) => (tag.object && tag.object.name === importedVariableName) || (tag.callee && tag.callee.name === importedVariableName)
-
 let id = 0
 
 export default function({ types: t }) {
   return {
     visitor: {
-			ImportDeclaration: {
-				enter(path) {
-					// Is the styled-components import!
-					if (path.node.source.value === 'styled-components') {
-						// If the default is imported it's at defaultImport[0], otherwise defaultImport is empty
-						const defaultImport = path.node.specifiers.filter((specifier) => t.isImportDefaultSpecifier(specifier))
-						if (defaultImport.length > 0) {
-							// Save the imported name
-							importedVariableName = defaultImport[0].local.name
-						}
-					}
-				}
-			},
-      TaggedTemplateExpression: {
-        enter(path, { opts, file }) {
-          const addDisplayName = (opts.displayName === undefined || opts.displayName === null) ? true : opts.displayName
-          const addIdentifier = (opts.ssr === undefined || opts.ssr === null) ? true : opts.ssr
-          const useFileName = (opts.fileName === undefined || opts.fileName === null) ? true : opts.fileName
-
-          const tag = path.node.tag
-
-          if (!isStyled(tag)) return
-
-          let namedNode
-
-          path.find((path) => {
-            // const X = styled
-            if (path.isAssignmentExpression()) {
-              namedNode = path.node.left
-            // const X = { Y: styled }
-            } else if (path.isObjectProperty()) {
-              namedNode = path.node.key
-            // let X; X = styled
-            } else if (path.isVariableDeclarator()) {
-              namedNode = path.node.id
-            } else if (path.isStatement()) {
-              // we've hit a statement, we should stop crawling up
-              return true
+      Program(path, state) {
+        // Default imported variable name to "styled", adjust based on import below
+        let importedVariableName = 'styled'
+
+        const isStyled = (tag) => (tag.object && tag.object.name === importedVariableName) || (tag.callee && tag.callee.name === importedVariableName)
+
+        path.traverse({
+          ImportDeclaration(path) {
+            // Is the styled-components import!
+            if (path.node.source.value === 'styled-components') {
+              // If the default is imported it's at defaultImport[0], otherwise defaultImport is empty
+              const defaultImport = path.get('specifiers').find((specifier) => {
+                return specifier.isImportDefaultSpecifier() || specifier.isImportSpecifier() && specifier.node.imported.name === 'default'
+              })
+              if (defaultImport) {
+                // Save the imported name
+                importedVariableName = defaultImport.node.local.name
+              }
             }
-
-            // we've got an displayName (if we need it) no need to continue
-            if (namedNode) return true
-          })
-
-          // foo.bar -> bar
-          if (t.isMemberExpression(namedNode)) {
-            namedNode = namedNode.property
           }
+        })
+
+        path.traverse({
+          TaggedTemplateExpression(path, { opts, file }) {
+            const addDisplayName = (opts.displayName === undefined || opts.displayName === null) ? true : opts.displayName
+            const addIdentifier = (opts.ssr === undefined || opts.ssr === null) ? true : opts.ssr
+            const useFileName = (opts.fileName === undefined || opts.fileName === null) ? true : opts.fileName
+
+            const tag = path.node.tag
+
+            if (!isStyled(tag)) return
+
+            let namedNode
+
+            path.find((path) => {
+              // const X = styled
+              if (path.isAssignmentExpression()) {
+                namedNode = path.node.left
+              // const X = { Y: styled }
+              } else if (path.isObjectProperty()) {
+                namedNode = path.node.key
+              // let X; X = styled
+              } else if (path.isVariableDeclarator()) {
+                namedNode = path.node.id
+              } else if (path.isStatement()) {
+                // we've hit a statement, we should stop crawling up
+                return true
+              }
+
+              // we've got an displayName (if we need it) no need to continue
+              if (namedNode) return true
+            })
+
+            // foo.bar -> bar
+            if (t.isMemberExpression(namedNode)) {
+              namedNode = namedNode.property
+            }
 
-          // Get target
-          const target = getTarget(path.node.tag)
+            // Get target
+            const target = getTarget(path.node.tag)
 
-          // identifiers are the only thing we can reliably get a name from
-          const componentName = t.isIdentifier(namedNode) ? namedNode.name : undefined
+            // identifiers are the only thing we can reliably get a name from
+            const componentName = t.isIdentifier(namedNode) ? namedNode.name : undefined
 
-          let displayName
+            let displayName
 
-          if (!useFileName) {
-            displayName = componentName
-          } else {
-            let blockName = file.opts.basename
-            if (blockName === 'index') {
-              blockName = path.basename(path.dirname(file.opts.filename))
+            if (!useFileName) {
+              displayName = componentName
+            } else {
+              let blockName = file.opts.basename
+              if (blockName === 'index') {
+                blockName = path.basename(path.dirname(file.opts.filename))
+              }
+              displayName = componentName ? `${blockName}__${namedNode.name}` : blockName
             }
-            displayName = componentName ? `${blockName}__${namedNode.name}` : blockName
-          }
 
-          id++
-          // Prefix the identifier with a character if no displayName exists because CSS classes cannot start with a number
-          const identifier = `${displayName || 's'}-${hash(`${id}${displayName}`)}`
-          // Put together the final code again
-          const call = buildStyledCall({
-            STYLED: t.identifier(importedVariableName),
-            TARGET: target,
-            DISPLAYNAME: (addDisplayName && displayName && t.stringLiteral(displayName)) || t.identifier('undefined'),
-            IDENTIFIER: (addIdentifier && identifier && t.stringLiteral(identifier)) || t.identifier('undefined')
-          })
-          // Put together the styled call with the template literal
-          // to get the finished styled({ })`` form! 🎉
-        	path.node.tag = call.expression
-        }
+            id++
+            // Prefix the identifier with a character if no displayName exists because CSS classes cannot start with a number
+            const identifier = `${displayName || 's'}-${hash(`${id}${displayName}`)}`
+            // Put together the final code again
+            const call = buildStyledCall({
+              STYLED: t.identifier(importedVariableName),
+              TARGET: target,
+              DISPLAYNAME: (addDisplayName && displayName && t.stringLiteral(displayName)) || t.identifier('undefined'),
+              IDENTIFIER: (addIdentifier && identifier && t.stringLiteral(identifier)) || t.identifier('undefined')
+            })
+            // Put together the styled call with the template literal
+            // to get the finished styled({ })`` form! 🎉
+            path.node.tag = call.expression
+          }
+        }, state)
       }
     }
   }


Test patch:
diff --git a/test/fixtures/works-with-hoisted-default-as-import/.babelrc b/test/fixtures/works-with-hoisted-default-as-import/.babelrc
new file mode 100644
index 0000000..fb02be5
--- /dev/null
+++ b/test/fixtures/works-with-hoisted-default-as-import/.babelrc
@@ -0,0 +1,5 @@
+{
+  "plugins": [
+    "../../../src"
+  ]
+}
diff --git a/test/fixtures/works-with-hoisted-default-as-import/after.js b/test/fixtures/works-with-hoisted-default-as-import/after.js
new file mode 100644
index 0000000..ab6562b
--- /dev/null
+++ b/test/fixtures/works-with-hoisted-default-as-import/after.js
@@ -0,0 +1,6 @@
+const Test = s({
+  target: 'div',
+  displayName: 'before__Test',
+  identifier: 'before__Test-1ex1ta1'
+})` width: 100%;`;
+import { default as s, css } from 'styled-components';
diff --git a/test/fixtures/works-with-hoisted-default-as-import/before.js b/test/fixtures/works-with-hoisted-default-as-import/before.js
new file mode 100644
index 0000000..e9c32b7
--- /dev/null
+++ b/test/fixtures/works-with-hoisted-default-as-import/before.js
@@ -0,0 +1,2 @@
+const Test = s.div` width: 100%;`;
+import { default as s, css } from 'styled-components';