├── .babelrc
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── package.json
├── preset.js
└── src
├── __tests__
├── .babelrc
├── __snapshots__
│ ├── index.js.snap
│ └── react.js.snap
├── index.js
└── react.js
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "loose": true
8 | }
9 | ],
10 | "stage-2",
11 | "react"
12 | ],
13 | "env": {
14 | "test": {
15 | "presets": [
16 | [
17 | "env",
18 | {
19 | "loose": true
20 | }
21 | ],
22 | "stage-2",
23 | "react"
24 | ]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /demo/dist
3 | /dist
4 | /lib
5 | /es
6 | /node_modules
7 | /umd
8 | /es
9 | npm-debug.log
10 | .idea
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "7"
5 |
6 | cache:
7 | directories:
8 | - node_modules
9 |
10 | before_install:
11 | - npm install codecov
12 |
13 | after_success:
14 | - cat ./coverage/lcov.info | ./node_modules/codecov/bin/codecov
15 |
16 | cache:
17 | directories:
18 | - node_modules
19 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | [Node.js](http://nodejs.org/) >= v7 must be installed.
4 |
5 | ## Installation
6 |
7 | - Running `npm install` in the module's root directory will install everything you need for development.
8 |
9 | ## Running Tests
10 |
11 | - `npm test` will run the tests once.
12 |
13 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`.
14 |
15 | - `npm run test:watch` will run the tests on every change.
16 |
17 | ## Building
18 |
19 | - `npm run build` will build the module for publishing to npm.
20 |
21 | - `npm run clean` will delete built resources.
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Kye Hohenberger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # babel-plugin-styled-components-attr
2 |
3 |
4 | use the CSS attr function in your styled-components code.
5 |
6 |
7 |
8 | [](https://badge.fury.io/js/babel-plugin-styled-components-attr)
9 | [](https://travis-ci.org/tkh44/babel-plugin-styled-components-attr)
10 | [](https://codecov.io/gh/tkh44/babel-plugin-styled-components-attr)
11 |
12 |
13 |
14 | ```jsx
15 | const Input = styled.input`
16 | color: attr(color);
17 | width: attr(width %);
18 | margin: attr(margin px, 16);
19 | `
20 | ```
21 |
22 | ## Install
23 |
24 | ```bash
25 | npm install -S babel-plugin-styled-components-attr
26 | ```
27 |
28 |
29 | **.babelrc**
30 | ```json
31 | {
32 | "plugins": [
33 | "styled-components-attr"
34 | ]
35 | }
36 | ```
37 |
38 | #### attr
39 |
40 | The [attr](https://developer.mozilla.org/en-US/docs/Web/CSS/attr) CSS function is supported in
41 | a basic capacity. I will be adding more features, but PRs are welcome.
42 |
43 | ```css
44 | /* get value from `width` prop */
45 | width: attr(width vw);
46 |
47 | /* specify type or unit to apply to value */
48 | width: attr(width vw);
49 |
50 | /* fallback value if props.width is falsey */
51 | width: attr(width vw, 50);
52 | ```
53 |
54 | ```jsx
55 | const H1 = styled.h1`
56 | font-size: attr(fontSize px);
57 | margin: attr(margin rem, 4);
58 | font-family: sans-serif;
59 | color: ${colors.pink[5]};
60 | @media (min-width: 680px) {
61 | color: attr(desktopColor);
62 | }
63 | `
64 |
65 | const Title = ({ title, scale }) => {
66 | return (
67 |
68 | {title}
69 |
70 | )
71 | }
72 | ```
73 |
74 | ##### Value types
75 | *checked is supported*
76 |
77 | - [x] em
78 | - [x] ex
79 | - [x] px
80 | - [x] rem
81 | - [x] vw
82 | - [x] vh
83 | - [x] vmin
84 | - [x] vmax
85 | - [x] mm
86 | - [x] cm
87 | - [x] in
88 | - [x] pt
89 | - [x] pc
90 | - [x] %
91 | - [ ] string
92 | - [ ] color
93 | - [ ] url
94 | - [ ] integer
95 | - [ ] number
96 | - [ ] length
97 | - [ ] deg
98 | - [ ] grad
99 | - [ ] rad
100 | - [ ] time
101 | - [ ] s
102 | - [ ] ms
103 | - [ ] frequency
104 | - [ ] Hz
105 | - [ ] kHz
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "babel-plugin-styled-components-attr",
3 | "version": "0.0.3",
4 | "description": "CSS attr function in your styled components",
5 | "main": "src/index.js",
6 | "files": [
7 | "src"
8 | ],
9 | "scripts": {
10 | "test": "standard src test && jest --coverage --no-cache",
11 | "test:watch": "jest --watch",
12 | "release": "npm run test && npm version patch && npm publish && git push --tags"
13 | },
14 | "dependencies": {},
15 | "devDependencies": {
16 | "babel-cli": "^6.24.1",
17 | "babel-core": "^6.24.1",
18 | "babel-eslint": "^7.2.3",
19 | "babel-jest": "^20.0.3",
20 | "babel-loader": "^7.0.0",
21 | "babel-preset-env": "^1.5.1",
22 | "babel-preset-react": "^6.24.1",
23 | "babel-preset-stage-0": "^6.24.1",
24 | "jest": "^20.0.4",
25 | "jest-cli": "^20.0.4",
26 | "jest-styled-components": "^3.0.0",
27 | "npm-run-all": "^4.0.2",
28 | "react": "^15.5.4",
29 | "react-addons-test-utils": "^15.5.1",
30 | "react-dom": "^15.5.4",
31 | "react-test-renderer": "^15.5.4",
32 | "standard": "^10.0.2",
33 | "styled-components": "^2.0.1"
34 | },
35 | "author": "Kye Hohenberger",
36 | "homepage": "https://github.com/tkh44/babel-plugin-styled-components-attr#readme",
37 | "license": "MIT",
38 | "repository": {
39 | "type": "git",
40 | "url": "git+https://github.com/tkh44/babel-plugin-styled-components-attr.git"
41 | },
42 | "directories": {
43 | "test": "tests"
44 | },
45 | "keywords": [
46 | "styles",
47 | "babel-plugin-styled-components-attr",
48 | "react",
49 | "css",
50 | "css-in-js",
51 | "styled-components"
52 | ],
53 | "eslintConfig": {
54 | "extends": "standard",
55 | "parser": "babel-eslint"
56 | },
57 | "standard": {
58 | "parser": "babel-eslint",
59 | "ignore": [
60 | "/dist/"
61 | ]
62 | },
63 | "bugs": {
64 | "url": "https://github.com/tkh44/babel-plugin-styled-components-attr/issues"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/preset.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('./src/index.js')
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/__tests__/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "stage-0",
5 | "react"
6 | ],
7 | "plugins": [
8 | ["../index.js"]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/index.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`emotion/babel babel styled component all value types 1`] = `
4 | "styled('input')\`
5 | margin: \${function getMargin(props) {
6 | return props.margin + 'em';
7 | }};
8 | margin: \${function getMargin(props) {
9 | return props.margin + 'ex';
10 | }};
11 | margin: \${function getMargin(props) {
12 | return props.margin + 'px';
13 | }};
14 | margin: \${function getMargin(props) {
15 | return props.margin + 'rem';
16 | }};
17 | margin: \${function getMargin(props) {
18 | return props.margin + 'vw';
19 | }};
20 | margin: \${function getMargin(props) {
21 | return props.margin + 'vh';
22 | }};
23 | margin: \${function getMargin(props) {
24 | return props.margin + 'vmin';
25 | }};
26 | margin: \${function getMargin(props) {
27 | return props.margin + 'vmax';
28 | }};
29 | margin: \${function getMargin(props) {
30 | return props.margin + 'mm';
31 | }};
32 | margin: \${function getMargin(props) {
33 | return props.margin + 'cm';
34 | }};
35 | margin: \${function getMargin(props) {
36 | return props.margin + 'in';
37 | }};
38 | margin: \${function getMargin(props) {
39 | return props.margin + 'pt';
40 | }};
41 | margin: \${function getMargin(props) {
42 | return props.margin + 'pc';
43 | }};
44 | margin: \${function getMargin(props) {
45 | return props.margin + '%';
46 | }};
47 | \`;"
48 | `;
49 |
50 | exports[`emotion/babel babel styled component basic 1`] = `
51 | "
52 | styled('input')\`
53 | height: \${props => props.height};
54 | margin: \${function getMargin(props) {
55 | return props.margin;
56 | }};
57 | padding: \${function getPadding(props) {
58 | return undefined === props.padding || null === props.padding ? ('16' + 'em') : props.padding;
59 | }};
60 | color: blue;
61 | \`;"
62 | `;
63 |
64 | exports[`emotion/babel babel styled component css tag 1`] = `
65 | "css\`
66 | margin: \${function getMargin(props) {
67 | return undefined === props.margin || null === props.margin ? (\\"16\\" + \\"px\\") : props.margin;
68 | }};
69 | padding: \${function getPadding(props) {
70 | return undefined === props.padding || null === props.padding ? (\\"16\\" + \\"em\\") : props.padding;
71 | }};
72 | font-size: attr(fontSize ch, 8);
73 | width: \${function getWidth(props) {
74 | return undefined === props.width || null === props.width ? (\\"95\\" + \\"%\\") : props.width;
75 | }};
76 | height: \${function getHeight(props) {
77 | return undefined === props.height || null === props.height ? (\\"90\\" + \\"vw\\") : props.height;
78 | }};
79 | display: \${function getDisplay(props) {
80 | return undefined === props.display || null === props.display ? (\\"flex\\" + \\"\\") : props.display;
81 | }};
82 | \`;"
83 | `;
84 |
85 | exports[`emotion/babel babel styled component empty 1`] = `
86 | "
87 | styled('input')\`\`;"
88 | `;
89 |
90 | exports[`emotion/babel babel styled component kitchen sink 1`] = `
91 | "styled('input')\`
92 | margin: \${function getMargin(props) {
93 | return undefined === props.margin || null === props.margin ? ('16' + 'px') : props.margin;
94 | }};
95 | padding: \${function getPadding(props) {
96 | return undefined === props.padding || null === props.padding ? ('16' + 'em') : props.padding;
97 | }};
98 | font-size: attr(fontSize ch, 8);
99 | width: \${function getWidth(props) {
100 | return undefined === props.width || null === props.width ? ('95' + '%') : props.width;
101 | }};
102 | height: \${function getHeight(props) {
103 | return undefined === props.height || null === props.height ? ('90' + 'vw') : props.height;
104 | }};
105 | display: \${function getDisplay(props) {
106 | return undefined === props.display || null === props.display ? ('flex' + '') : props.display;
107 | }};
108 | \`;"
109 | `;
110 |
111 | exports[`emotion/babel babel styled component member expression 1`] = `
112 | "styled.input\`
113 | margin: \${function getMargin(props) {
114 | return props.margin;
115 | }};
116 | color: #ffffff;
117 | height: \${props => props.height * props.scale};
118 | width: \${function getWidth(props) {
119 | return props.width;
120 | }};
121 | color: blue;
122 | display: \${flex};
123 | \`;"
124 | `;
125 |
126 | exports[`emotion/babel babel styled component something else 1`] = `
127 | "
128 | const a = \`color: attr(height);\`;"
129 | `;
130 |
131 | exports[`emotion/babel babel styled component with default value 1`] = `
132 | "styled('input')\`
133 | margin: \${function getMargin(props) {
134 | return undefined === props.margin || null === props.margin ? ('16' + '') : props.margin;
135 | }};
136 | \`;"
137 | `;
138 |
139 | exports[`emotion/babel babel styled component with value type 1`] = `
140 | "styled('input')\`
141 | margin: \${function getMargin(props) {
142 | return props.margin + 'px';
143 | }};
144 | \`;"
145 | `;
146 |
147 | exports[`emotion/babel babel styled component with value type 2`] = `
148 | "styled.input.attrs({ type: 'text' })\`
149 | margin: \${function getMargin(props) {
150 | return props.margin + 'px';
151 | }};
152 | \`;"
153 | `;
154 |
155 | exports[`emotion/babel babel styled component with value type and default value 1`] = `
156 | "styled('input')\`
157 | margin: \${function getMargin(props) {
158 | return undefined === props.margin || null === props.margin ? ('16' + 'px') : props.margin;
159 | }};
160 | \`;"
161 | `;
162 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/react.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`react attr 1`] = `
4 | .hKjquV {
5 | font-size: 48;
6 | margin: 4rem;
7 | color: #495057;
8 | }
9 |
10 |
15 | `;
16 |
17 | exports[`react call expression 1`] = `
18 | .etaRTp {
19 | color: #ffffff;
20 | width: 48%;
21 | margin: 16;
22 | }
23 |
24 | .jQCmMp {
25 | font-size: ;
26 | margin: 4rem;
27 | }
28 |
29 |
32 | hello world
33 |
38 |
39 | `;
40 |
41 | exports[`react default value does not show when real value is 0 1`] = `
42 | .dpjTwL {
43 | height: 0;
44 | }
45 |
46 |
50 | `;
51 |
52 | exports[`react function in expression 1`] = `
53 | .dFwslv {
54 | height: 48;
55 | font-size: 20px;
56 | }
57 |
58 | .gXhtwV {
59 | font-size: 40;
60 | }
61 |
62 |
67 | hello world
68 |
69 | `;
70 |
--------------------------------------------------------------------------------
/src/__tests__/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-quotes,no-useless-escape,no-template-curly-in-string */
2 | /* eslint-env jest */
3 | const plugin = require('../index')
4 | const babel = require('babel-core')
5 |
6 | describe('emotion/babel', () => {
7 | describe('babel styled component', () => {
8 | test('something else', () => {
9 | const basic = `
10 | const a = \`color: attr(height);\`
11 | `
12 | const {code} = babel.transform(basic, {
13 | plugins: [plugin]
14 | })
15 | expect(code).toMatchSnapshot()
16 | })
17 |
18 | test('empty', () => {
19 | const basic = `
20 | styled('input')\`\`
21 | `
22 | const {code} = babel.transform(basic, {
23 | plugins: [plugin]
24 | })
25 | expect(code).toMatchSnapshot()
26 | })
27 |
28 | test('css tag', () => {
29 | const basic = `css\`
30 | margin: attr(margin px, 16);
31 | padding: attr(padding em, 16);
32 | font-size: attr(fontSize ch, 8);
33 | width: attr(width %, 95);
34 | height: attr(height vw, 90);
35 | display: attr(display, flex);
36 | \``
37 | const {code} = babel.transform(basic, {
38 | plugins: [plugin]
39 | })
40 | expect(code).toMatchSnapshot()
41 | })
42 |
43 | test('basic', () => {
44 | const basic = `
45 | styled('input')\`
46 | height: $\{props => props.height};
47 | margin: attr(margin);
48 | padding: attr(padding em, 16);
49 | color: blue;
50 | \`
51 | `
52 | const {code} = babel.transform(basic, {
53 | plugins: [plugin]
54 | })
55 | expect(code).toMatchSnapshot()
56 | })
57 |
58 |
59 |
60 | test('member expression', () => {
61 | const basic = `styled.input\`
62 | margin: attr(margin);
63 | color: #ffffff;
64 | height: \$\{props => props.height * props.scale\};
65 | width: attr(width);
66 | color: blue;
67 | display: \$\{flex\};
68 | \``
69 | const { code } = babel.transform(basic, {
70 | plugins: [plugin]
71 | })
72 | expect(code).toMatchSnapshot()
73 | })
74 |
75 | test('with value type', () => {
76 | const basic = `styled('input')\`
77 | margin: attr(margin px);
78 | \``
79 | const { code } = babel.transform(basic, {
80 | plugins: [plugin]
81 | })
82 | expect(code).toMatchSnapshot()
83 | })
84 |
85 | test('with default value', () => {
86 | const basic = `styled('input')\`
87 | margin: attr(margin, 16);
88 | \``
89 | const { code } = babel.transform(basic, {
90 | plugins: [plugin]
91 | })
92 | expect(code).toMatchSnapshot()
93 | })
94 |
95 | test('with value type and default value', () => {
96 | const basic = `styled('input')\`
97 | margin: attr(margin px, 16);
98 | \``
99 | const { code } = babel.transform(basic, {
100 | plugins: [plugin]
101 | })
102 | expect(code).toMatchSnapshot()
103 | })
104 |
105 | test('with value type', () => {
106 | const basic = `styled.input.attrs({ type: 'text' })\`
107 | margin: attr(margin px);
108 | \``
109 | const {code} = babel.transform(basic, {
110 | plugins: [plugin]
111 | })
112 | expect(code).toMatchSnapshot()
113 | })
114 |
115 | test('kitchen sink', () => {
116 | const basic = `styled('input')\`
117 | margin: attr(margin px, 16);
118 | padding: attr(padding em, 16);
119 | font-size: attr(fontSize ch, 8);
120 | width: attr(width %, 95);
121 | height: attr(height vw, 90);
122 | display: attr(display, flex);
123 | \``
124 | const { code } = babel.transform(basic, {
125 | plugins: [plugin]
126 | })
127 | expect(code).toMatchSnapshot()
128 | })
129 |
130 | test('all value types', () => {
131 | const basic = `styled('input')\`
132 | margin: attr(margin em);
133 | margin: attr(margin ex);
134 | margin: attr(margin px);
135 | margin: attr(margin rem);
136 | margin: attr(margin vw);
137 | margin: attr(margin vh);
138 | margin: attr(margin vmin);
139 | margin: attr(margin vmax);
140 | margin: attr(margin mm);
141 | margin: attr(margin cm);
142 | margin: attr(margin in);
143 | margin: attr(margin pt);
144 | margin: attr(margin pc);
145 | margin: attr(margin %);
146 | \``
147 | const {code} = babel.transform(basic, {
148 | plugins: [plugin]
149 | })
150 | expect(code).toMatchSnapshot()
151 | })
152 | })
153 | })
154 |
--------------------------------------------------------------------------------
/src/__tests__/react.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-quotes,no-useless-escape,no-template-curly-in-string */
2 | /* eslint-env jest */
3 | import 'jest-styled-components'
4 | import React from 'react'
5 | import renderer from 'react-test-renderer'
6 | import styled, { css } from 'styled-components'
7 |
8 | describe('react', () => {
9 | test('attr', () => {
10 | const baseText = css`color: attr(color, #a9e34b);`
11 |
12 | const H1 = styled.h1`
13 | font-size: attr(fontSize);
14 | margin: attr(margin rem, 4);
15 | ${baseText}
16 | `
17 |
18 | const Title = ({ title }) => {
19 | return (
20 |
21 | {title}
22 |
23 | )
24 | }
25 |
26 | const tree = renderer.create().toJSON()
27 |
28 | expect(tree).toMatchStyledComponentsSnapshot()
29 | })
30 |
31 | test('call expression', () => {
32 | const Input = styled.input`
33 | color: attr(color);
34 | width: attr(width %);
35 | margin: attr(margin px, 16);
36 | `
37 |
38 | const H1 = styled('h1')`
39 | font-size: attr(fontSize);
40 | margin: attr(margin rem, 4);
41 | `
42 |
43 | const tree = renderer
44 | .create(
45 |
46 | hello world
47 |
48 |
49 | )
50 | .toJSON()
51 |
52 | expect(tree).toMatchStyledComponentsSnapshot()
53 | })
54 |
55 | test('function in expression', () => {
56 | const fontSize = 20
57 | const H1 = styled('h1')`
58 | height: attr(height);
59 | font-size: ${fontSize}px;
60 | `
61 |
62 | const H2 = styled(H1)`font-size: ${({ scale }) => fontSize * scale}`
63 |
64 | const tree = renderer
65 | .create(
66 |
67 | hello world
68 |
69 | )
70 | .toJSON()
71 |
72 | expect(tree).toMatchStyledComponentsSnapshot()
73 | })
74 |
75 | test('default value does not show when real value is 0', () => {
76 | const H1 = styled('h1')`
77 | height: attr(height px, 99);
78 | `
79 |
80 | const tree = renderer.create().toJSON()
81 |
82 | expect(tree).toMatchStyledComponentsSnapshot()
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function (babel) {
2 | const { types: t } = babel
3 |
4 | function findAndReplaceAttrs (path) {
5 | let quasis = path.node.quasis
6 | let stubs = path.node.expressions
7 | let didFindAtLeastOneMatch = false
8 |
9 | let [nextQuasis, nextStubs] = quasis.reduce(
10 | (accum, quasi, i) => {
11 | const str = quasi.value.cooked
12 | const regex = /attr\(([\S]+)(?:\s*(em|ex|px|rem|vw|vh|vmin|vmax|mm|cm|in|pt|pc|%)?)(?:,\s*([\S^)]+))?\)/gm
13 | let attrMatch
14 | let matches = []
15 | while ((attrMatch = regex.exec(str)) !== null) {
16 | didFindAtLeastOneMatch = true
17 | matches.push({
18 | value: attrMatch[0],
19 | propName: attrMatch[1],
20 | valueType: attrMatch[2],
21 | defaultValue: attrMatch[3],
22 | index: attrMatch.index
23 | })
24 | }
25 | let cursor = 0
26 | for (let j = 0; j < matches.length; ++j) {
27 | const match = matches[j]
28 | const value = match.value
29 | const propName = match.propName
30 | const valueType = match.valueType
31 | const defaultValue = match.defaultValue
32 | const index = match.index
33 |
34 | const preAttr = `${str.slice(cursor, index)}`
35 | cursor = index + value.length
36 | const postAttr = `${str.slice(cursor)}`
37 |
38 | if (preAttr) {
39 | accum[0].push(
40 | t.templateElement(
41 | {
42 | raw: preAttr,
43 | cooked: preAttr
44 | },
45 | false
46 | )
47 | )
48 | }
49 |
50 | if (postAttr && j === matches.length - 1) {
51 | accum[0].push(
52 | t.templateElement(
53 | {
54 | raw: postAttr,
55 | cooked: postAttr
56 | },
57 | i === quasis.length - 1
58 | )
59 | )
60 | }
61 |
62 | let createMemberExpression = () =>
63 | t.memberExpression(t.identifier('props'), t.identifier(propName))
64 |
65 | let returnValue = createMemberExpression()
66 |
67 | if (valueType) {
68 | returnValue = t.binaryExpression(
69 | '+',
70 | createMemberExpression(),
71 | t.stringLiteral(valueType)
72 | )
73 | }
74 |
75 | if (defaultValue) {
76 | returnValue = t.conditionalExpression(
77 | t.logicalExpression(
78 | '||',
79 | t.binaryExpression(
80 | '===',
81 | t.identifier('undefined'),
82 | createMemberExpression()
83 | ),
84 | t.binaryExpression(
85 | '===',
86 | t.nullLiteral(),
87 | createMemberExpression()
88 | )
89 | ),
90 | t.parenthesizedExpression(
91 | t.binaryExpression(
92 | '+',
93 | t.stringLiteral(defaultValue),
94 | t.stringLiteral(valueType || '')
95 | )
96 | ),
97 | createMemberExpression()
98 | )
99 | }
100 |
101 | const body = t.blockStatement([t.returnStatement(returnValue)])
102 |
103 | const expr = t.functionExpression(
104 | t.identifier(
105 | `get${propName.charAt(0).toUpperCase() + propName.slice(1)}`
106 | ),
107 | [t.identifier('props')],
108 | body
109 | )
110 | accum[1].push(expr)
111 | }
112 |
113 | if (matches.length === 0) {
114 | accum[0].push(quasi)
115 | if (stubs[i]) {
116 | accum[1].push(stubs[i])
117 | }
118 | } else if (stubs[i]) {
119 | accum[1].push(stubs[i])
120 | }
121 |
122 | return accum
123 | },
124 | [[], []]
125 | )
126 |
127 | if (didFindAtLeastOneMatch) {
128 | return t.templateLiteral(nextQuasis, nextStubs)
129 | }
130 |
131 | return path.node
132 | }
133 |
134 | const Visitor = {
135 | TemplateLiteral (path) {
136 | const parentPath = path.find(path => path.isTaggedTemplateExpression())
137 | if (!parentPath) {
138 | return
139 | }
140 | let parentTag = parentPath.node.tag
141 |
142 | // grab correct tab is styled.input.attrs type is used
143 | if (
144 | t.isMemberExpression(parentTag.callee) &&
145 | t.isMemberExpression(parentTag.callee.object) &&
146 | parentTag.callee.object.object.name === 'styled'
147 | ) {
148 | parentTag = parentTag.callee.object
149 | }
150 |
151 | if (parentTag.name === 'css') {
152 | // css`...`
153 | path.replaceWith(findAndReplaceAttrs(path))
154 | } else if (
155 | // styled.h1`...`
156 | t.isMemberExpression(parentTag) &&
157 | parentTag.object.name === 'styled' &&
158 | t.isTemplateLiteral(path.node)
159 | ) {
160 | path.replaceWith(findAndReplaceAttrs(path))
161 | } else if (
162 | // styled('h1')`...`
163 | t.isCallExpression(parentPath.node.tag) &&
164 | parentPath.node.tag.callee.name === 'styled' &&
165 | t.isTemplateLiteral(path.node)
166 | ) {
167 | path.replaceWith(findAndReplaceAttrs(path))
168 | }
169 | }
170 | }
171 |
172 | return {
173 | visitor: {
174 | TaggedTemplateExpression (path) {
175 | path.traverse(Visitor)
176 | }
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------