Repository: smooth-code/svgr
Pull request #227 (2018-11-08 14:09:28)

Various fixes
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. The two fields below are mandatory. -->


<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->


<!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. -->


Patch:
diff --git a/package.json b/package.json
index 3eeabec..7848570 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
   "devDependencies": {
     "@babel/cli": "^7.1.2",
     "@babel/core": "^7.1.2",
+    "@babel/generator": "^7.1.5",
     "@babel/node": "^7.0.0",
     "@babel/plugin-proposal-class-properties": "^7.1.0",
     "@babel/preset-env": "^7.1.0",
@@ -32,6 +33,9 @@
     "eslint-plugin-import": "^2.14.0",
     "jest": "^23.6.0",
     "lerna": "^3.4.3",
-    "react": "^16.6.0"
+    "react": "^16.6.0",
+    "rehype-parse": "^5.0.0",
+    "unified": "^7.0.1",
+    "vfile": "^3.0.1"
   }
 }
diff --git a/packages/babel-plugin-transform-svg-component/src/index.js b/packages/babel-plugin-transform-svg-component/src/index.js
index 8098c87..e88e7d5 100644
--- a/packages/babel-plugin-transform-svg-component/src/index.js
+++ b/packages/babel-plugin-transform-svg-component/src/index.js
@@ -16,13 +16,18 @@ const plugin = (api, opts) => ({
     Program(path) {
       const { types: t } = api
       const template = opts.template || defaultTemplate
-      path.node.body = template(api, opts, {
+      const body = template(api, opts, {
         componentName: t.identifier(opts.state.componentName),
         props: getProps(api, opts),
         imports: getImport(api, opts),
         exports: getExport(api, opts),
         jsx: path.node.body[0].expression,
       })
+      if (Array.isArray(body)) {
+        path.node.body = body
+      } else {
+        path.node.body = [body]
+      }
       path.replaceWith(path.node)
     },
   },
diff --git a/packages/hast-util-to-babel-ast/src/getAttributes.js b/packages/hast-util-to-babel-ast/src/getAttributes.js
index 37dcc2d..c21a0ee 100644
--- a/packages/hast-util-to-babel-ast/src/getAttributes.js
+++ b/packages/hast-util-to-babel-ast/src/getAttributes.js
@@ -1,9 +1,13 @@
 import * as t from '@babel/types'
-import { isNumeric } from './util'
+import { isNumeric, kebabCase, replaceLineBreaks } from './util'
 import stringToObjectStyle from './stringToObjectStyle'
 import { ATTRIBUTE_MAPPING, ELEMENT_ATTRIBUTE_MAPPING } from './mappings'
 
 function getKey(key, value, node) {
+  const kebabKey = kebabCase(key)
+  if (kebabKey.startsWith('aria-') || kebabKey.startsWith('data-')) {
+    return t.jsxIdentifier(kebabKey)
+  }
   const lowerCaseKey = key.toLowerCase()
   const mappedElementAttribute =
     ELEMENT_ATTRIBUTE_MAPPING[node.name] &&
@@ -15,7 +19,7 @@ function getKey(key, value, node) {
 function getValue(key, value) {
   // Handle className
   if (Array.isArray(value)) {
-    return t.stringLiteral(value.join(' '))
+    return t.stringLiteral(replaceLineBreaks(value.join(' ')))
   }
 
   if (key === 'style') {
@@ -26,7 +30,7 @@ function getValue(key, value) {
     return t.jsxExpressionContainer(t.numericLiteral(Number(value)))
   }
 
-  return t.stringLiteral(value)
+  return t.stringLiteral(replaceLineBreaks(value))
 }
 
 const getAttributes = node => {
diff --git a/packages/hast-util-to-babel-ast/src/util.js b/packages/hast-util-to-babel-ast/src/util.js
index 3a021fc..daa106d 100644
--- a/packages/hast-util-to-babel-ast/src/util.js
+++ b/packages/hast-util-to-babel-ast/src/util.js
@@ -31,3 +31,15 @@ export function trimEnd(haystack, needle) {
     ? haystack.slice(0, -needle.length)
     : haystack
 }
+
+const KEBAB_REGEX = /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g
+
+export function kebabCase(str) {
+  return str.replace(KEBAB_REGEX, match => `-${match.toLowerCase()}`)
+}
+
+const LINE_BREAKS_REGEXP = /[\r\n\u0085\u2028\u2029]+/g
+
+export function replaceLineBreaks(str) {
+  return str.replace(LINE_BREAKS_REGEXP, ' ')
+}
diff --git a/yarn.lock b/yarn.lock
index c2b6213..3b0bb12 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -57,6 +57,17 @@
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
+"@babel/generator@^7.1.5":
+  version "7.1.5"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.5.tgz#615f064d13d95f8f9157c7261f68eddf32ec15b3"
+  integrity sha512-IO31r62xfMI+wBJVmgx0JR9ZOHty8HkoYpQAjRWUGG9vykBTlGHdArZ8zoFtpUu2gs17K7qTl/TtPpiSi6t+MA==
+  dependencies:
+    "@babel/types" "^7.1.5"
+    jsesc "^2.5.1"
+    lodash "^4.17.10"
+    source-map "^0.5.0"
+    trim-right "^1.0.1"
+
 "@babel/helper-annotate-as-pure@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
@@ -719,6 +730,15 @@
     lodash "^4.17.10"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.1.5":
+  version "7.1.5"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.5.tgz#12fe64e91a431234b7017b4227a78cc0eec4e081"
+  integrity sha512-sJeqa/d9eM/bax8Ivg+fXF7FpN3E/ZmTrWbkk6r+g7biVYfALMnLin4dKijsaqEhpd2xvOGfQTkQkD31YCVV4A==
+  dependencies:
+    esutils "^2.0.2"
+    lodash "^4.17.10"
+    to-fast-properties "^2.0.0"
+
 "@lerna/add@^3.4.1":
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.4.1.tgz#d41068317e30f530df48220d256b5e79690b1877"


Test patch:
diff --git a/packages/babel-plugin-transform-svg-component/src/index.test.js b/packages/babel-plugin-transform-svg-component/src/index.test.js
index f64ed3e..4e79564 100644
--- a/packages/babel-plugin-transform-svg-component/src/index.test.js
+++ b/packages/babel-plugin-transform-svg-component/src/index.test.js
@@ -45,6 +45,14 @@ export default MyComponent;"
 `)
   })
 
+  it('should handle template that does not return an array', () => {
+    const { code } = testPlugin('<svg><div /></svg>', {
+      template: ({ template }, opts, { jsx }) => template.ast`${jsx}`,
+      state: { componentName: 'SvgComponent' },
+    })
+    expect(code).toMatchInlineSnapshot(`"<svg><div /></svg>;"`)
+  })
+
   it('should work with ref', () => {
     const { code } = testPlugin('<svg><div /></svg>', {
       state: { componentName: 'SvgComponent' },
diff --git a/packages/hast-util-to-babel-ast/src/__snapshots__/index.test.js.snap b/packages/hast-util-to-babel-ast/src/__snapshots__/index.test.js.snap
new file mode 100644
index 0000000..a160379
--- /dev/null
+++ b/packages/hast-util-to-babel-ast/src/__snapshots__/index.test.js.snap
@@ -0,0 +1,10 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`hast-util-to-babel-ast should correctly transform svg 1`] = `
+"<svg width=\\"88px\\" height=\\"88px\\" viewBox=\\"0 0 88 88\\" version={1.1} xmlns=\\"http://www.w3.org/2000/svg\\" xmlnsXlink=\\"http://www.w3.org/1999/xlink\\">{
+    /* Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch */
+  }<title>{\\"Dismiss\\"}</title><desc>{\\"Created with Sketch.\\"}</desc><defs /><g id=\\"Blocks\\" stroke=\\"none\\" strokeWidth={1} fill=\\"none\\" fillRule=\\"evenodd\\" strokeLinecap=\\"square\\"><g id=\\"Dismiss\\" stroke=\\"#063855\\" strokeWidth={2}><path d=\\"M51,37 L37,51\\" id=\\"Shape\\" /><path d=\\"M51,51 L37,37\\" id=\\"Shape\\" /></g></g></svg>;"
+`;
+
+exports[`hast-util-to-babel-ast should handle 
+	 1`] = `"<svg viewBox=\\"0 0 32 32\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path d=\\"M25,5h-3V3c0-1.7-1.3-3-3-3H5C3.3,0,2,1.3,2,3v20c0,1.7,1.3,3,3,3h4v1c0,2.2,1.8,4,4,4h12c2.2,0,4-1.8,4-4V9       C29,6.8,27.2,5,25,5z M5,24c-0.6,0-1-0.5-1-1V3c0-0.6,0.4-1,1-1h14c0.5,0,1,0.4,1,1v2h-6.3H13H6.3c-0.6,0-1,0.4-1,1s0.4,1,1,1h3.2       C9.4,7.3,9.2,7.7,9.1,8C9.1,8,9,8,9,8H6.5c-0.6,0-1,0.4-1,1s0.4,1,1,1H9v3c-0.1,0-0.1,0-0.2,0H6.1c-0.6,0-1,0.4-1,1s0.4,1,1,1h2.7       c0.1,0,0.1,0,0.2,0V16c-0.1,0-0.1,0-0.2,0H6.1c-0.6,0-1,0.4-1,1c0,0.6,0.4,1,1,1h2.7c0.1,0,0.1,0,0.2,0V19c-0.1,0-0.1,0-0.2,0H6.1       c-0.6,0-1,0.4-1,1s0.4,1,1,1h2.7c0.1,0,0.1,0,0.2,0v3H5z M27,27c0,1.1-0.9,2-2,2H13c-1.1,0-2-0.9-2-2V9c0-1.1,0.9-2,2-2h0.7H25       c1.1,0,2,0.9,2,2V27z M25.2,19c0,0.6-0.4,1-1,1H13.4c-0.6,0-1-0.4-1-1s0.4-1,1-1h10.7C24.7,18,25.2,18.4,25.2,19z M25.2,22       c0,0.6-0.4,1-1,1H13.4c-0.6,0-1-0.4-1-1s0.4-1,1-1h10.7C24.7,21,25.2,21.4,25.2,22z M25.2,25c0,0.6-0.4,1-1,1H13.4c-0.6,0-1-0.4-1-1       s0.4-1,1-1h10.7C24.7,24,25.2,24.4,25.2,25z M12.3,11c0-0.6,0.4-1,1-1h7.3c0.6,0,1,0.4,1,1s-0.4,1-1,1h-7.3       C12.8,12,12.3,11.6,12.3,11z M16,13c0.6,0,1,0.4,1,1s-0.4,1-1,1h-2.5c-0.6,0-1-0.4-1-1s0.4-1,1-1H16z\\" /></svg>;"`;
diff --git a/packages/hast-util-to-babel-ast/src/index.test.js b/packages/hast-util-to-babel-ast/src/index.test.js
new file mode 100644
index 0000000..422bf37
--- /dev/null
+++ b/packages/hast-util-to-babel-ast/src/index.test.js
@@ -0,0 +1,75 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import unified from 'unified'
+import parse from 'rehype-parse'
+import vfile from 'vfile'
+import generate from '@babel/generator'
+import hastToBabelAst from './index'
+
+function transform(code) {
+  const hastTree = unified()
+    .use(parse, {
+      fragment: true,
+      space: 'svg',
+      emitParseErrors: true,
+      duplicateAttribute: false,
+    })
+    .parse(vfile({ path: 'test.svg', contents: code }))
+
+  const babelTree = hastToBabelAst(hastTree)
+
+  const { code: generatedCode } = generate(babelTree)
+
+  return generatedCode
+}
+
+describe('hast-util-to-babel-ast', () => {
+  it('should correctly transform svg', () => {
+    const code = `
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
+    <title>Dismiss</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Blocks" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
+        <g id="Dismiss" stroke="#063855" stroke-width="2">
+            <path d="M51,37 L37,51" id="Shape"></path>
+            <path d="M51,51 L37,37" id="Shape"></path>
+        </g>
+    </g>
+</svg>
+`
+    expect(transform(code)).toMatchSnapshot()
+  })
+
+  it('should correctly transform aria-x', () => {
+    const code = `<svg aria-hidden="true"></svg>`
+    expect(transform(code)).toMatchInlineSnapshot(
+      `"<svg aria-hidden=\\"true\\" />;"`,
+    )
+  })
+
+  it('should correctly transform data-x', () => {
+    const code = `<svg data-hidden="true"></svg>`
+    expect(transform(code)).toMatchInlineSnapshot(
+      `"<svg data-hidden=\\"true\\" />;"`,
+    )
+  })
+
+  it('should handle \n\t', () => {
+    const code = `<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+    <path d="M25,5h-3V3c0-1.7-1.3-3-3-3H5C3.3,0,2,1.3,2,3v20c0,1.7,1.3,3,3,3h4v1c0,2.2,1.8,4,4,4h12c2.2,0,4-1.8,4-4V9
+      C29,6.8,27.2,5,25,5z M5,24c-0.6,0-1-0.5-1-1V3c0-0.6,0.4-1,1-1h14c0.5,0,1,0.4,1,1v2h-6.3H13H6.3c-0.6,0-1,0.4-1,1s0.4,1,1,1h3.2
+      C9.4,7.3,9.2,7.7,9.1,8C9.1,8,9,8,9,8H6.5c-0.6,0-1,0.4-1,1s0.4,1,1,1H9v3c-0.1,0-0.1,0-0.2,0H6.1c-0.6,0-1,0.4-1,1s0.4,1,1,1h2.7
+      c0.1,0,0.1,0,0.2,0V16c-0.1,0-0.1,0-0.2,0H6.1c-0.6,0-1,0.4-1,1c0,0.6,0.4,1,1,1h2.7c0.1,0,0.1,0,0.2,0V19c-0.1,0-0.1,0-0.2,0H6.1
+      c-0.6,0-1,0.4-1,1s0.4,1,1,1h2.7c0.1,0,0.1,0,0.2,0v3H5z M27,27c0,1.1-0.9,2-2,2H13c-1.1,0-2-0.9-2-2V9c0-1.1,0.9-2,2-2h0.7H25
+      c1.1,0,2,0.9,2,2V27z M25.2,19c0,0.6-0.4,1-1,1H13.4c-0.6,0-1-0.4-1-1s0.4-1,1-1h10.7C24.7,18,25.2,18.4,25.2,19z M25.2,22
+      c0,0.6-0.4,1-1,1H13.4c-0.6,0-1-0.4-1-1s0.4-1,1-1h10.7C24.7,21,25.2,21.4,25.2,22z M25.2,25c0,0.6-0.4,1-1,1H13.4c-0.6,0-1-0.4-1-1
+      s0.4-1,1-1h10.7C24.7,24,25.2,24.4,25.2,25z M12.3,11c0-0.6,0.4-1,1-1h7.3c0.6,0,1,0.4,1,1s-0.4,1-1,1h-7.3
+      C12.8,12,12.3,11.6,12.3,11z M16,13c0.6,0,1,0.4,1,1s-0.4,1-1,1h-2.5c-0.6,0-1-0.4-1-1s0.4-1,1-1H16z"/>
+    </svg>
+    `
+
+    expect(transform(code)).toMatchSnapshot()
+  })
+})