├── .eslintignore
├── .github
├── dependabot.yml
└── workflows
│ └── nodejs.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmrc
├── .prettierignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── camel-case.js
├── extract.js
├── get-template.js
├── index.js
├── literal.js
├── object-parse.js
├── object-parser.js
├── object-stringifier.js
├── object-stringify.js
├── object-syntax.js
├── object.js
├── package-lock.json
├── package.json
├── template-parse.js
├── template-parser-helper.js
├── template-parser.js
├── template-safe-parse.js
├── template-safe-parser.js
├── template-stringifier.js
├── template-stringify.js
├── template-tokenize.js
├── test
├── babel-config
│ ├── .babelrc.json
│ ├── babel-config.test.js
│ ├── babel-plugin-my-plugin.js
│ └── fixtures
│ │ └── styled-components.js
├── camel-case.js
├── css-in-js.js
├── emotion.js
├── fixtures
│ ├── emotion-10.jsx
│ ├── emotion-10.jsx.json
│ ├── glamorous.jsx
│ ├── glamorous.jsx.json
│ ├── interpolation-content.mjs
│ ├── interpolation-content.mjs.json
│ ├── jsx.jsx
│ ├── jsx.jsx.json
│ ├── lit-css.mjs
│ ├── lit-css.mjs.json
│ ├── material-ui.jsx
│ ├── material-ui.jsx.json
│ ├── multiline-arrow-function.mjs
│ ├── react-emotion.jsx
│ ├── react-emotion.jsx.json
│ ├── react-native.mjs
│ ├── react-native.mjs.json
│ ├── styled-components-nesting-expr.js
│ ├── styled-components-nesting-expr.js.json
│ ├── styled-components-nesting-nesting.js
│ ├── styled-components-nesting-nesting.js.json
│ ├── styled-components-nesting-template-literal.js
│ ├── styled-components-nesting-template-literal.js.json
│ ├── styled-components-nesting.js
│ ├── styled-components-nesting.js.json
│ ├── styled-components-nesting2.js
│ ├── styled-components-nesting2.js.json
│ ├── styled-components-nesting3.js
│ ├── styled-components-nesting3.js.json
│ ├── styled-components.js
│ ├── styled-components.js.json
│ ├── styled-opts.mjs
│ ├── styled-opts.mjs.json
│ ├── styled-props.jsx
│ ├── styled-props.jsx.json
│ ├── toLocaleString.js
│ ├── tpl-decl.mjs
│ ├── tpl-decl.mjs.json
│ ├── tpl-in-tpl.mjs
│ ├── tpl-in-tpl.mjs.json
│ ├── tpl-selector.mjs
│ ├── tpl-selector.mjs.json
│ ├── tpl-special.mjs
│ └── tpl-special.mjs.json
├── glamorous.js
├── literals.js
├── non-style.js
├── react-native.js
├── react.js
├── styled-components.js
└── supports.js
└── un-camel-case.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/fixtures/**
2 | coverage/**
3 | .nyc_output/**
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: '/'
5 | schedule:
6 | interval: montly
7 | versioning-strategy: increase
8 | open-pull-requests-limit: 1
9 | labels:
10 | - 'pr: dependencies'
11 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - 'dependabot/**'
8 | pull_request:
9 | branches:
10 | - '**'
11 |
12 | jobs:
13 | lint:
14 | name: Lint on Node.js LTS
15 |
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Use Node.js LTS
22 | uses: actions/setup-node@v2
23 | with:
24 | node-version: 'lts/*'
25 | cache: npm
26 |
27 | - name: Install latest npm
28 | run: npm install --global npm@latest
29 |
30 | - name: Install dependencies
31 | run: npm ci
32 |
33 | - name: Lint
34 | run: npm run lint
35 |
36 | test:
37 | name: Test on Node.js ${{ matrix.node }}
38 |
39 | runs-on: ubuntu-latest
40 |
41 | strategy:
42 | fail-fast: false
43 | matrix:
44 | node: [12, 14, 16]
45 |
46 | steps:
47 | - uses: actions/checkout@v2
48 |
49 | - name: Use Node.js ${{ matrix.node }}
50 | uses: actions/setup-node@v2
51 | with:
52 | node-version: ${{ matrix.node }}
53 | cache: npm
54 |
55 | - name: Install latest npm
56 | run: npm install --global npm@latest
57 |
58 | - name: Install dependencies
59 | run: npm ci
60 |
61 | - name: Test
62 | run: npm test
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/node
2 |
3 | ### Node ###
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 | *.pid.lock
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 |
23 | # nyc test coverage
24 | .nyc_output
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # Bower dependency directory (https://bower.io/)
30 | bower_components
31 |
32 | # node-waf configuration
33 | .lock-wscript
34 |
35 | # Compiled binary addons (http://nodejs.org/api/addons.html)
36 | build/Release
37 |
38 | # Dependency directories
39 | node_modules/
40 | jspm_packages/
41 |
42 | # Typescript v1 declaration files
43 | typings/
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional eslint cache
49 | .eslintcache
50 |
51 | # Optional REPL history
52 | .node_repl_history
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 |
60 | # dotenv environment variables file
61 | .env
62 |
63 |
64 |
65 | # End of https://www.gitignore.io/api/node
66 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | [ -n "$CI" ] && exit 0
3 |
4 | . "$(dirname -- "$0")/_/husky.sh"
5 |
6 | npx lint-staged
7 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix = ""
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/fixtures/**
2 | coverage/**
3 | .nyc_output/**
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.38.0
4 |
5 | - Removed: hardcoded transform from float to cssFloat in objects ([#172](https://github.com/stylelint/postcss-css-in-js/pull/172))
6 | - Fixed: silent fail if @babel/plugin-proposal-decorators is in user's Babel configuration ([#237](https://github.com/stylelint/postcss-css-in-js/pull/237))
7 |
8 | ## 0.37.3
9 |
10 | - Fixed: silent fail if Babel config is present ([#258](https://github.com/stylelint/postcss-css-in-js/pull/258)).
11 |
12 | ## 0.37.2
13 |
14 | - Fixed: use HTTPS url instead of HTTP for postcss logo in README ([#39](https://github.com/stylelint/postcss-css-in-js/pull/39)).
15 |
16 | ## 0.37.1
17 |
18 | - Fixed: maximum call stack size exceeded error ([#31](https://github.com/stylelint/postcss-css-in-js/pull/31)).
19 |
20 | ## 0.37.0
21 |
22 | - Fixed: babel configuration conflict when using TypeScript ([#2](https://github.com/stylelint/postcss-css-in-js/pull/2)).
23 | - Fixed: parsing/stringifying for nested tagged template literals ([#17](https://github.com/stylelint/postcss-css-in-js/pull/17)).
24 |
25 | ## Previous changes
26 |
27 | See [postcss-jsx releases](https://github.com/gucong3000/postcss-jsx/releases).
28 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for wanting to contribute. This parser is essential to stylelint's built-in support of CSS-in-JS.
4 |
5 | We want to encourage contributions! If you want to participate but couldn't, please [give us feedback](https://github.com/stylelint/postcss-css-in-js/issues/new) about what we could do better.
6 |
7 | ## Code contributions
8 |
9 | To start coding, you'll need:
10 |
11 | - a minimum of [Node.js](https://nodejs.org/en/) v10, though we do recommend using the latest LTS release
12 | - the latest [npm](https://www.npmjs.com/)
13 |
14 | Then:
15 |
16 | 1. [Fork and clone](https://guides.github.com/activities/forking/) this repository.
17 | 2. Install all the dependencies with `npm ci`.
18 |
19 | ### Run tests
20 |
21 | Next, you'll want to run the tests using `npm test`.
22 |
23 | However, this runs the test just once.
24 |
25 | You can use `npm run watch` instead. It will run the tests when you change a file.
26 |
27 | Additionally, you can run linting checks with `npm run lint`.
28 |
29 | ### Format code
30 |
31 | We use [Prettier](https://prettier.io/) (with [a Husky and lint-staged precommit](https://prettier.io/docs/en/precommit.html)) to format your code automatically.
32 |
33 | Alternatively, you can:
34 |
35 | - trigger the pretty-printing all the files using `npm run format`
36 | - use a [Prettier editor integration](https://prettier.io/docs/en/editors.html)
37 |
38 | ### Open a pull request
39 |
40 | When you have something to share, it's time to [open a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork).
41 |
42 | After we review and merge your pull request, we'll invite you to become a maintainer of the stylelint organization. You'll then be able to work on the repository directly rather than your fork.
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 刘祺
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 | # PostCSS CSS-in-JS Syntax (deprecated)
2 |
3 | __This syntax is deprecated. Please refer to the [Stylelint v15 migration guide](https://github.com/stylelint/stylelint/blob/main/docs/migration-guide/to-15.md).__
4 |
5 | [](https://www.npmjs.org/package/@stylelint/postcss-css-in-js) [](https://github.com/stylelint/postcss-css-in-js/actions)
6 |
7 |
10 |
11 | [PostCSS](https://github.com/postcss/postcss) syntax for parsing [CSS in JS](https://github.com/MicheleBertoli/css-in-js) literals:
12 |
13 | - [aphrodite](https://github.com/Khan/aphrodite)
14 | - [astroturf](https://github.com/4Catalyzer/astroturf)
15 | - [csjs](https://github.com/rtsao/csjs)
16 | - [css-light](https://github.com/streamich/css-light)
17 | - [cssobj](https://github.com/cssobj/cssobj)
18 | - [electron-css](https://github.com/azukaar/electron-css)
19 | - [emotion](https://github.com/emotion-js/emotion)
20 | - [freestyler](https://github.com/streamich/freestyler)
21 | - [glamor](https://github.com/threepointone/glamor)
22 | - [glamorous](https://github.com/paypal/glamorous)
23 | - [j2c](https://github.com/j2css/j2c)
24 | - [linaria](https://github.com/callstack/linaria)
25 | - [lit-css](https://github.com/bashmish/lit-css)
26 | - [react-native](https://github.com/necolas/react-native-web)
27 | - [react-style](https://github.com/js-next/react-style)
28 | - [reactcss](https://github.com/casesandberg/reactcss)
29 | - [styled-components](https://github.com/styled-components/styled-components)
30 | - [styletron-react](https://github.com/rtsao/styletron)
31 | - [styling](https://github.com/andreypopp/styling)
32 | - [typestyle](https://github.com/typestyle/typestyle)
33 |
34 | ## Getting Started
35 |
36 | First thing's first, install the module:
37 |
38 | ```
39 | npm install postcss-syntax @stylelint/postcss-css-in-js --save-dev
40 | ```
41 |
42 | ## Use Cases
43 |
44 | ```js
45 | const postcss = require("postcss");
46 | const stylelint = require("stylelint");
47 | const syntax = require("postcss-syntax");
48 | postcss([stylelint({ fix: true })])
49 | .process(source, { syntax: syntax })
50 | .then(function (result) {
51 | // An alias for the result.css property. Use it with syntaxes that generate non-CSS output.
52 | result.content;
53 | });
54 | ```
55 |
56 | input:
57 |
58 | ```javascript
59 | import glm from "glamorous";
60 | const Component1 = glm.a({
61 | flexDirectionn: "row",
62 | display: "inline-block",
63 | color: "#fff"
64 | });
65 | ```
66 |
67 | output:
68 |
69 | ```javascript
70 | import glm from "glamorous";
71 | const Component1 = glm.a({
72 | color: "#fff",
73 | display: "inline-block",
74 | flexDirectionn: "row"
75 | });
76 | ```
77 |
78 | ## Advanced Use Cases
79 |
80 | Add support for more `css-in-js` package:
81 |
82 | ```js
83 | const syntax = require("postcss-syntax")({
84 | "i-css": (index, namespace) => namespace[index + 1] === "addStyles",
85 | "styled-components": true
86 | });
87 | ```
88 |
89 | See: [postcss-syntax](https://github.com/gucong3000/postcss-syntax)
90 |
91 | ## Style Transformations
92 |
93 | The main use case of this plugin is to apply PostCSS transformations to CSS code in template literals & styles as object literals.
94 |
--------------------------------------------------------------------------------
/camel-case.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function camelCase(str) {
4 | return str.replace(/[\w-]+/g, (s) => {
5 | return /^-?[a-z]+(?:-[a-z]+)+$/.test(s)
6 | ? s
7 | .replace(/^-(ms|moz|khtml|epub|(\w+-?)*webkit)(?=-)/i, '$1') // eslint-disable-line regexp/no-super-linear-backtracking -- TODO: fix
8 | .replace(/-\w/g, (uncasedStr) => uncasedStr[1].toUpperCase())
9 | : s;
10 | });
11 | }
12 |
13 | module.exports = camelCase;
14 |
--------------------------------------------------------------------------------
/extract.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getTemplate = require('./get-template');
4 | const loadSyntax = require('postcss-syntax/load-syntax');
5 | const { parse, types, traverse, loadOptions } = require('@babel/core');
6 |
7 | const isStyleSheetCreate = expectAdjacentSibling(['create']);
8 | const supports = {
9 | // import styled from '@emotion/styled'
10 | // import { styled } from 'glamor/styled'
11 | // import { styled } from "styletron-react";
12 | // import { styled } from 'linaria/react';
13 | // import { styled } from '@material-ui/styles'
14 | styled: true,
15 |
16 | // import { style } from "typestyle";
17 | style: true,
18 |
19 | // import { StyleSheet, css } from 'aphrodite';
20 | // import styled, { css } from 'astroturf';
21 | // import { css } from 'lit-css';
22 | // import { css } from 'glamor'
23 | // require('css-light').css({color: 'red'});
24 | // import { css } from 'linaria';
25 | css: true,
26 |
27 | // import { StyleSheet, css } from 'aphrodite';
28 | // import { AppRegistry, StyleSheet, Text, View } from 'react-native';
29 | StyleSheet: isStyleSheetCreate,
30 |
31 | // import styled, { css } from 'astroturf';
32 | astroturf: true,
33 |
34 | // require('csjs')`css`;
35 | csjs: true,
36 |
37 | // require('cssobj')({color: 'red'})
38 | cssobj: true,
39 |
40 | // require('electron-css')({color: 'red'})
41 | 'electron-css': true,
42 |
43 | // import styled from "react-emotion";
44 | 'react-emotion': true,
45 |
46 | // import styled from 'vue-emotion';
47 | // Also see:
48 | // - https://github.com/stylelint/stylelint/issues/4247
49 | // - https://github.com/gucong3000/postcss-jsx/issues/63
50 | // - https://github.com/stylelint/postcss-css-in-js/issues/22
51 | 'vue-emotion': true,
52 |
53 | // import styled from 'preact-emotion'
54 | 'preact-emotion': true,
55 |
56 | // https://github.com/streamich/freestyler
57 | freestyler: true,
58 |
59 | // https://github.com/paypal/glamorous
60 | glamorous: true,
61 |
62 | // https://github.com/irom-io/i-css
63 | // "i-css": (i, nameSpace) => nameSpace[i + 1] === "addStyles" && nameSpace[i + 2] === "wrapper",
64 |
65 | // https://github.com/j2css/j2c
66 | j2c: expectAdjacentSibling(['inline', 'sheet']),
67 |
68 | // var styles = StyleSheet.create({color: 'red'})
69 | 'react-inline': isStyleSheetCreate,
70 | 'react-style': isStyleSheetCreate,
71 |
72 | // import reactCSS from 'reactcss'
73 | reactcss: true,
74 |
75 | // const StyledButton = injectSheet(styles)(Button)
76 | 'react-jss': true,
77 |
78 | // import styled from 'styled-components';
79 | 'styled-components': true,
80 |
81 | // import {withStyle} from "styletron-react";
82 | 'styletron-react': expectAdjacentSibling(['withStyle']),
83 |
84 | styling: true,
85 |
86 | // const rule = superstyle({ color: 'blue' })
87 | superstyle: true,
88 |
89 | // import { makeStyles } from '@material-ui/styles'
90 | styles: expectAdjacentSibling(['makeStyles']),
91 | };
92 |
93 | const plugins = [
94 | 'jsx',
95 | 'typescript',
96 | 'objectRestSpread',
97 | ['decorators', { decoratorsBeforeExport: false }],
98 | 'classProperties',
99 | 'exportExtensions',
100 | 'asyncGenerators',
101 | 'functionBind',
102 | 'functionSent',
103 | 'dynamicImport',
104 | 'optionalCatchBinding',
105 | ];
106 |
107 | function expectAdjacentSibling(names) {
108 | return (i, nameSpace) => names.some((name) => nameSpace[i + 1] === name);
109 | }
110 |
111 | function loadBabelOpts(opts) {
112 | const filename = opts.from && opts.from.replace(/\?.*$/, '');
113 |
114 | opts = {
115 | filename,
116 | parserOpts: {
117 | plugins,
118 | sourceFilename: filename,
119 | sourceType: filename && /\.m[tj]sx?$/.test(filename) ? 'module' : 'unambiguous',
120 | allowImportExportEverywhere: true,
121 | allowAwaitOutsideFunction: true,
122 | allowReturnOutsideFunction: true,
123 | allowSuperOutsideMethod: true,
124 | },
125 | };
126 | let fileOpts;
127 |
128 | try {
129 | fileOpts =
130 | filename &&
131 | loadOptions({
132 | filename,
133 | });
134 | } catch (ex) {
135 | //
136 | }
137 |
138 | for (const key in fileOpts) {
139 | if (Array.isArray(fileOpts[key]) && !fileOpts[key].length) {
140 | continue;
141 | }
142 |
143 | opts[key] = fileOpts[key];
144 |
145 | if (Array.isArray(fileOpts[key]) && Array.isArray(opts.parserOpts[key])) {
146 | // combine arrays for plugins
147 | // plugins in fileOpts could be string, array or object
148 | for (const plugin of fileOpts[key]) {
149 | const option =
150 | Array.isArray(plugin) || typeof plugin === 'string'
151 | ? plugin
152 | : [plugin.key, plugin.options];
153 |
154 | opts.parserOpts[key] = [...opts.parserOpts[key], option];
155 | }
156 | } else {
157 | // because some options need to be passed to parser also
158 | opts.parserOpts[key] = fileOpts[key];
159 | }
160 | }
161 |
162 | // avoid conflicting with the legacy decorators plugin
163 | if (opts.plugins && opts.plugins.some((p) => p.key === 'proposal-decorators')) {
164 | const index = opts.parserOpts.plugins.findIndex(
165 | (p) => Array.isArray(p) && p[0] === 'decorators',
166 | );
167 |
168 | if (index > -1) {
169 | opts.parserOpts.plugins.splice(index, 1);
170 | }
171 | }
172 |
173 | return opts;
174 | }
175 |
176 | function literalParser(source, opts, styles) {
177 | let ast;
178 |
179 | try {
180 | ast = parse(source, loadBabelOpts(opts));
181 | } catch (ex) {
182 | // console.error(ex);
183 | return styles || [];
184 | }
185 |
186 | const specifiers = new Map();
187 | const variableDeclarator = new Map();
188 | const objLiteral = new Set();
189 | const tplLiteral = new Set();
190 | const tplCallee = new Set();
191 | const jobs = [];
192 |
193 | function addObjectJob(path) {
194 | jobs.push(() => {
195 | addObjectValue(path);
196 | });
197 | }
198 |
199 | function addObjectValue(path) {
200 | if (path.isIdentifier()) {
201 | const identifier = path.scope.getBindingIdentifier(path.node.name);
202 |
203 | if (identifier) {
204 | path = variableDeclarator.get(identifier);
205 |
206 | if (path) {
207 | variableDeclarator.delete(identifier);
208 | path.forEach(addObjectExpression);
209 | }
210 | }
211 | } else {
212 | addObjectExpression(path);
213 | }
214 | }
215 |
216 | function addObjectExpression(path) {
217 | if (path.isObjectExpression()) {
218 | path.get('properties').forEach((prop) => {
219 | if (prop.isSpreadElement()) {
220 | addObjectValue(prop.get('argument'));
221 | }
222 | });
223 | objLiteral.add(path.node);
224 |
225 | return path;
226 | }
227 |
228 | // If this is not an object but a function returning an object, we want to parse the
229 | // object that is in the body of the function. We will only parse it if the body only
230 | // consist of an object and nothing else.
231 | if (path.isArrowFunctionExpression()) {
232 | const body = path.get('body');
233 |
234 | if (body) {
235 | addObjectExpression(body);
236 | }
237 | }
238 | }
239 |
240 | function setSpecifier(id, nameSpace) {
241 | nameSpace.unshift(
242 | ...nameSpace
243 | .shift()
244 | .replace(/^\W+/, '')
245 | .split(/[/\\]+/g),
246 | );
247 |
248 | if (types.isIdentifier(id)) {
249 | specifiers.set(id.name, nameSpace);
250 | specifiers.set(id, nameSpace);
251 | } else if (types.isObjectPattern(id)) {
252 | id.properties.forEach((property) => {
253 | if (types.isObjectProperty(property)) {
254 | const key = property.key;
255 |
256 | nameSpace = nameSpace.concat(key.name || key.value);
257 | id = property.value;
258 | } else {
259 | id = property.argument;
260 | }
261 |
262 | setSpecifier(id, nameSpace);
263 | });
264 | } else if (types.isArrayPattern(id)) {
265 | id.elements.forEach((element, i) => {
266 | setSpecifier(element, nameSpace.concat(String(i)));
267 | });
268 | }
269 | }
270 |
271 | function getNameSpace(path, nameSpace) {
272 | let node = path.node;
273 |
274 | if (path.isIdentifier() || path.isJSXIdentifier()) {
275 | node = path.scope.getBindingIdentifier(node.name) || node;
276 | const specifier = specifiers.get(node) || specifiers.get(node.name);
277 |
278 | if (specifier) {
279 | nameSpace.unshift(...specifier);
280 | } else {
281 | nameSpace.unshift(node.name);
282 | }
283 | } else {
284 | ['name', 'property', 'object', 'callee'].forEach((prop) => {
285 | node[prop] && getNameSpace(path.get(prop), nameSpace);
286 | });
287 | }
288 |
289 | return nameSpace;
290 | }
291 |
292 | function isStylePath(path) {
293 | return getNameSpace(path, []).some(function (name, ...args) {
294 | const result =
295 | name &&
296 | ((Object.prototype.hasOwnProperty.call(supports, name) && supports[name]) ||
297 | (Object.prototype.hasOwnProperty.call(opts.syntax.config, name) &&
298 | opts.syntax.config[name]));
299 |
300 | switch (typeof result) {
301 | case 'function': {
302 | return result.apply(this, args);
303 | }
304 | case 'boolean': {
305 | return result;
306 | }
307 | default: {
308 | return undefined;
309 | }
310 | }
311 | });
312 | }
313 |
314 | const visitor = {
315 | ImportDeclaration: (path) => {
316 | const moduleId = path.node.source.value;
317 |
318 | path.node.specifiers.forEach((specifier) => {
319 | const nameSpace = [moduleId];
320 |
321 | if (specifier.imported) {
322 | nameSpace.push(specifier.imported.name);
323 | }
324 |
325 | setSpecifier(specifier.local, nameSpace);
326 | });
327 | },
328 | JSXAttribute: (path) => {
329 | if (/^(?:css|style)$/.test(path.node.name.name)) {
330 | addObjectJob(path.get('value.expression'));
331 | }
332 | },
333 | VariableDeclarator: (path) => {
334 | variableDeclarator.set(path.node.id, path.node.init ? [path.get('init')] : []);
335 | },
336 | AssignmentExpression: (path) => {
337 | if (types.isIdentifier(path.node.left) && types.isObjectExpression(path.node.right)) {
338 | const identifier = path.scope.getBindingIdentifier(path.node.left.name);
339 | const variable = variableDeclarator.get(identifier);
340 | const valuePath = path.get('right');
341 |
342 | if (variable) {
343 | variable.push(valuePath);
344 | } else {
345 | variableDeclarator.set(identifier, [valuePath]);
346 | }
347 | }
348 | },
349 | CallExpression: (path) => {
350 | const callee = path.node.callee;
351 |
352 | if (
353 | types.isIdentifier(callee, { name: 'require' }) &&
354 | !path.scope.getBindingIdentifier(callee.name)
355 | ) {
356 | path.node.arguments.filter(types.isStringLiteral).forEach((arg) => {
357 | const moduleId = arg.value;
358 | const nameSpace = [moduleId];
359 | let currPath = path;
360 |
361 | do {
362 | let id = currPath.parent.id;
363 |
364 | if (!id) {
365 | id = currPath.parent.left;
366 |
367 | if (id) {
368 | id = path.scope.getBindingIdentifier(id.name) || id;
369 | } else {
370 | if (types.isIdentifier(currPath.parent.property)) {
371 | nameSpace.push(currPath.parent.property.name);
372 | }
373 |
374 | currPath = currPath.parentPath;
375 | continue;
376 | }
377 | }
378 |
379 | setSpecifier(id, nameSpace);
380 | break;
381 | } while (currPath);
382 | });
383 | } else if (!tplCallee.has(callee) && isStylePath(path.get('callee'))) {
384 | path.get('arguments').forEach((arg) => {
385 | addObjectJob(arg.isFunction() ? arg.get('body') : arg);
386 | });
387 | }
388 | },
389 | TaggedTemplateExpression: (path) => {
390 | if (isStylePath(path.get('tag'))) {
391 | tplLiteral.add(path.node.quasi);
392 |
393 | if (path.node.tag.callee) {
394 | tplCallee.add(path.node.tag.callee);
395 | }
396 | }
397 | },
398 | };
399 |
400 | traverse(ast, visitor);
401 | jobs.forEach((job) => job());
402 |
403 | const objLiteralStyles = Array.from(objLiteral).map((endNode) => {
404 | const objectSyntax = require('./object-syntax');
405 | let startNode = endNode;
406 |
407 | if (startNode.leadingComments && startNode.leadingComments.length) {
408 | startNode = startNode.leadingComments[0];
409 | }
410 |
411 | let startIndex = startNode.start;
412 | const before = source.slice(startNode.start - startNode.loc.start.column, startNode.start);
413 |
414 | if (/^\s+$/.test(before)) {
415 | startIndex -= before.length;
416 | }
417 |
418 | return {
419 | startIndex,
420 | endIndex: endNode.end,
421 | skipConvert: true,
422 | content: source,
423 | opts: {
424 | node: endNode,
425 | },
426 | syntax: objectSyntax,
427 | lang: 'object-literal',
428 | };
429 | });
430 |
431 | const tplLiteralStyles = [];
432 |
433 | Array.from(tplLiteral).forEach((node) => {
434 | if (
435 | objLiteralStyles.some((style) => style.startIndex <= node.end && node.start < style.endIndex)
436 | ) {
437 | return;
438 | }
439 |
440 | const quasis = node.quasis.map((quasiNode) => ({
441 | start: quasiNode.start,
442 | end: quasiNode.end,
443 | }));
444 | const style = {
445 | startIndex: quasis[0].start,
446 | endIndex: quasis[quasis.length - 1].end,
447 | content: getTemplate(node, source),
448 | };
449 |
450 | if (node.expressions.length) {
451 | const expressions = node.expressions.map((expressionNode) => ({
452 | start: expressionNode.start,
453 | end: expressionNode.end,
454 | }));
455 |
456 | style.syntax = loadSyntax(opts, __dirname);
457 | style.lang = 'template-literal';
458 | style.opts = {
459 | quasis,
460 | expressions,
461 | };
462 | } else {
463 | style.lang = 'css';
464 | }
465 |
466 | let parent = null;
467 | let targetStyles = tplLiteralStyles;
468 |
469 | while (targetStyles) {
470 | const target = targetStyles.find(
471 | (targetStyle) =>
472 | targetStyle.opts &&
473 | targetStyle.opts.expressions.some(
474 | (expr) => expr.start <= style.startIndex && style.endIndex < expr.end,
475 | ),
476 | );
477 |
478 | if (target) {
479 | parent = target;
480 | targetStyles = target.opts.templateLiteralStyles;
481 | } else {
482 | break;
483 | }
484 | }
485 |
486 | if (parent) {
487 | const templateLiteralStyles =
488 | parent.opts.templateLiteralStyles || (parent.opts.templateLiteralStyles = []);
489 |
490 | templateLiteralStyles.push(style);
491 | } else {
492 | tplLiteralStyles.push(style);
493 | }
494 | });
495 |
496 | return (styles || []).concat(objLiteralStyles).concat(tplLiteralStyles);
497 | }
498 |
499 | module.exports = literalParser;
500 |
--------------------------------------------------------------------------------
/get-template.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function getTemplate(node, source) {
4 | return source.slice(node.quasis[0].start, node.quasis[node.quasis.length - 1].end);
5 | }
6 |
7 | module.exports = getTemplate;
8 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const extract = require('./extract');
4 | const syntax = require('postcss-syntax/syntax')(extract, 'jsx');
5 |
6 | module.exports = syntax;
7 |
--------------------------------------------------------------------------------
/literal.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Container = require('postcss/lib/container');
4 |
5 | /**
6 | * Represents a JS literal
7 | *
8 | * @extends Container
9 | *
10 | * @example
11 | * const root = postcss.parse('{}');
12 | * const literal = root.first;
13 | * literal.type //=> 'literal'
14 | * literal.toString() //=> 'a{}'
15 | */
16 | class Literal extends Container {
17 | constructor(defaults) {
18 | super(defaults);
19 | this.type = 'literal';
20 | }
21 | }
22 |
23 | module.exports = Literal;
24 |
--------------------------------------------------------------------------------
/object-parse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Input = require('postcss/lib/input');
4 | const ObjectParser = require('./object-parser');
5 |
6 | function objectParse(source, opts) {
7 | const input = new Input(source, opts);
8 | const parser = new ObjectParser(input);
9 |
10 | parser.parse(opts.node);
11 |
12 | return parser.root;
13 | }
14 |
15 | module.exports = objectParse;
16 |
--------------------------------------------------------------------------------
/object-parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const camelCase = require('./camel-case');
4 | const getTemplate = require('./get-template');
5 | const Literal = require('./literal');
6 | const ObjectLiteral = require('./object');
7 | const postcss = require('postcss');
8 | const unCamelCase = require('./un-camel-case');
9 |
10 | function forEach(arr, callback) {
11 | arr && arr.forEach(callback);
12 | }
13 |
14 | function defineRaws(node, prop, prefix, suffix, props) {
15 | if (!props) {
16 | props = {};
17 | }
18 |
19 | const descriptor = {
20 | enumerable: true,
21 | get: () => node[prop],
22 | set: (value) => {
23 | node[prop] = value;
24 | },
25 | };
26 |
27 | if (!props.raw) {
28 | props.raw = descriptor;
29 | } else if (props.raw === 'camel') {
30 | props.raw = {
31 | enumerable: true,
32 | get: () => camelCase(node[prop]),
33 | set: (value) => {
34 | node[prop] = unCamelCase(value);
35 | },
36 | };
37 | }
38 |
39 | props.value = descriptor;
40 |
41 | node.raws[prop] = Object.defineProperties(
42 | {
43 | prefix,
44 | suffix,
45 | },
46 | props,
47 | );
48 | }
49 |
50 | class objectParser {
51 | constructor(input) {
52 | this.input = input;
53 | }
54 | parse(node) {
55 | const root = postcss.root({
56 | source: {
57 | input: this.input,
58 | start: node.loc.start,
59 | },
60 | });
61 |
62 | root.raws.node = node;
63 | const obj = new ObjectLiteral({
64 | raws: {
65 | node,
66 | },
67 | });
68 |
69 | root.push(obj);
70 | this.process(node, obj);
71 | this.sort(root);
72 | this.raws(root);
73 |
74 | const startNode = root.first.raws.node;
75 | const endNode = root.last.raws.node;
76 |
77 | const start = {
78 | line: startNode.loc.start.line,
79 | };
80 |
81 | let before = root.source.input.css.slice(
82 | startNode.start - startNode.loc.start.column,
83 | startNode.start,
84 | );
85 |
86 | if (/^\s+$/.test(before)) {
87 | start.column = 1;
88 | } else {
89 | before = '';
90 | start.column = startNode.loc.start.column;
91 | }
92 |
93 | root.first.raws.before = before;
94 | root.source.input.css = before + root.source.input.css.slice(startNode.start, endNode.end);
95 | root.source.start = start;
96 |
97 | this.root = root;
98 | }
99 |
100 | process(node, parent) {
101 | ['leadingComments', 'innerComments', 'trailingComments'].forEach((prop) => {
102 | forEach(node[prop], (child) => {
103 | this.source(child, this.comment(child, parent));
104 | });
105 | });
106 |
107 | const child = (this[node.type] || this.literal).apply(this, [node, parent]);
108 |
109 | this.source(node, child);
110 |
111 | return child;
112 | }
113 | source(node, parent) {
114 | parent.source = {
115 | input: this.input,
116 | start: node.loc.start,
117 | end: node.loc.end,
118 | };
119 |
120 | return parent;
121 | }
122 | raws(parent, node) {
123 | const source = this.input.css;
124 |
125 | parent.nodes.forEach((child, i) => {
126 | if (i) {
127 | child.raws.before = source
128 | .slice(parent.nodes[i - 1].raws.node.end, child.raws.node.start)
129 | .replace(/^\s*,+/, '');
130 | } else if (node) {
131 | child.raws.before = source.slice(node.start, child.raws.node.start).replace(/^\s*\{+/, '');
132 | }
133 | });
134 |
135 | if (node) {
136 | let semicolon;
137 | let after;
138 |
139 | if (parent.nodes.length) {
140 | after = source.slice(parent.last.raws.node.end, node.end).replace(/^\s*,+/, () => {
141 | semicolon = true;
142 |
143 | return '';
144 | });
145 | } else {
146 | after = source.slice(node.start, node.end).replace(/^\s*\{/, '');
147 | }
148 |
149 | parent.raws.after = after.replace(/\}+\s*$/, '');
150 | parent.raws.semicolon = semicolon || false;
151 | }
152 | }
153 |
154 | sort(node) {
155 | node.nodes = node.nodes.sort((a, b) => a.raws.node.start - b.raws.node.start);
156 | }
157 |
158 | getNodeValue(node, wrappedValue) {
159 | const source = this.input.css;
160 | let rawValue;
161 | let cookedValue;
162 |
163 | switch (node.type) {
164 | case 'Identifier': {
165 | const isCssFloat = node.name === 'cssFloat';
166 |
167 | return {
168 | prefix: '',
169 | suffix: '',
170 | raw: isCssFloat && node.name,
171 | value: isCssFloat ? 'float' : node.name,
172 | };
173 | }
174 | case 'StringLiteral': {
175 | rawValue = node.extra.raw.slice(1, -1);
176 | cookedValue = node.value;
177 | break;
178 | }
179 | case 'TemplateLiteral': {
180 | rawValue = getTemplate(node, source);
181 | break;
182 | }
183 | default: {
184 | rawValue = source.slice(node.start, node.end);
185 | break;
186 | }
187 | }
188 |
189 | const valueWrap = wrappedValue.split(rawValue);
190 |
191 | return {
192 | prefix: valueWrap[0],
193 | suffix: valueWrap[1],
194 | value: cookedValue || rawValue,
195 | };
196 | }
197 |
198 | ObjectExpression(node, parent) {
199 | forEach(node.properties, (child) => {
200 | this.process(child, parent);
201 | });
202 | this.sort(parent);
203 | this.raws(parent, node);
204 |
205 | return parent;
206 | }
207 |
208 | ObjectProperty(node, parent) {
209 | const source = this.input.css;
210 | let between = source.indexOf(':', node.key.end);
211 | const rawKey = source.slice(node.start, between).trimRight();
212 | const rawValue = source.slice(between + 1, node.end).trimLeft();
213 |
214 | between = source.slice(node.start + rawKey.length, node.end - rawValue.length);
215 | const key = this.getNodeValue(node.key, rawKey);
216 |
217 | if (node.value.type === 'ObjectExpression') {
218 | let rule;
219 |
220 | // eslint-disable-next-line regexp/no-unused-capturing-group, regexp/no-super-linear-backtracking -- TODO: fix
221 | if (/^@(\S+)(\s*)(.*)$/.test(key.value)) {
222 | const name = RegExp.$1; // eslint-disable-line regexp/no-legacy-features -- TODO: fix
223 | const afterName = RegExp.$2; // eslint-disable-line regexp/no-legacy-features -- TODO: fix
224 | const params = RegExp.$3; // eslint-disable-line regexp/no-legacy-features -- TODO: fix
225 | const atRule = postcss.atRule({
226 | name: unCamelCase(name),
227 | raws: {
228 | afterName,
229 | },
230 | nodes: [],
231 | });
232 |
233 | defineRaws(atRule, 'name', `${key.prefix}@`, params ? '' : key.suffix, {
234 | raw: 'camel',
235 | });
236 |
237 | if (params) {
238 | atRule.params = params;
239 | defineRaws(atRule, 'params', '', key.suffix);
240 | }
241 |
242 | rule = atRule;
243 | } else {
244 | // rule = this.rule(key, keyWrap, node.value, parent);
245 | rule = postcss.rule({
246 | selector: key.value,
247 | });
248 | defineRaws(rule, 'selector', key.prefix, key.suffix);
249 | }
250 |
251 | raw(rule);
252 | this.ObjectExpression(node.value, rule);
253 |
254 | return rule;
255 | }
256 |
257 | const value = this.getNodeValue(node.value, rawValue);
258 |
259 | if (key.value[0] === '@') {
260 | const atRule = postcss.atRule({
261 | name: unCamelCase(key.value),
262 | params: value.value,
263 | });
264 |
265 | defineRaws(atRule, 'name', key.prefix, key.suffix, {
266 | raw: 'camel',
267 | });
268 |
269 | defineRaws(atRule, 'params', value.prefix, value.suffix);
270 | raw(atRule);
271 |
272 | return atRule;
273 | }
274 |
275 | let decl;
276 |
277 | if (key.raw) {
278 | decl = postcss.decl({
279 | prop: key.value,
280 | value: value.value,
281 | raws: {
282 | prop: key,
283 | },
284 | });
285 | } else {
286 | decl = postcss.decl({
287 | prop: unCamelCase(key.value),
288 | value: value.value,
289 | });
290 |
291 | defineRaws(decl, 'prop', key.prefix, key.suffix, {
292 | raw: 'camel',
293 | });
294 | }
295 |
296 | defineRaws(decl, 'value', value.prefix, value.suffix);
297 | raw(decl);
298 |
299 | return decl;
300 |
301 | function raw(postcssNode) {
302 | postcssNode.raws.between = between;
303 | postcssNode.raws.node = node;
304 | parent.push(postcssNode);
305 | }
306 | }
307 |
308 | literal(node, parent) {
309 | const literal = new Literal({
310 | text: this.input.css.slice(node.start, node.end),
311 | raws: {
312 | node,
313 | },
314 | });
315 |
316 | parent.push(literal);
317 |
318 | return literal;
319 | }
320 |
321 | comment(node, parent) {
322 | if (
323 | !parent.nodes ||
324 | (node.start < parent.raws.node.start && parent.type !== 'root' && parent.parent)
325 | ) {
326 | return this.comment(node, parent.parent);
327 | }
328 |
329 | // eslint-disable-next-line regexp/no-super-linear-backtracking -- TODO: fix
330 | const text = node.value.match(/^(\s*)((?:\S[\s\S]*?)?)(\s*)$/);
331 | const comment = postcss.comment({
332 | text: text[2],
333 | raws: {
334 | node,
335 | left: text[1],
336 | right: text[3],
337 | inline: node.type === 'CommentLine',
338 | },
339 | });
340 |
341 | parent.push(comment);
342 |
343 | return comment;
344 | }
345 | }
346 | module.exports = objectParser;
347 |
--------------------------------------------------------------------------------
/object-stringifier.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const camelCase = require('./camel-case');
4 | const Stringifier = require('postcss/lib/stringifier');
5 |
6 | class ObjectStringifier extends Stringifier {
7 | object(node) {
8 | this.builder('{', node, 'start');
9 |
10 | let after;
11 |
12 | if (node.nodes && node.nodes.length) {
13 | this.body(node);
14 | after = this.raw(node, 'after');
15 | } else {
16 | after = this.raw(node, 'after', 'emptyBody');
17 | }
18 |
19 | if (after) this.builder(after);
20 |
21 | this.builder('}', node, 'end');
22 | }
23 | literal(node, semicolon) {
24 | this.builder(node.text + (semicolon ? ',' : ''), node);
25 | }
26 | decl(node, semicolon) {
27 | let prop = this.rawValue(node, 'prop');
28 |
29 | let string = prop;
30 |
31 | const isObjectShorthand = node.raws.node && node.raws.node.shorthand;
32 |
33 | if (!isObjectShorthand) {
34 | const between = this.raw(node, 'between', 'colon');
35 | const value = this.rawValue(node, 'value');
36 |
37 | string += between + value;
38 | }
39 |
40 | if (semicolon) string += ',';
41 |
42 | this.builder(string, node);
43 | }
44 | rule(node, semicolon) {
45 | this.block(node, this.rawValue(node, 'selector'), semicolon);
46 | }
47 | atrule(node, semicolon) {
48 | const name = this.rawValue(node, 'name');
49 | const params = this.rawValue(node, 'params');
50 |
51 | if (node.nodes) {
52 | let string;
53 |
54 | if (params) {
55 | const afterName = this.raw(node, 'afterName');
56 |
57 | string = name + afterName + params;
58 | } else {
59 | string = name;
60 | }
61 |
62 | this.block(node, string, semicolon);
63 | } else {
64 | const between = this.raw(node, 'between', 'colon');
65 | let string = name + between + params;
66 |
67 | if (semicolon) string += ',';
68 |
69 | this.builder(string, node);
70 | }
71 | }
72 | block(node, start, semicolon) {
73 | super.block(node, start);
74 |
75 | if (semicolon) {
76 | this.builder(',', node);
77 | }
78 | }
79 | comment(node) {
80 | const left = this.raw(node, 'left', 'commentLeft');
81 | const right = this.raw(node, 'right', 'commentRight');
82 |
83 | if (node.raws.inline) {
84 | const text = node.raws.text || node.text;
85 |
86 | this.builder(`//${left}${text}${right}`, node);
87 | } else {
88 | this.builder(`/*${left}${node.text}${right}*/`, node);
89 | }
90 | }
91 | raw(node, own, detect) {
92 | let value = super.raw(node, own, detect);
93 |
94 | if (
95 | (own === 'between' || (own === 'afterName' && node.type === 'atrule' && !node.nodes)) &&
96 | !/:/.test(value)
97 | ) {
98 | value = `:${value}`;
99 | } else if (own === 'before' && /^(?:decl|rule)$/.test(node.type)) {
100 | value = value.replace(/\S+$/, '');
101 | }
102 |
103 | return value;
104 | }
105 | rawValue(node, prop) {
106 | const raw = node.raws[prop];
107 |
108 | if (raw) {
109 | const descriptor = Object.getOwnPropertyDescriptor(raw, 'raw');
110 |
111 | if (descriptor && descriptor.get) {
112 | return raw.prefix + raw.raw + raw.suffix;
113 | }
114 | }
115 |
116 | let value = super.rawValue(node, prop);
117 |
118 | if (value === null || value === undefined) {
119 | return value;
120 | }
121 |
122 | if (/^(?:prop|selector)$/i.test(prop)) {
123 | value = camelCase(value);
124 |
125 | // eslint-disable-next-line regexp/no-unused-capturing-group -- TODO: fix
126 | if (node.raws.before && /(\S+)$/.test(node.raws.before)) {
127 | value = RegExp.$1 + value; // eslint-disable-line regexp/no-legacy-features -- TODO: fix
128 | } else if (value && !/\W/.test(value)) {
129 | return value;
130 | }
131 | } else if (node.type === 'atrule') {
132 | if (prop === 'name') {
133 | value = `@${value}`;
134 | } else if (node.nodes) {
135 | return;
136 | }
137 |
138 | if (node.nodes) {
139 | value += this.raw(node, 'afterName');
140 | value += super.rawValue(node, 'params');
141 | }
142 | }
143 |
144 | value = JSON.stringify(value);
145 |
146 | return value;
147 | }
148 | }
149 |
150 | module.exports = ObjectStringifier;
151 |
--------------------------------------------------------------------------------
/object-stringify.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ObjectStringifier = require('./object-stringifier');
4 |
5 | module.exports = function objectStringify(node, builder) {
6 | const str = new ObjectStringifier(builder);
7 |
8 | str.stringify(node);
9 | };
10 |
--------------------------------------------------------------------------------
/object-syntax.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const parse = require('./object-parse');
4 | const stringify = require('./object-stringify');
5 |
6 | const syntax = {
7 | parse,
8 | stringify,
9 | };
10 |
11 | module.exports = syntax;
12 |
--------------------------------------------------------------------------------
/object.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Container = require('postcss/lib/container');
4 |
5 | /**
6 | * Represents a JS Object Literal
7 | *
8 | * @extends Container
9 | *
10 | * @example
11 | * const root = postcss.parse('{}');
12 | * const obj = root.first;
13 | * obj.type //=> 'object'
14 | * obj.toString() //=> '{}'
15 | */
16 | class ObjectLiteral extends Container {
17 | constructor(defaults) {
18 | super(defaults);
19 | this.type = 'object';
20 | this.nodes = [];
21 | }
22 | }
23 |
24 | module.exports = ObjectLiteral;
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@stylelint/postcss-css-in-js",
3 | "version": "0.38.0",
4 | "description": "PostCSS syntax for parsing CSS in JS literals",
5 | "keywords": [
6 | "postcss",
7 | "syntax",
8 | "emotion",
9 | "aphrodite",
10 | "glamor",
11 | "glamorous",
12 | "react-native",
13 | "react-style",
14 | "reactcss",
15 | "styled-components",
16 | "styletron-react",
17 | "typestyle",
18 | "css-in-js",
19 | "css"
20 | ],
21 | "repository": "stylelint/postcss-css-in-js",
22 | "license": "MIT",
23 | "author": "gucong3000",
24 | "main": "index.js",
25 | "files": [
26 | "*.js"
27 | ],
28 | "scripts": {
29 | "format": "prettier . --write",
30 | "lint": "npm-run-all --parallel lint:*",
31 | "lint:formatting": "prettier . --check",
32 | "lint:js": "eslint . --cache --max-warnings=0",
33 | "lint:md": "remark . --quiet --frail",
34 | "prepare": "husky install",
35 | "release": "np",
36 | "test": "jest",
37 | "watch": "jest --watch"
38 | },
39 | "husky": {
40 | "hooks": {
41 | "pre-commit": "lint-staged"
42 | }
43 | },
44 | "lint-staged": {
45 | "*.js": "eslint --cache --fix",
46 | "*.{js,md,yml}": "prettier --write"
47 | },
48 | "prettier": "@stylelint/prettier-config",
49 | "eslintConfig": {
50 | "extends": [
51 | "stylelint"
52 | ],
53 | "globals": {
54 | "__dirname": true,
55 | "module": true,
56 | "require": true
57 | },
58 | "reportUnusedDisableDirectives": true,
59 | "root": true
60 | },
61 | "remarkConfig": {
62 | "plugins": [
63 | "@stylelint/remark-preset"
64 | ]
65 | },
66 | "jest": {
67 | "collectCoverage": true,
68 | "collectCoverageFrom": [
69 | "**/*.js",
70 | "!coverage/**",
71 | "!test{,s}/**",
72 | "!**/.{prettier,eslint,mocha}rc.{js,cjs}"
73 | ],
74 | "testMatch": [
75 | "**/test/*.js",
76 | "**/test/**/*.test.js"
77 | ]
78 | },
79 | "dependencies": {
80 | "@babel/core": "^7.17.10"
81 | },
82 | "devDependencies": {
83 | "@babel/plugin-proposal-decorators": "^7.17.9",
84 | "@stylelint/prettier-config": "^2.0.0",
85 | "@stylelint/remark-preset": "^3.0.0",
86 | "autoprefixer": "^9.8.6",
87 | "codecov": "^3.8.3",
88 | "eslint": "^8.15.0",
89 | "eslint-config-prettier": "^8.3.0",
90 | "eslint-config-stylelint": "^15.1.0",
91 | "husky": "^8.0.0",
92 | "jest": "^28.1.0",
93 | "json5": "^2.2.0",
94 | "lint-staged": "^12.4.1",
95 | "np": "^7.6.1",
96 | "npm-run-all": "^4.1.5",
97 | "postcss": ">=7.0.32",
98 | "postcss-parser-tests": "^6.5.0",
99 | "postcss-safe-parser": "^4.0.2",
100 | "postcss-syntax": ">=0.36.2",
101 | "prettier": "^2.6.2",
102 | "remark-cli": "^10.0.1"
103 | },
104 | "peerDependencies": {
105 | "postcss": ">=7.0.0",
106 | "postcss-syntax": ">=0.36.2"
107 | },
108 | "engines": {
109 | "node": ">=12.0.0"
110 | },
111 | "publishConfig": {
112 | "access": "public"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/template-parse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Input = require('postcss/lib/input');
4 | const TemplateParser = require('./template-parser');
5 |
6 | function templateParse(css, opts) {
7 | const input = new Input(css, opts);
8 |
9 | input.quasis = opts.quasis;
10 | input.templateLiteralStyles = opts.templateLiteralStyles;
11 | input.parseOptions = opts;
12 | const parser = new TemplateParser(input);
13 |
14 | parser.parse();
15 |
16 | return parser.root;
17 | }
18 |
19 | module.exports = templateParse;
20 |
--------------------------------------------------------------------------------
/template-parser-helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Literal = require('./literal');
4 | const postcssParse = require('postcss/lib/parse');
5 |
6 | // eslint-disable-next-line regexp/no-useless-non-capturing-group, regexp/no-useless-flag -- TODO: fix
7 | const reNewLine = /(?:\r?\n|\r)/gm;
8 | const isLiteral = (token) => token[0] === 'word' && /^\$\{[\s\S]*\}$/.test(token[1]);
9 |
10 | function literal(start) {
11 | if (!isLiteral(start)) {
12 | return;
13 | }
14 |
15 | const tokens = [];
16 | let hasWord;
17 | let type;
18 | let token;
19 |
20 | while ((token = this.tokenizer.nextToken())) {
21 | tokens.push(token);
22 | type = token[0];
23 |
24 | if (type.length === 1) {
25 | break;
26 | } else if (type === 'word') {
27 | hasWord = true;
28 | }
29 | }
30 |
31 | while (tokens.length) {
32 | this.tokenizer.back(tokens.pop());
33 | }
34 |
35 | if (type === '{' || (type === ':' && !hasWord)) {
36 | return;
37 | }
38 |
39 | const node = new Literal({
40 | text: start[1],
41 | });
42 |
43 | this.init(node, start[2], start[3]);
44 |
45 | const input = this.input;
46 |
47 | if (input.templateLiteralStyles) {
48 | const offset = input.quasis[0].start;
49 | const nodeIndex = getNodeIndex(node, input);
50 | const startIndex = offset + nodeIndex;
51 | const endIndex = startIndex + node.text.length;
52 | const templateLiteralStyles = input.templateLiteralStyles.filter(
53 | (style) => style.startIndex <= endIndex && startIndex < style.endIndex,
54 | );
55 |
56 | if (templateLiteralStyles.length) {
57 | const nodes = parseTemplateLiteralStyles(templateLiteralStyles, input, [
58 | nodeIndex,
59 | nodeIndex + node.text.length,
60 | ]);
61 |
62 | if (nodes.length) {
63 | node.nodes = nodes;
64 | nodes.forEach((n) => (n.parent = node));
65 | }
66 | }
67 | }
68 |
69 | return node;
70 | }
71 |
72 | function freeSemicolon(token) {
73 | this.spaces += token[1];
74 | const nodes = this.current.nodes;
75 | const prev = nodes && nodes[nodes.length - 1];
76 |
77 | if (prev && /^(?:rule|literal)$/.test(prev.type) && !prev.raws.ownSemicolon) {
78 | prev.raws.ownSemicolon = this.spaces;
79 | this.spaces = '';
80 | }
81 | }
82 |
83 | module.exports = {
84 | freeSemicolon,
85 | literal,
86 | };
87 |
88 | function parseTemplateLiteralStyles(styles, input, range) {
89 | const offset = input.quasis[0].start;
90 | const source = input.css;
91 |
92 | const opts = { ...input.parseOptions };
93 |
94 | delete opts.templateLiteralStyles;
95 | delete opts.expressions;
96 | delete opts.quasis;
97 |
98 | const parseStyle = docFixer(offset, source, opts);
99 |
100 | const nodes = [];
101 | let index = range[0];
102 |
103 | styles
104 | .sort((a, b) => a.startIndex - b.startIndex)
105 | .forEach((style) => {
106 | const root = parseStyle(style);
107 |
108 | if (!root || !root.nodes.length) {
109 | return;
110 | }
111 |
112 | root.raws.beforeStart = source.slice(index, style.startIndex - offset);
113 | root.raws.afterEnd = '';
114 |
115 | if (style.endIndex) {
116 | index = style.endIndex - offset;
117 | } else {
118 | index = style.startIndex - offset + (style.content || root.source.input.css).length;
119 | }
120 |
121 | nodes.push(root);
122 | });
123 |
124 | if (nodes.length) {
125 | nodes[nodes.length - 1].raws.afterEnd = source.slice(index, range[1]);
126 | }
127 |
128 | return nodes;
129 | }
130 |
131 | class LocalFixer {
132 | constructor(offset, lines, style, templateParse) {
133 | const startIndex = style.startIndex - offset;
134 | let line = 0;
135 | let column = startIndex;
136 |
137 | lines.some((lineEndIndex, lineNumber) => {
138 | if (lineEndIndex >= startIndex) {
139 | line = lineNumber--;
140 |
141 | if (lineNumber in lines) {
142 | column = startIndex - lines[lineNumber] - 1;
143 | }
144 |
145 | return true;
146 | }
147 |
148 | return false;
149 | });
150 |
151 | this.line = line;
152 | this.column = column;
153 | this.style = style;
154 | this.templateParse = templateParse;
155 | }
156 | object(object) {
157 | if (object) {
158 | if (object.line === 1) {
159 | object.column += this.column;
160 | }
161 |
162 | object.line += this.line;
163 | }
164 | }
165 | node(node) {
166 | this.object(node.source.start);
167 | this.object(node.source.end);
168 | }
169 | root(root) {
170 | this.node(root);
171 | root.walk((node) => {
172 | this.node(node);
173 | });
174 | }
175 | error(error) {
176 | if (error && error.name === 'CssSyntaxError') {
177 | this.object(error);
178 | this.object(error.input);
179 | error.message = error.message.replace(/:\d+:\d+:/, `:${error.line}:${error.column}:`);
180 | }
181 |
182 | return error;
183 | }
184 | parse(opts) {
185 | const style = this.style;
186 | const syntax = style.syntax;
187 | let root = style.root;
188 |
189 | try {
190 | root = this.templateParse(style.content, {
191 | ...opts,
192 | map: false,
193 | ...style.opts,
194 | });
195 | } catch (error) {
196 | if (style.ignoreErrors) {
197 | return;
198 | }
199 |
200 | if (!style.skipConvert) {
201 | this.error(error);
202 | }
203 |
204 | throw error;
205 | }
206 |
207 | if (!style.skipConvert) {
208 | this.root(root);
209 | }
210 |
211 | root.source.inline = Boolean(style.inline);
212 | root.source.lang = style.lang;
213 | root.source.syntax = syntax;
214 |
215 | return root;
216 | }
217 | }
218 |
219 | function docFixer(offset, source, opts) {
220 | let match;
221 | const lines = [];
222 |
223 | reNewLine.lastIndex = 0;
224 | while ((match = reNewLine.exec(source))) {
225 | lines.push(match.index);
226 | }
227 | lines.push(source.length);
228 |
229 | return function parseStyle(style) {
230 | const parse = style.syntax ? style.syntax.parse : postcssParse;
231 |
232 | return new LocalFixer(offset, lines, style, parse).parse(opts);
233 | };
234 | }
235 |
236 | function getNodeIndex(node, input) {
237 | const source = input.css;
238 | let match;
239 | let line = 1;
240 | let lastIndex = -1;
241 |
242 | reNewLine.lastIndex = 0;
243 | while ((match = reNewLine.exec(source))) {
244 | if (line === node.source.start.line) {
245 | return lastIndex + node.source.start.column;
246 | }
247 |
248 | lastIndex = match.index;
249 | line++;
250 | }
251 |
252 | if (line === node.source.start.line) {
253 | return lastIndex + node.source.start.column;
254 | }
255 |
256 | return source.length;
257 | }
258 |
--------------------------------------------------------------------------------
/template-parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const helper = require('./template-parser-helper');
4 | const Parser = require('postcss/lib/parser');
5 | const templateTokenize = require('./template-tokenize');
6 |
7 | class TemplateParser extends Parser {
8 | createTokenizer() {
9 | this.tokenizer = templateTokenize(this.input);
10 | }
11 | other(start) {
12 | return helper.literal.call(this, start) || super.other.call(this, start);
13 | }
14 | freeSemicolon(token) {
15 | return helper.freeSemicolon.call(this, token);
16 | }
17 | }
18 | module.exports = TemplateParser;
19 |
--------------------------------------------------------------------------------
/template-safe-parse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Input = require('postcss/lib/input');
4 | const TemplateSafeParser = require('./template-safe-parser');
5 |
6 | function templateSafeParse(css, opts) {
7 | const input = new Input(css, opts);
8 |
9 | input.quasis = opts.quasis;
10 | input.templateLiteralStyles = opts.templateLiteralStyles;
11 | input.parseOptions = opts;
12 | const parser = new TemplateSafeParser(input);
13 |
14 | parser.parse();
15 |
16 | return parser.root;
17 | }
18 |
19 | module.exports = templateSafeParse;
20 |
--------------------------------------------------------------------------------
/template-safe-parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const helper = require('./template-parser-helper');
4 | // eslint-disable-next-line node/no-unpublished-require
5 | const SafeParser = require('postcss-safe-parser/lib/safe-parser');
6 | const templateTokenize = require('./template-tokenize');
7 |
8 | class TemplateSafeParser extends SafeParser {
9 | createTokenizer() {
10 | this.tokenizer = templateTokenize(this.input, { ignoreErrors: true });
11 | }
12 | other(start) {
13 | return helper.literal.call(this, start) || super.other.call(this, start);
14 | }
15 | freeSemicolon(token) {
16 | return helper.freeSemicolon.call(this, token);
17 | }
18 | }
19 | module.exports = TemplateSafeParser;
20 |
--------------------------------------------------------------------------------
/template-stringifier.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Stringifier = require('postcss/lib/stringifier');
4 |
5 | class TemplateStringifier extends Stringifier {
6 | literal(node) {
7 | if (node.nodes && node.nodes.length) {
8 | node.nodes.forEach((root) => {
9 | this.builder(root.raws.beforeStart, root, 'beforeStart');
10 | this.stringify(root);
11 | this.builder(root.raws.afterEnd, root, 'afterEnd');
12 | });
13 | } else {
14 | this.builder(node.text, node);
15 | }
16 |
17 | if (node.raws.ownSemicolon) {
18 | this.builder(node.raws.ownSemicolon, node, 'end');
19 | }
20 | }
21 | }
22 |
23 | module.exports = TemplateStringifier;
24 |
--------------------------------------------------------------------------------
/template-stringify.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const TemplateStringifier = require('./template-stringifier');
4 |
5 | module.exports = function TemplateStringify(node, builder) {
6 | const str = new TemplateStringifier(builder);
7 |
8 | str.stringify(node);
9 | };
10 |
--------------------------------------------------------------------------------
/template-tokenize.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const tokenize = require('postcss/lib/tokenize');
4 |
5 | function templateTokenize(input, options = {}) {
6 | let pos = input.quasis[0].start;
7 | const quasis = input.quasis.filter((quasi) => quasi.start !== quasi.end);
8 | const tokenizer = tokenize(input, options);
9 |
10 | function tokenInExpressions(token, returned) {
11 | const start = pos;
12 |
13 | pos += token[1].length;
14 |
15 | if (
16 | !quasis.some((quasi) => start >= quasi.start && pos <= quasi.end) ||
17 | (returned.length && token[0] === returned[0][0])
18 | ) {
19 | return true;
20 | }
21 |
22 | if (returned.length) {
23 | back(token);
24 | }
25 | }
26 |
27 | function back(token) {
28 | pos -= token[1].length;
29 |
30 | return tokenizer.back(token);
31 | }
32 |
33 | function nextToken(opts) {
34 | const returned = [];
35 | let token;
36 | let line;
37 | let column;
38 |
39 | while ((token = tokenizer.nextToken(opts)) && tokenInExpressions(token, returned)) {
40 | line = token[4] || token[2] || line;
41 | column = token[5] || token[3] || column;
42 | returned.push(token);
43 | }
44 |
45 | if (returned.length) {
46 | token = [
47 | returned[0][0],
48 | returned.map((parentToken) => parentToken[1]).join(''),
49 | returned[0][2],
50 | returned[0][3],
51 | line,
52 | column,
53 | ];
54 | }
55 |
56 | return token;
57 | }
58 |
59 | return { ...tokenizer, back, nextToken };
60 | }
61 |
62 | module.exports = templateTokenize;
63 |
--------------------------------------------------------------------------------
/test/babel-config/.babelrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["./babel-plugin-my-plugin.js"]
3 | }
4 |
--------------------------------------------------------------------------------
/test/babel-config/babel-config.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const syntax = require('../../');
5 |
6 | it('works with babel config present', () => {
7 | const file = require.resolve('./fixtures/styled-components');
8 | let code = fs.readFileSync(file);
9 |
10 | const document = syntax.parse(code, {
11 | from: file,
12 | });
13 |
14 | code = code.toString();
15 | expect(document.toString()).toBe(code);
16 | expect(document.source).toHaveProperty('lang', 'jsx');
17 |
18 | expect(document.nodes).toHaveLength(1);
19 | expect(document.first.nodes).toHaveLength(8);
20 |
21 | expect(document.first.nodes[0]).toHaveProperty('type', 'comment');
22 | expect(document.first.nodes[1]).toHaveProperty('type', 'decl');
23 | expect(document.first.nodes[2]).toHaveProperty('type', 'decl');
24 | expect(document.first.nodes[3]).toHaveProperty('type', 'decl');
25 | expect(document.first.nodes[4]).toHaveProperty('type', 'decl');
26 | expect(document.first.nodes[5]).toHaveProperty('type', 'decl');
27 | expect(document.first.nodes[6]).toHaveProperty('type', 'decl');
28 | expect(document.first.nodes[7]).toHaveProperty('type', 'decl');
29 | });
30 |
--------------------------------------------------------------------------------
/test/babel-config/babel-plugin-my-plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function () {
4 | return {
5 | visitor: {
6 | Identifier() {},
7 | },
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/test/babel-config/fixtures/styled-components.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const styled = require("styled-components");
3 | const Button = styled.button`
4 | /* Adapt the colours based on primary prop */
5 | background: ${props => props.primary ? "palevioletred" : "white"};
6 | color: ${props => props.primary ? "white" : "palevioletred"};
7 |
8 | font-size: 1em;
9 | margin: 1em;
10 | padding: 0.25em 1em;
11 | border: 2px solid palevioletred;
12 | border-radius: 3px;
13 | `;
14 |
--------------------------------------------------------------------------------
/test/camel-case.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const camelCase = require('../camel-case');
4 | const unCamelCase = require('../un-camel-case');
5 |
6 | const data = {
7 | borderTopLeftRadius: 'border-top-left-radius',
8 | backgroundImage: 'background-image',
9 | xwebkitAnimation: '-xwebkit-animation',
10 | webkitAnimation: '-webkit-animation',
11 | epubAnimation: '-epub-animation',
12 | mozAnimation: '-moz-animation',
13 | msAnimation: '-ms-animation',
14 | OAnimation: '-o-animation',
15 | XAnimation: '-x-animation',
16 | webkitApp: '-webkit-app',
17 | onChange: 'on-change',
18 | OnChange: '-on-change',
19 | overflowWrap: 'overflow-wrap',
20 | overflowX: 'overflow-x',
21 | zIndex: 'z-index',
22 | '::selection': '::selection',
23 | '::mozSelection': '::-moz-selection',
24 | '::mozSelection,::selection': '::-moz-selection,::selection',
25 | '--margin-top': '--margin-top',
26 | 'margin--top': 'margin--top',
27 | 'height: webkitCalc(2vh-20px);': 'height: -webkit-calc(2vh-20px);',
28 | 'calc(2vh-20px)': 'calc(2vh-20px)',
29 | 'calc(2vh--20px)': 'calc(2vh--20px)',
30 | };
31 |
32 | const testCases = Object.keys(data).map((prop) => {
33 | return {
34 | camel: prop,
35 | unCamel: data[prop],
36 | };
37 | });
38 |
39 | const symbols = Array.from('@*:;\n,(){} ');
40 |
41 | describe('camelCase', () => {
42 | testCases.forEach((testCase) => {
43 | it(`${testCase.unCamel} => ${testCase.camel}`, () => {
44 | expect(camelCase(testCase.unCamel)).toBe(testCase.camel);
45 | });
46 | });
47 | describe('symbols', () => {
48 | symbols.forEach((symbol) => {
49 | it(`"${symbol}"`, () => {
50 | expect(camelCase(testCases.map((testCase) => testCase.unCamel).join(symbol))).toBe(
51 | testCases.map((testCase) => testCase.camel).join(symbol),
52 | );
53 | });
54 | });
55 | });
56 | });
57 |
58 | describe('unCamelCase', () => {
59 | testCases.forEach((testCase) => {
60 | it(`${testCase.camel} => ${testCase.unCamel}`, () => {
61 | expect(unCamelCase(testCase.camel)).toBe(testCase.unCamel);
62 | });
63 | });
64 | describe('symbols', () => {
65 | symbols.forEach((symbol) => {
66 | it(`"${symbol}"`, () => {
67 | expect(unCamelCase(testCases.map((testCase) => testCase.camel).join(symbol))).toBe(
68 | testCases.map((testCase) => testCase.unCamel).join(symbol),
69 | );
70 | });
71 | });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/test/css-in-js.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const autoprefixer = require('autoprefixer');
4 | const cases = require('postcss-parser-tests');
5 | const JSON5 = require('json5');
6 | const objectStringify = require('../object-stringify');
7 | const postcss = require('postcss');
8 | const syntax = require('../');
9 |
10 | describe('CSS in JS', () => {
11 | it('basic js', () => {
12 | const document = syntax.parse('x().y(z => {});', {
13 | from: '/fixtures/basic.js',
14 | });
15 |
16 | expect(document.nodes).toHaveLength(0);
17 | });
18 | it('glamorous', () => {
19 | const code = `
20 | import glm from 'glamorous';
21 | const Component1 = glm.a({
22 | "::placeholder": {
23 | color: "gray",
24 | },
25 | });
26 | `;
27 | const out = `
28 | import glm from 'glamorous';
29 | const Component1 = glm.a({
30 | "::webkitInputPlaceholder": {
31 | color: "gray",
32 | },
33 | "::placeholder": {
34 | color: "gray",
35 | },
36 | });
37 | `;
38 |
39 | return postcss([
40 | autoprefixer({
41 | overrideBrowserslist: ['Chrome > 10'],
42 | }),
43 | ])
44 | .process(code, {
45 | syntax,
46 | from: '/fixtures/glamorous-prefix.jsx',
47 | })
48 | .then((result) => {
49 | expect(result.content).toBe(out);
50 | });
51 | });
52 |
53 | it('leaves kebab-case and camelCase in media query params untouched', () => {
54 | // In previous versions, at-rule properties were converted from camelCase to kebab-case
55 | // during parsing, and back to camelCase during stringifying. This is however not correct,
56 | // params should not be changed. Also see:
57 | // https://github.com/stylelint/postcss-css-in-js/issues/38
58 | const code = `
59 | import glm from 'glamorous';
60 | const Component1 = glm.a({
61 | "@media (max-width: 1000px)": {
62 | color: "red",
63 | },
64 | "@media (maxWidth: 1000px)": {
65 | color: "red",
66 | },
67 | });
68 | `;
69 |
70 | expect(syntax.parse(code).toString()).toBe(code);
71 | });
72 |
73 | describe('setter for object literals', () => {
74 | it('decl.raws.prop.raw & decl.raws.value.raw', () => {
75 | const decl = syntax.parse(
76 | `
77 | import glm from 'glamorous';
78 | const Component1 = glm.a({
79 | borderRadius: '5px'
80 | });
81 | `,
82 | {
83 | from: '/fixtures/glamorous-atRule.jsx',
84 | },
85 | ).first.first.first;
86 |
87 | decl.raws.prop.raw = 'WebkitBorderRadius';
88 | expect(decl.prop).toBe('-webkit-border-radius');
89 | decl.raws.value.raw = '15px';
90 | expect(decl.value).toBe('15px');
91 | });
92 | it('atRule.raws.params.raw', () => {
93 | const atRule = syntax.parse(
94 | `
95 | import glm from 'glamorous';
96 | const Component1 = glm.a({
97 | '@media (max-width: 500px)': {
98 | borderRadius: '5px'
99 | }
100 | });
101 | `,
102 | {
103 | from: '/fixtures/glamorous-atRule.jsx',
104 | },
105 | ).first.first.first;
106 |
107 | atRule.raws.params.raw = "(min-width: ' + minWidth + ')";
108 | expect(atRule.params).toBe("(min-width: ' + minWidth + ')");
109 | });
110 | });
111 |
112 | it('empty object literals', () => {
113 | const code = `
114 | import glm from 'glamorous';
115 | const Component1 = glm.a({
116 | });
117 | `;
118 | const root = syntax.parse(code, {
119 | from: '/fixtures/glamorous-empty-object-literals.jsx',
120 | });
121 |
122 | expect(root.toString()).toBe(code);
123 |
124 | root.first.first.raws.after = '';
125 | expect(root.toString()).toBe(`
126 | import glm from 'glamorous';
127 | const Component1 = glm.a({});
128 | `);
129 | });
130 |
131 | it('cssFloat', () => {
132 | const code = `
133 | import glm from 'glamorous';
134 | const Component1 = glm.a({
135 | cssFloat: "left",
136 | });
137 | `;
138 |
139 | const root = syntax.parse(code, {
140 | from: '/fixtures/glamorous-float.jsx',
141 | });
142 |
143 | expect(root.first.first.first).toHaveProperty('prop', 'float');
144 |
145 | expect(root.toString()).toBe(`
146 | import glm from 'glamorous';
147 | const Component1 = glm.a({
148 | cssFloat: "left",
149 | });
150 | `);
151 | });
152 |
153 | it('float', () => {
154 | const code = `
155 | const component = styled(Btn)({
156 | float: "left",
157 | });
158 | `;
159 |
160 | const root = syntax.parse(code, {
161 | from: '/fixtures/styled-float.jsx',
162 | });
163 |
164 | expect(root.first.first.first).toHaveProperty('prop', 'float');
165 |
166 | expect(root.toString()).toBe(`
167 | const component = styled(Btn)({
168 | float: "left",
169 | });
170 | `);
171 | });
172 |
173 | describe('objectify for css', () => {
174 | cases.each((name, css) => {
175 | if (name === 'bom.css') return;
176 |
177 | if (name === 'custom-properties.css') return;
178 |
179 | it(`objectStringifier ${name}`, () => {
180 | const root = postcss.parse(css);
181 | const jsSource = root.toString(objectStringify).trim();
182 | const jsonSource = `{\n${jsSource.replace(/,$/, '').replace(/[\s;]+$/gm, '')}\n}`;
183 |
184 | expect(JSON5.parse(jsonSource)).toBeTruthy();
185 | });
186 | });
187 | });
188 |
189 | it('incomplete code', () => {
190 | const filename = 'fixtures/incomplete- react-native.mjs';
191 | const code = [
192 | `StyleSheet.create({
193 | box: { padding: 10 },
194 | text: { fontWeight: "bold" },
195 | });`,
196 | 'styled.div`a{display: block}`',
197 | ].join('\n');
198 |
199 | const document = syntax.parse(code, {
200 | from: filename,
201 | });
202 |
203 | expect(document.nodes).toHaveLength(2);
204 | });
205 | });
206 |
--------------------------------------------------------------------------------
/test/emotion.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const syntax = require('../');
5 |
6 | describe('javascript tests', () => {
7 | it('react-emotion', () => {
8 | const filename = require.resolve('./fixtures/react-emotion.jsx');
9 | let code = fs.readFileSync(filename);
10 |
11 | const document = syntax.parse(code, {
12 | from: filename,
13 | });
14 |
15 | code = code.toString();
16 |
17 | expect(document.toString()).toBe(code);
18 | expect(document.nodes).toHaveLength(4);
19 |
20 | document.nodes.forEach((root) => {
21 | expect(typeof root.last.toString()).toBe('string');
22 | expect(root.source).toHaveProperty('input');
23 |
24 | expect(code).toEqual(expect.stringContaining(root.source.input.css));
25 | expect(root.source.input.css.length).toBeLessThan(code.length);
26 | expect(root.source.start.line).toBeGreaterThan(1);
27 |
28 | root.walk((node) => {
29 | expect(node).toHaveProperty('source');
30 |
31 | expect(node.source.input.css).toBe(root.source.input.css);
32 |
33 | expect(node.source).toHaveProperty('start.line');
34 | expect(node.source).toHaveProperty('end.line');
35 | });
36 | });
37 | });
38 |
39 | it('emotion-10', () => {
40 | const filename = require.resolve('./fixtures/emotion-10.jsx');
41 | let code = fs.readFileSync(filename);
42 |
43 | const document = syntax.parse(code, {
44 | from: filename,
45 | });
46 |
47 | code = code.toString();
48 |
49 | expect(document.toString()).toBe(code);
50 | expect(document.nodes).toHaveLength(6);
51 |
52 | document.nodes.forEach((root) => {
53 | expect(typeof root.last.toString()).toBe('string');
54 | expect(root.source).toHaveProperty('input');
55 |
56 | expect(code).toEqual(expect.stringContaining(root.source.input.css));
57 | expect(root.source.input.css.length).toBeLessThan(code.length);
58 | expect(root.source.start.line).toBeGreaterThan(1);
59 |
60 | root.walk((node) => {
61 | expect(node).toHaveProperty('source');
62 |
63 | expect(node.source.input.css).toBe(root.source.input.css);
64 |
65 | expect(node.source).toHaveProperty('start.line');
66 | expect(node.source).toHaveProperty('end.line');
67 | });
68 | });
69 | });
70 |
71 | it('works with vue-emotion', () => {
72 | // Related issues:
73 | // - https://github.com/stylelint/stylelint/issues/4247
74 | // - https://github.com/gucong3000/postcss-jsx/issues/63
75 | // - https://github.com/stylelint/postcss-css-in-js/issues/22
76 | const parsed = syntax.parse(`
77 | import styled from 'vue-emotion';
78 |
79 | const Wrapper = styled('div')\`
80 | left: 0;
81 | top: 0;
82 | width: 100%;
83 | height: 100%;
84 | \`;
85 | `);
86 |
87 | expect(parsed.nodes).toHaveLength(1);
88 | });
89 |
90 | it('works with @emotion/styled', () => {
91 | const parsed = syntax.parse(`
92 | import styled from '@emotion/styled';
93 |
94 | const Wrapper = styled.div\`
95 | left: 0;
96 | \`;
97 | `);
98 |
99 | expect(parsed.nodes).toHaveLength(1);
100 | });
101 |
102 | it('works with css objects', () => {
103 | // It should parse:
104 | // - Inline objects (inside of the JSX)
105 | // - Variables that are referenced inside of the JSX
106 | // - Variables that are referenced as spread
107 | const parsed = syntax.parse(`
108 | import React from 'react';
109 |
110 | const spreaded = {
111 | width: 100,
112 | padding: 40,
113 | };
114 |
115 | const notInline = {
116 | ...spreaded,
117 | margin: 60,
118 | };
119 |
120 | const Component = () => (
121 |
({
157 | ...spreaded,
158 | margin: 60,
159 | color: theme.color.primary,
160 | })}>
161 | some other text
162 | Hello
163 |
164 | );
165 | `);
166 |
167 | expect(parsed.nodes).toHaveLength(3);
168 | });
169 | });
170 |
--------------------------------------------------------------------------------
/test/fixtures/emotion-10.jsx:
--------------------------------------------------------------------------------
1 | /* global render */
2 | /** @jsx jsx */
3 | import { css } from "@emotion/core";
4 | import styled from "@emotion/styled";
5 |
6 | const SomeComponent = styled.div`
7 | display: flex;
8 | background-color: ${props => props.color};
9 | `;
10 |
11 | const AnotherComponent = styled.h1(
12 | {
13 | color: "hotpink",
14 | },
15 | props => ({ flex: props.flex })
16 | );
17 |
18 | render(
19 |
46 | }
47 |
48 | export default {
49 | HelloWorldComponent,
50 | React,
51 | App,
52 | ObjectShorthandComponent
53 | };
54 |
--------------------------------------------------------------------------------
/test/fixtures/lit-css.mjs:
--------------------------------------------------------------------------------
1 | import { css } from "lit-css";
2 | const customPropStyle = "customPropStyle";
3 | const tableStyle = "tableStyle";
4 | const fancyTableStyle = "fancyTableStyle";
5 | export default css`
6 | /* @define --table-border-color */
7 | :host {
8 | --table-border-color: green;
9 | }
10 | ${customPropStyle} ${tableStyle} ${fancyTableStyle}
11 | `;
12 |
--------------------------------------------------------------------------------
/test/fixtures/lit-css.mjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "left": " ",
16 | "right": " "
17 | },
18 | "type": "comment",
19 | "source": {
20 | "start": {
21 | "line": 6,
22 | "column": 2
23 | },
24 | "input": {
25 | "file": "lit-css.mjs",
26 | "quasis": [
27 | {
28 | "start": 169,
29 | "end": 250
30 | },
31 | {
32 | "start": 268,
33 | "end": 269
34 | },
35 | {
36 | "start": 282,
37 | "end": 283
38 | },
39 | {
40 | "start": 301,
41 | "end": 302
42 | }
43 | ]
44 | },
45 | "end": {
46 | "line": 6,
47 | "column": 35
48 | }
49 | },
50 | "text": "@define --table-border-color"
51 | },
52 | {
53 | "raws": {
54 | "before": "\n\t",
55 | "between": " ",
56 | "semicolon": true,
57 | "after": "\n\t"
58 | },
59 | "type": "rule",
60 | "nodes": [
61 | {
62 | "raws": {
63 | "before": "\n\t\t",
64 | "between": ": "
65 | },
66 | "type": "decl",
67 | "source": {
68 | "start": {
69 | "line": 8,
70 | "column": 3
71 | },
72 | "input": {
73 | "file": "lit-css.mjs",
74 | "quasis": [
75 | {
76 | "start": 169,
77 | "end": 250
78 | },
79 | {
80 | "start": 268,
81 | "end": 269
82 | },
83 | {
84 | "start": 282,
85 | "end": 283
86 | },
87 | {
88 | "start": 301,
89 | "end": 302
90 | }
91 | ]
92 | },
93 | "end": {
94 | "line": 8,
95 | "column": 30
96 | }
97 | },
98 | "prop": "--table-border-color",
99 | "value": "green"
100 | }
101 | ],
102 | "source": {
103 | "start": {
104 | "line": 7,
105 | "column": 2
106 | },
107 | "input": {
108 | "file": "lit-css.mjs",
109 | "quasis": [
110 | {
111 | "start": 169,
112 | "end": 250
113 | },
114 | {
115 | "start": 268,
116 | "end": 269
117 | },
118 | {
119 | "start": 282,
120 | "end": 283
121 | },
122 | {
123 | "start": 301,
124 | "end": 302
125 | }
126 | ]
127 | },
128 | "end": {
129 | "line": 9,
130 | "column": 2
131 | }
132 | },
133 | "selector": ":host"
134 | },
135 | {
136 | "raws": {
137 | "before": "\n\t"
138 | },
139 | "text": "${customPropStyle}",
140 | "type": "literal",
141 | "source": {
142 | "start": {
143 | "line": 10,
144 | "column": 2
145 | },
146 | "input": {
147 | "file": "lit-css.mjs",
148 | "quasis": [
149 | {
150 | "start": 169,
151 | "end": 250
152 | },
153 | {
154 | "start": 268,
155 | "end": 269
156 | },
157 | {
158 | "start": 282,
159 | "end": 283
160 | },
161 | {
162 | "start": 301,
163 | "end": 302
164 | }
165 | ]
166 | }
167 | }
168 | },
169 | {
170 | "raws": {
171 | "before": " "
172 | },
173 | "text": "${tableStyle}",
174 | "type": "literal",
175 | "source": {
176 | "start": {
177 | "line": 10,
178 | "column": 21
179 | },
180 | "input": {
181 | "file": "lit-css.mjs",
182 | "quasis": [
183 | {
184 | "start": 169,
185 | "end": 250
186 | },
187 | {
188 | "start": 268,
189 | "end": 269
190 | },
191 | {
192 | "start": 282,
193 | "end": 283
194 | },
195 | {
196 | "start": 301,
197 | "end": 302
198 | }
199 | ]
200 | }
201 | }
202 | },
203 | {
204 | "raws": {
205 | "before": " "
206 | },
207 | "text": "${fancyTableStyle}",
208 | "type": "literal",
209 | "source": {
210 | "start": {
211 | "line": 10,
212 | "column": 35
213 | },
214 | "input": {
215 | "file": "lit-css.mjs",
216 | "quasis": [
217 | {
218 | "start": 169,
219 | "end": 250
220 | },
221 | {
222 | "start": 268,
223 | "end": 269
224 | },
225 | {
226 | "start": 282,
227 | "end": 283
228 | },
229 | {
230 | "start": 301,
231 | "end": 302
232 | }
233 | ]
234 | }
235 | }
236 | }
237 | ],
238 | "source": {
239 | "input": {
240 | "file": "lit-css.mjs",
241 | "quasis": [
242 | {
243 | "start": 169,
244 | "end": 250
245 | },
246 | {
247 | "start": 268,
248 | "end": 269
249 | },
250 | {
251 | "start": 282,
252 | "end": 283
253 | },
254 | {
255 | "start": 301,
256 | "end": 302
257 | }
258 | ]
259 | },
260 | "start": {
261 | "line": 5,
262 | "column": 20
263 | },
264 | "inline": false,
265 | "lang": "template-literal",
266 | "syntax": {}
267 | }
268 | }
269 | ],
270 | "source": {
271 | "input": {
272 | "file": "lit-css.mjs"
273 | },
274 | "start": {
275 | "line": 1,
276 | "column": 1
277 | },
278 | "lang": "jsx"
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/test/fixtures/material-ui.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/styles';
3 | import Button from '@material-ui/core/Button';
4 |
5 | const useStyles = makeStyles({
6 | root: {
7 | background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
8 | border: 0,
9 | borderRadius: 3,
10 | boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
11 | color: 'white',
12 | height: 48,
13 | padding: '0 30px',
14 | },
15 | });
16 |
17 | export default function Hook() {
18 | const classes = useStyles();
19 | return
;
20 | }
21 |
--------------------------------------------------------------------------------
/test/fixtures/material-ui.jsx.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {},
7 | "source": {
8 | "input": {
9 | "file": "material-ui.jsx"
10 | },
11 | "start": {
12 | "line": 5,
13 | "column": 29
14 | },
15 | "inline": false,
16 | "lang": "object-literal",
17 | "syntax": {}
18 | },
19 | "type": "root",
20 | "nodes": [
21 | {
22 | "raws": {
23 | "after": "\n",
24 | "semicolon": true,
25 | "before": ""
26 | },
27 | "type": "object",
28 | "nodes": [
29 | {
30 | "raws": {
31 | "selector": {
32 | "prefix": "",
33 | "suffix": "",
34 | "raw": "root",
35 | "value": "root"
36 | },
37 | "between": ": ",
38 | "after": "\n ",
39 | "semicolon": true,
40 | "before": "\n "
41 | },
42 | "selector": "root",
43 | "type": "rule",
44 | "nodes": [
45 | {
46 | "raws": {
47 | "prop": {
48 | "prefix": "",
49 | "suffix": "",
50 | "raw": "background",
51 | "value": "background"
52 | },
53 | "value": {
54 | "prefix": "'",
55 | "suffix": "'",
56 | "raw": "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
57 | "value": "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)"
58 | },
59 | "between": ": ",
60 | "before": "\n "
61 | },
62 | "prop": "background",
63 | "value": "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
64 | "type": "decl",
65 | "source": {
66 | "input": {
67 | "file": "material-ui.jsx"
68 | },
69 | "start": {
70 | "line": 7,
71 | "column": 4,
72 | "index": 170
73 | },
74 | "end": {
75 | "line": 7,
76 | "column": 66,
77 | "index": 232
78 | }
79 | }
80 | },
81 | {
82 | "raws": {
83 | "prop": {
84 | "prefix": "",
85 | "suffix": "",
86 | "raw": "border",
87 | "value": "border"
88 | },
89 | "value": {
90 | "prefix": "",
91 | "suffix": "",
92 | "raw": "0",
93 | "value": "0"
94 | },
95 | "between": ": ",
96 | "before": "\n "
97 | },
98 | "prop": "border",
99 | "value": "0",
100 | "type": "decl",
101 | "source": {
102 | "input": {
103 | "file": "material-ui.jsx"
104 | },
105 | "start": {
106 | "line": 8,
107 | "column": 4,
108 | "index": 238
109 | },
110 | "end": {
111 | "line": 8,
112 | "column": 13,
113 | "index": 247
114 | }
115 | }
116 | },
117 | {
118 | "raws": {
119 | "prop": {
120 | "prefix": "",
121 | "suffix": "",
122 | "raw": "borderRadius",
123 | "value": "border-radius"
124 | },
125 | "value": {
126 | "prefix": "",
127 | "suffix": "",
128 | "raw": "3",
129 | "value": "3"
130 | },
131 | "between": ": ",
132 | "before": "\n "
133 | },
134 | "prop": "border-radius",
135 | "value": "3",
136 | "type": "decl",
137 | "source": {
138 | "input": {
139 | "file": "material-ui.jsx"
140 | },
141 | "start": {
142 | "line": 9,
143 | "column": 4,
144 | "index": 253
145 | },
146 | "end": {
147 | "line": 9,
148 | "column": 19,
149 | "index": 268
150 | }
151 | }
152 | },
153 | {
154 | "raws": {
155 | "prop": {
156 | "prefix": "",
157 | "suffix": "",
158 | "raw": "boxShadow",
159 | "value": "box-shadow"
160 | },
161 | "value": {
162 | "prefix": "'",
163 | "suffix": "'",
164 | "raw": "0 3px 5px 2px rgba(255, 105, 135, .3)",
165 | "value": "0 3px 5px 2px rgba(255, 105, 135, .3)"
166 | },
167 | "between": ": ",
168 | "before": "\n "
169 | },
170 | "prop": "box-shadow",
171 | "value": "0 3px 5px 2px rgba(255, 105, 135, .3)",
172 | "type": "decl",
173 | "source": {
174 | "input": {
175 | "file": "material-ui.jsx"
176 | },
177 | "start": {
178 | "line": 10,
179 | "column": 4,
180 | "index": 274
181 | },
182 | "end": {
183 | "line": 10,
184 | "column": 54,
185 | "index": 324
186 | }
187 | }
188 | },
189 | {
190 | "raws": {
191 | "prop": {
192 | "prefix": "",
193 | "suffix": "",
194 | "raw": "color",
195 | "value": "color"
196 | },
197 | "value": {
198 | "prefix": "'",
199 | "suffix": "'",
200 | "raw": "white",
201 | "value": "white"
202 | },
203 | "between": ": ",
204 | "before": "\n "
205 | },
206 | "prop": "color",
207 | "value": "white",
208 | "type": "decl",
209 | "source": {
210 | "input": {
211 | "file": "material-ui.jsx"
212 | },
213 | "start": {
214 | "line": 11,
215 | "column": 4,
216 | "index": 330
217 | },
218 | "end": {
219 | "line": 11,
220 | "column": 18,
221 | "index": 344
222 | }
223 | }
224 | },
225 | {
226 | "raws": {
227 | "prop": {
228 | "prefix": "",
229 | "suffix": "",
230 | "raw": "height",
231 | "value": "height"
232 | },
233 | "value": {
234 | "prefix": "",
235 | "suffix": "",
236 | "raw": "48",
237 | "value": "48"
238 | },
239 | "between": ": ",
240 | "before": "\n "
241 | },
242 | "prop": "height",
243 | "value": "48",
244 | "type": "decl",
245 | "source": {
246 | "input": {
247 | "file": "material-ui.jsx"
248 | },
249 | "start": {
250 | "line": 12,
251 | "column": 4,
252 | "index": 350
253 | },
254 | "end": {
255 | "line": 12,
256 | "column": 14,
257 | "index": 360
258 | }
259 | }
260 | },
261 | {
262 | "raws": {
263 | "prop": {
264 | "prefix": "",
265 | "suffix": "",
266 | "raw": "padding",
267 | "value": "padding"
268 | },
269 | "value": {
270 | "prefix": "'",
271 | "suffix": "'",
272 | "raw": "0 30px",
273 | "value": "0 30px"
274 | },
275 | "between": ": ",
276 | "before": "\n "
277 | },
278 | "prop": "padding",
279 | "value": "0 30px",
280 | "type": "decl",
281 | "source": {
282 | "input": {
283 | "file": "material-ui.jsx"
284 | },
285 | "start": {
286 | "line": 13,
287 | "column": 4,
288 | "index": 366
289 | },
290 | "end": {
291 | "line": 13,
292 | "column": 21,
293 | "index": 383
294 | }
295 | }
296 | }
297 | ],
298 | "source": {
299 | "input": {
300 | "file": "material-ui.jsx"
301 | },
302 | "start": {
303 | "line": 6,
304 | "column": 2,
305 | "index": 158
306 | },
307 | "end": {
308 | "line": 14,
309 | "column": 3,
310 | "index": 388
311 | }
312 | }
313 | }
314 | ],
315 | "source": {
316 | "input": {
317 | "file": "material-ui.jsx"
318 | },
319 | "start": {
320 | "line": 5,
321 | "column": 29,
322 | "index": 154
323 | },
324 | "end": {
325 | "line": 15,
326 | "column": 1,
327 | "index": 391
328 | }
329 | }
330 | }
331 | ]
332 | }
333 | ],
334 | "source": {
335 | "input": {
336 | "file": "material-ui.jsx"
337 | },
338 | "start": {
339 | "line": 1,
340 | "column": 1
341 | },
342 | "lang": "jsx"
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/test/fixtures/multiline-arrow-function.mjs:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StatusText = styled.div`
4 | color: ${(props) =>
5 | (props.status === "signed" && "red") ||
6 | "blue"};
7 | `;
8 |
--------------------------------------------------------------------------------
/test/fixtures/react-emotion.jsx:
--------------------------------------------------------------------------------
1 | /* global render */
2 |
3 | import styled, { css } from "react-emotion";
4 | const SomeComponent = styled("div")`
5 | display: flex;
6 | background-color: ${props => props.color};
7 | `;
8 |
9 | const AnotherComponent = styled("h1")(
10 | {
11 | color: "hotpink",
12 | },
13 | props => ({ flex: props.flex })
14 | );
15 |
16 | render(
17 |
18 |
19 | Some text.
20 |
21 |
22 | );
23 | const app = document.getElementById("root");
24 | const myStyle = css`
25 | color: rebeccapurple;
26 | `;
27 | app.classList.add(myStyle);
28 |
29 | export default {
30 | SomeComponent,
31 | AnotherComponent,
32 | };
33 |
--------------------------------------------------------------------------------
/test/fixtures/react-emotion.jsx.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": true,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 5,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "react-emotion.jsx",
25 | "quasis": [
26 | {
27 | "start": 102,
28 | "end": 138
29 | },
30 | {
31 | "start": 161,
32 | "end": 163
33 | }
34 | ]
35 | },
36 | "end": {
37 | "line": 5,
38 | "column": 15
39 | }
40 | },
41 | "prop": "display",
42 | "value": "flex"
43 | },
44 | {
45 | "raws": {
46 | "before": "\n\t",
47 | "between": ": "
48 | },
49 | "type": "decl",
50 | "source": {
51 | "start": {
52 | "line": 6,
53 | "column": 2
54 | },
55 | "input": {
56 | "file": "react-emotion.jsx",
57 | "quasis": [
58 | {
59 | "start": 102,
60 | "end": 138
61 | },
62 | {
63 | "start": 161,
64 | "end": 163
65 | }
66 | ]
67 | },
68 | "end": {
69 | "line": 6,
70 | "column": 43
71 | }
72 | },
73 | "prop": "background-color",
74 | "value": "${props => props.color}"
75 | }
76 | ],
77 | "source": {
78 | "input": {
79 | "file": "react-emotion.jsx",
80 | "quasis": [
81 | {
82 | "start": 102,
83 | "end": 138
84 | },
85 | {
86 | "start": 161,
87 | "end": 163
88 | }
89 | ]
90 | },
91 | "start": {
92 | "line": 4,
93 | "column": 37
94 | },
95 | "inline": false,
96 | "lang": "template-literal",
97 | "syntax": {}
98 | }
99 | },
100 | {
101 | "raws": {},
102 | "source": {
103 | "input": {
104 | "file": "react-emotion.jsx"
105 | },
106 | "start": {
107 | "line": 10,
108 | "column": 1
109 | },
110 | "inline": false,
111 | "lang": "object-literal",
112 | "syntax": {}
113 | },
114 | "type": "root",
115 | "nodes": [
116 | {
117 | "raws": {
118 | "after": "\n\t",
119 | "semicolon": true,
120 | "before": "\t"
121 | },
122 | "type": "object",
123 | "nodes": [
124 | {
125 | "raws": {
126 | "prop": {
127 | "prefix": "",
128 | "suffix": "",
129 | "raw": "color",
130 | "value": "color"
131 | },
132 | "value": {
133 | "prefix": "\"",
134 | "suffix": "\"",
135 | "raw": "hotpink",
136 | "value": "hotpink"
137 | },
138 | "between": ": ",
139 | "before": "\n\t\t"
140 | },
141 | "prop": "color",
142 | "value": "hotpink",
143 | "type": "decl",
144 | "source": {
145 | "input": {
146 | "file": "react-emotion.jsx"
147 | },
148 | "start": {
149 | "line": 11,
150 | "column": 2,
151 | "index": 211
152 | },
153 | "end": {
154 | "line": 11,
155 | "column": 18,
156 | "index": 227
157 | }
158 | }
159 | }
160 | ],
161 | "source": {
162 | "input": {
163 | "file": "react-emotion.jsx"
164 | },
165 | "start": {
166 | "line": 10,
167 | "column": 1,
168 | "index": 207
169 | },
170 | "end": {
171 | "line": 12,
172 | "column": 2,
173 | "index": 231
174 | }
175 | }
176 | }
177 | ]
178 | },
179 | {
180 | "raws": {},
181 | "source": {
182 | "input": {
183 | "file": "react-emotion.jsx"
184 | },
185 | "start": {
186 | "line": 13,
187 | "column": 11
188 | },
189 | "inline": false,
190 | "lang": "object-literal",
191 | "syntax": {}
192 | },
193 | "type": "root",
194 | "nodes": [
195 | {
196 | "raws": {
197 | "after": " ",
198 | "semicolon": false,
199 | "before": ""
200 | },
201 | "type": "object",
202 | "nodes": [
203 | {
204 | "raws": {
205 | "prop": {
206 | "prefix": "",
207 | "suffix": "",
208 | "raw": "flex",
209 | "value": "flex"
210 | },
211 | "value": {
212 | "prefix": "",
213 | "suffix": "",
214 | "raw": "props.flex",
215 | "value": "props.flex"
216 | },
217 | "between": ": ",
218 | "before": " "
219 | },
220 | "prop": "flex",
221 | "value": "props.flex",
222 | "type": "decl",
223 | "source": {
224 | "input": {
225 | "file": "react-emotion.jsx"
226 | },
227 | "start": {
228 | "line": 13,
229 | "column": 13,
230 | "index": 246
231 | },
232 | "end": {
233 | "line": 13,
234 | "column": 29,
235 | "index": 262
236 | }
237 | }
238 | }
239 | ],
240 | "source": {
241 | "input": {
242 | "file": "react-emotion.jsx"
243 | },
244 | "start": {
245 | "line": 13,
246 | "column": 11,
247 | "index": 244
248 | },
249 | "end": {
250 | "line": 13,
251 | "column": 31,
252 | "index": 264
253 | }
254 | }
255 | }
256 | ]
257 | },
258 | {
259 | "raws": {
260 | "semicolon": true,
261 | "after": "\n"
262 | },
263 | "type": "root",
264 | "nodes": [
265 | {
266 | "raws": {
267 | "before": "\n\t",
268 | "between": ": "
269 | },
270 | "type": "decl",
271 | "source": {
272 | "start": {
273 | "line": 25,
274 | "column": 2
275 | },
276 | "input": {
277 | "file": "react-emotion.jsx"
278 | },
279 | "end": {
280 | "line": 25,
281 | "column": 22
282 | }
283 | },
284 | "prop": "color",
285 | "value": "rebeccapurple"
286 | }
287 | ],
288 | "source": {
289 | "input": {
290 | "file": "react-emotion.jsx"
291 | },
292 | "start": {
293 | "line": 24,
294 | "column": 21
295 | },
296 | "inline": false,
297 | "lang": "css",
298 | "syntax": {}
299 | }
300 | }
301 | ],
302 | "source": {
303 | "input": {
304 | "file": "react-emotion.jsx"
305 | },
306 | "start": {
307 | "line": 1,
308 | "column": 1
309 | },
310 | "lang": "jsx"
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/test/fixtures/react-native.mjs:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { AppRegistry, StyleSheet } from "react-native";
3 |
4 | class App extends React.Component {
5 | render () {
6 | return (
7 |
8 | Hello, world!
9 |
10 | );
11 | }
12 | }
13 |
14 | const styles = StyleSheet.create({
15 | box: { padding: 10 },
16 | text: { fontWeight: "bold" },
17 | });
18 |
19 | AppRegistry.registerComponent("App", () => App);
20 | AppRegistry.runApplication("App", { rootTag: document.getElementById("react-root") });
21 |
--------------------------------------------------------------------------------
/test/fixtures/react-native.mjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {},
7 | "source": {
8 | "input": {
9 | "file": "react-native.mjs"
10 | },
11 | "start": {
12 | "line": 14,
13 | "column": 33
14 | },
15 | "inline": false,
16 | "lang": "object-literal",
17 | "syntax": {}
18 | },
19 | "type": "root",
20 | "nodes": [
21 | {
22 | "raws": {
23 | "after": "\n",
24 | "semicolon": true,
25 | "before": ""
26 | },
27 | "type": "object",
28 | "nodes": [
29 | {
30 | "raws": {
31 | "selector": {
32 | "prefix": "",
33 | "suffix": "",
34 | "raw": "box",
35 | "value": "box"
36 | },
37 | "between": ": ",
38 | "after": " ",
39 | "semicolon": false,
40 | "before": "\n\t"
41 | },
42 | "selector": "box",
43 | "type": "rule",
44 | "nodes": [
45 | {
46 | "raws": {
47 | "prop": {
48 | "prefix": "",
49 | "suffix": "",
50 | "raw": "padding",
51 | "value": "padding"
52 | },
53 | "value": {
54 | "prefix": "",
55 | "suffix": "",
56 | "raw": "10",
57 | "value": "10"
58 | },
59 | "between": ": ",
60 | "before": " "
61 | },
62 | "prop": "padding",
63 | "value": "10",
64 | "type": "decl",
65 | "source": {
66 | "input": {
67 | "file": "react-native.mjs"
68 | },
69 | "start": {
70 | "line": 15,
71 | "column": 8,
72 | "index": 289
73 | },
74 | "end": {
75 | "line": 15,
76 | "column": 19,
77 | "index": 300
78 | }
79 | }
80 | }
81 | ],
82 | "source": {
83 | "input": {
84 | "file": "react-native.mjs"
85 | },
86 | "start": {
87 | "line": 15,
88 | "column": 1,
89 | "index": 282
90 | },
91 | "end": {
92 | "line": 15,
93 | "column": 21,
94 | "index": 302
95 | }
96 | }
97 | },
98 | {
99 | "raws": {
100 | "selector": {
101 | "prefix": "",
102 | "suffix": "",
103 | "raw": "text",
104 | "value": "text"
105 | },
106 | "between": ": ",
107 | "after": " ",
108 | "semicolon": false,
109 | "before": "\n\t"
110 | },
111 | "selector": "text",
112 | "type": "rule",
113 | "nodes": [
114 | {
115 | "raws": {
116 | "prop": {
117 | "prefix": "",
118 | "suffix": "",
119 | "raw": "fontWeight",
120 | "value": "font-weight"
121 | },
122 | "value": {
123 | "prefix": "\"",
124 | "suffix": "\"",
125 | "raw": "bold",
126 | "value": "bold"
127 | },
128 | "between": ": ",
129 | "before": " "
130 | },
131 | "prop": "font-weight",
132 | "value": "bold",
133 | "type": "decl",
134 | "source": {
135 | "input": {
136 | "file": "react-native.mjs"
137 | },
138 | "start": {
139 | "line": 16,
140 | "column": 9,
141 | "index": 313
142 | },
143 | "end": {
144 | "line": 16,
145 | "column": 27,
146 | "index": 331
147 | }
148 | }
149 | }
150 | ],
151 | "source": {
152 | "input": {
153 | "file": "react-native.mjs"
154 | },
155 | "start": {
156 | "line": 16,
157 | "column": 1,
158 | "index": 305
159 | },
160 | "end": {
161 | "line": 16,
162 | "column": 29,
163 | "index": 333
164 | }
165 | }
166 | }
167 | ],
168 | "source": {
169 | "input": {
170 | "file": "react-native.mjs"
171 | },
172 | "start": {
173 | "line": 14,
174 | "column": 33,
175 | "index": 279
176 | },
177 | "end": {
178 | "line": 17,
179 | "column": 1,
180 | "index": 336
181 | }
182 | }
183 | }
184 | ]
185 | }
186 | ],
187 | "source": {
188 | "input": {
189 | "file": "react-native.mjs"
190 | },
191 | "start": {
192 | "line": 1,
193 | "column": 1
194 | },
195 | "lang": "jsx"
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting-expr.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | const StyledComponent = styled.div`
3 | margin: 8px 0;
4 |
5 | ${() =>
6 | css`
7 | div {
8 | ${expr};
9 | }
10 | `}
11 | `;
12 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting-expr.js.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n ",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 3,
21 | "column": 3
22 | },
23 | "input": {
24 | "file": "styled-components-nesting-expr.js",
25 | "quasis": [
26 | {
27 | "start": 84,
28 | "end": 105
29 | },
30 | {
31 | "start": 165,
32 | "end": 166
33 | }
34 | ]
35 | },
36 | "end": {
37 | "line": 3,
38 | "column": 16
39 | }
40 | },
41 | "prop": "margin",
42 | "value": "8px 0"
43 | },
44 | {
45 | "raws": {
46 | "before": "\n\n "
47 | },
48 | "text": "${() =>\n css`\n div {\n ${expr};\n }\n `}",
49 | "type": "literal",
50 | "source": {
51 | "start": {
52 | "line": 5,
53 | "column": 3
54 | },
55 | "input": {
56 | "file": "styled-components-nesting-expr.js",
57 | "quasis": [
58 | {
59 | "start": 84,
60 | "end": 105
61 | },
62 | {
63 | "start": 165,
64 | "end": 166
65 | }
66 | ]
67 | }
68 | },
69 | "nodes": [
70 | {
71 | "raws": {
72 | "semicolon": false,
73 | "after": "\n "
74 | },
75 | "type": "root",
76 | "nodes": [
77 | {
78 | "raws": {
79 | "before": "\n ",
80 | "between": " ",
81 | "semicolon": false,
82 | "after": "\n "
83 | },
84 | "type": "rule",
85 | "nodes": [
86 | {
87 | "raws": {
88 | "before": "\n ",
89 | "ownSemicolon": ";"
90 | },
91 | "text": "${expr}",
92 | "type": "literal",
93 | "source": {
94 | "start": {
95 | "line": 8,
96 | "column": 9
97 | },
98 | "input": {
99 | "file": "styled-components-nesting-expr.js",
100 | "quasis": [
101 | {
102 | "start": 121,
103 | "end": 142
104 | },
105 | {
106 | "start": 149,
107 | "end": 163
108 | }
109 | ]
110 | }
111 | }
112 | }
113 | ],
114 | "source": {
115 | "start": {
116 | "line": 7,
117 | "column": 7
118 | },
119 | "input": {
120 | "file": "styled-components-nesting-expr.js",
121 | "quasis": [
122 | {
123 | "start": 121,
124 | "end": 142
125 | },
126 | {
127 | "start": 149,
128 | "end": 163
129 | }
130 | ]
131 | },
132 | "end": {
133 | "line": 9,
134 | "column": 7
135 | }
136 | },
137 | "selector": "div"
138 | }
139 | ],
140 | "source": {
141 | "input": {
142 | "file": "styled-components-nesting-expr.js",
143 | "quasis": [
144 | {
145 | "start": 121,
146 | "end": 142
147 | },
148 | {
149 | "start": 149,
150 | "end": 163
151 | }
152 | ]
153 | },
154 | "start": {
155 | "line": 6,
156 | "column": 9
157 | },
158 | "inline": false,
159 | "lang": "template-literal",
160 | "syntax": {}
161 | }
162 | }
163 | ]
164 | }
165 | ],
166 | "source": {
167 | "input": {
168 | "file": "styled-components-nesting-expr.js",
169 | "quasis": [
170 | {
171 | "start": 84,
172 | "end": 105
173 | },
174 | {
175 | "start": 165,
176 | "end": 166
177 | }
178 | ]
179 | },
180 | "start": {
181 | "line": 2,
182 | "column": 36
183 | },
184 | "inline": false,
185 | "lang": "template-literal",
186 | "syntax": {}
187 | }
188 | }
189 | ],
190 | "source": {
191 | "input": {
192 | "file": "styled-components-nesting-expr.js"
193 | },
194 | "start": {
195 | "line": 1,
196 | "column": 1
197 | },
198 | "lang": "jsx"
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting-nesting.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | const Message1 = styled.p`
3 | padding: 10px;
4 | ${css`
5 | color: #b02d00;
6 | ${css`
7 | background: white;
8 | `}
9 | `}
10 | `;
11 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting-nesting.js.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 3,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "styled-components-nesting-nesting.js",
25 | "quasis": [
26 | {
27 | "start": 75,
28 | "end": 93
29 | },
30 | {
31 | "start": 157,
32 | "end": 158
33 | }
34 | ]
35 | },
36 | "end": {
37 | "line": 3,
38 | "column": 15
39 | }
40 | },
41 | "prop": "padding",
42 | "value": "10px"
43 | },
44 | {
45 | "raws": {
46 | "before": "\n\t"
47 | },
48 | "text": "${css`\n\t\tcolor: #b02d00;\n\t\t${css`\n\t\t\tbackground: white;\n\t\t`}\n\t`}",
49 | "type": "literal",
50 | "source": {
51 | "start": {
52 | "line": 4,
53 | "column": 2
54 | },
55 | "input": {
56 | "file": "styled-components-nesting-nesting.js",
57 | "quasis": [
58 | {
59 | "start": 75,
60 | "end": 93
61 | },
62 | {
63 | "start": 157,
64 | "end": 158
65 | }
66 | ]
67 | }
68 | },
69 | "nodes": [
70 | {
71 | "raws": {
72 | "semicolon": false,
73 | "after": "\n\t"
74 | },
75 | "type": "root",
76 | "nodes": [
77 | {
78 | "raws": {
79 | "before": "\n\t\t",
80 | "between": ": "
81 | },
82 | "type": "decl",
83 | "source": {
84 | "start": {
85 | "line": 5,
86 | "column": 3
87 | },
88 | "input": {
89 | "file": "styled-components-nesting-nesting.js",
90 | "quasis": [
91 | {
92 | "start": 99,
93 | "end": 120
94 | },
95 | {
96 | "start": 153,
97 | "end": 155
98 | }
99 | ]
100 | },
101 | "end": {
102 | "line": 5,
103 | "column": 17
104 | }
105 | },
106 | "prop": "color",
107 | "value": "#b02d00"
108 | },
109 | {
110 | "raws": {
111 | "before": "\n\t\t"
112 | },
113 | "text": "${css`\n\t\t\tbackground: white;\n\t\t`}",
114 | "type": "literal",
115 | "source": {
116 | "start": {
117 | "line": 6,
118 | "column": 3
119 | },
120 | "input": {
121 | "file": "styled-components-nesting-nesting.js",
122 | "quasis": [
123 | {
124 | "start": 99,
125 | "end": 120
126 | },
127 | {
128 | "start": 153,
129 | "end": 155
130 | }
131 | ]
132 | }
133 | },
134 | "nodes": [
135 | {
136 | "raws": {
137 | "semicolon": true,
138 | "after": "\n\t\t"
139 | },
140 | "type": "root",
141 | "nodes": [
142 | {
143 | "raws": {
144 | "before": "\n\t\t\t",
145 | "between": ": "
146 | },
147 | "type": "decl",
148 | "source": {
149 | "start": {
150 | "line": 7,
151 | "column": 4
152 | },
153 | "input": {
154 | "file": "styled-components-nesting-nesting.js"
155 | },
156 | "end": {
157 | "line": 7,
158 | "column": 21
159 | }
160 | },
161 | "prop": "background",
162 | "value": "white"
163 | }
164 | ],
165 | "source": {
166 | "input": {
167 | "file": "styled-components-nesting-nesting.js"
168 | },
169 | "start": {
170 | "line": 6,
171 | "column": 9
172 | },
173 | "inline": false,
174 | "lang": "css"
175 | }
176 | }
177 | ]
178 | }
179 | ],
180 | "source": {
181 | "input": {
182 | "file": "styled-components-nesting-nesting.js",
183 | "quasis": [
184 | {
185 | "start": 99,
186 | "end": 120
187 | },
188 | {
189 | "start": 153,
190 | "end": 155
191 | }
192 | ]
193 | },
194 | "start": {
195 | "line": 4,
196 | "column": 8
197 | },
198 | "inline": false,
199 | "lang": "template-literal",
200 | "syntax": {}
201 | }
202 | }
203 | ]
204 | }
205 | ],
206 | "source": {
207 | "input": {
208 | "file": "styled-components-nesting-nesting.js",
209 | "quasis": [
210 | {
211 | "start": 75,
212 | "end": 93
213 | },
214 | {
215 | "start": 157,
216 | "end": 158
217 | }
218 | ]
219 | },
220 | "start": {
221 | "line": 2,
222 | "column": 27
223 | },
224 | "inline": false,
225 | "lang": "template-literal",
226 | "syntax": {}
227 | }
228 | }
229 | ],
230 | "source": {
231 | "input": {
232 | "file": "styled-components-nesting-nesting.js"
233 | },
234 | "start": {
235 | "line": 1,
236 | "column": 1
237 | },
238 | "lang": "jsx"
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting-template-literal.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | const Message = styled.p`
3 | padding: 10px;
4 | ${(props) => `
5 | color: #b02d00;
6 | `}
7 | ${(props2) => `
8 | border-color: red;
9 | `}
10 | `;
11 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting-template-literal.js.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 3,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "styled-components-nesting-template-literal.js",
25 | "quasis": [
26 | {
27 | "start": 74,
28 | "end": 92
29 | },
30 | {
31 | "start": 128,
32 | "end": 130
33 | },
34 | {
35 | "start": 170,
36 | "end": 171
37 | }
38 | ]
39 | },
40 | "end": {
41 | "line": 3,
42 | "column": 15
43 | }
44 | },
45 | "prop": "padding",
46 | "value": "10px"
47 | },
48 | {
49 | "raws": {
50 | "before": "\n\t"
51 | },
52 | "text": "${(props) => `\n\t\tcolor: #b02d00;\n\t`}",
53 | "type": "literal",
54 | "source": {
55 | "start": {
56 | "line": 4,
57 | "column": 2
58 | },
59 | "input": {
60 | "file": "styled-components-nesting-template-literal.js",
61 | "quasis": [
62 | {
63 | "start": 74,
64 | "end": 92
65 | },
66 | {
67 | "start": 128,
68 | "end": 130
69 | },
70 | {
71 | "start": 170,
72 | "end": 171
73 | }
74 | ]
75 | }
76 | }
77 | },
78 | {
79 | "raws": {
80 | "before": "\n\t"
81 | },
82 | "text": "${(props2) => `\n\t\tborder-color: red;\n\t`}",
83 | "type": "literal",
84 | "source": {
85 | "start": {
86 | "line": 7,
87 | "column": 2
88 | },
89 | "input": {
90 | "file": "styled-components-nesting-template-literal.js",
91 | "quasis": [
92 | {
93 | "start": 74,
94 | "end": 92
95 | },
96 | {
97 | "start": 128,
98 | "end": 130
99 | },
100 | {
101 | "start": 170,
102 | "end": 171
103 | }
104 | ]
105 | }
106 | }
107 | }
108 | ],
109 | "source": {
110 | "input": {
111 | "file": "styled-components-nesting-template-literal.js",
112 | "quasis": [
113 | {
114 | "start": 74,
115 | "end": 92
116 | },
117 | {
118 | "start": 128,
119 | "end": 130
120 | },
121 | {
122 | "start": 170,
123 | "end": 171
124 | }
125 | ]
126 | },
127 | "start": {
128 | "line": 2,
129 | "column": 26
130 | },
131 | "inline": false,
132 | "lang": "template-literal",
133 | "syntax": {}
134 | }
135 | }
136 | ],
137 | "source": {
138 | "input": {
139 | "file": "styled-components-nesting-template-literal.js"
140 | },
141 | "start": {
142 | "line": 1,
143 | "column": 1
144 | },
145 | "lang": "jsx"
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | const Message = styled.p`
3 | padding: 10px;
4 | ${(props) => css`
5 | color: #b02d00;
6 | `}
7 | ${(props2) => css`
8 | border-color: red;
9 | `}
10 | `;
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting.js.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 3,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "styled-components-nesting.js",
25 | "quasis": [
26 | {
27 | "start": 74,
28 | "end": 92
29 | },
30 | {
31 | "start": 131,
32 | "end": 133
33 | },
34 | {
35 | "start": 176,
36 | "end": 177
37 | }
38 | ]
39 | },
40 | "end": {
41 | "line": 3,
42 | "column": 15
43 | }
44 | },
45 | "prop": "padding",
46 | "value": "10px"
47 | },
48 | {
49 | "raws": {
50 | "before": "\n\t"
51 | },
52 | "text": "${(props) => css`\n\t\tcolor: #b02d00;\n\t`}",
53 | "type": "literal",
54 | "source": {
55 | "start": {
56 | "line": 4,
57 | "column": 2
58 | },
59 | "input": {
60 | "file": "styled-components-nesting.js",
61 | "quasis": [
62 | {
63 | "start": 74,
64 | "end": 92
65 | },
66 | {
67 | "start": 131,
68 | "end": 133
69 | },
70 | {
71 | "start": 176,
72 | "end": 177
73 | }
74 | ]
75 | }
76 | },
77 | "nodes": [
78 | {
79 | "raws": {
80 | "semicolon": true,
81 | "after": "\n\t"
82 | },
83 | "type": "root",
84 | "nodes": [
85 | {
86 | "raws": {
87 | "before": "\n\t\t",
88 | "between": ": "
89 | },
90 | "type": "decl",
91 | "source": {
92 | "start": {
93 | "line": 5,
94 | "column": 3
95 | },
96 | "input": {
97 | "file": "styled-components-nesting.js"
98 | },
99 | "end": {
100 | "line": 5,
101 | "column": 17
102 | }
103 | },
104 | "prop": "color",
105 | "value": "#b02d00"
106 | }
107 | ],
108 | "source": {
109 | "input": {
110 | "file": "styled-components-nesting.js"
111 | },
112 | "start": {
113 | "line": 4,
114 | "column": 19
115 | },
116 | "inline": false,
117 | "lang": "css"
118 | }
119 | }
120 | ]
121 | },
122 | {
123 | "raws": {
124 | "before": "\n\t"
125 | },
126 | "text": "${(props2) => css`\n\t\tborder-color: red;\n\t`}",
127 | "type": "literal",
128 | "source": {
129 | "start": {
130 | "line": 7,
131 | "column": 2
132 | },
133 | "input": {
134 | "file": "styled-components-nesting.js",
135 | "quasis": [
136 | {
137 | "start": 74,
138 | "end": 92
139 | },
140 | {
141 | "start": 131,
142 | "end": 133
143 | },
144 | {
145 | "start": 176,
146 | "end": 177
147 | }
148 | ]
149 | }
150 | },
151 | "nodes": [
152 | {
153 | "raws": {
154 | "semicolon": true,
155 | "after": "\n\t"
156 | },
157 | "type": "root",
158 | "nodes": [
159 | {
160 | "raws": {
161 | "before": "\n\t\t",
162 | "between": ": "
163 | },
164 | "type": "decl",
165 | "source": {
166 | "start": {
167 | "line": 8,
168 | "column": 3
169 | },
170 | "input": {
171 | "file": "styled-components-nesting.js"
172 | },
173 | "end": {
174 | "line": 8,
175 | "column": 20
176 | }
177 | },
178 | "prop": "border-color",
179 | "value": "red"
180 | }
181 | ],
182 | "source": {
183 | "input": {
184 | "file": "styled-components-nesting.js"
185 | },
186 | "start": {
187 | "line": 7,
188 | "column": 20
189 | },
190 | "inline": false,
191 | "lang": "css"
192 | }
193 | }
194 | ]
195 | }
196 | ],
197 | "source": {
198 | "input": {
199 | "file": "styled-components-nesting.js",
200 | "quasis": [
201 | {
202 | "start": 74,
203 | "end": 92
204 | },
205 | {
206 | "start": 131,
207 | "end": 133
208 | },
209 | {
210 | "start": 176,
211 | "end": 177
212 | }
213 | ]
214 | },
215 | "start": {
216 | "line": 2,
217 | "column": 26
218 | },
219 | "inline": false,
220 | "lang": "template-literal",
221 | "syntax": {}
222 | }
223 | }
224 | ],
225 | "source": {
226 | "input": {
227 | "file": "styled-components-nesting.js"
228 | },
229 | "start": {
230 | "line": 1,
231 | "column": 1
232 | },
233 | "lang": "jsx"
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting2.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | const Message = styled.p`
3 | padding: 10px;
4 | ${css`
5 | color: #b02d00;
6 | `}
7 | `;
8 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting2.js.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 3,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "styled-components-nesting2.js",
25 | "quasis": [
26 | {
27 | "start": 74,
28 | "end": 92
29 | },
30 | {
31 | "start": 120,
32 | "end": 121
33 | }
34 | ]
35 | },
36 | "end": {
37 | "line": 3,
38 | "column": 15
39 | }
40 | },
41 | "prop": "padding",
42 | "value": "10px"
43 | },
44 | {
45 | "raws": {
46 | "before": "\n\t"
47 | },
48 | "text": "${css`\n\t\tcolor: #b02d00;\n\t`}",
49 | "type": "literal",
50 | "source": {
51 | "start": {
52 | "line": 4,
53 | "column": 2
54 | },
55 | "input": {
56 | "file": "styled-components-nesting2.js",
57 | "quasis": [
58 | {
59 | "start": 74,
60 | "end": 92
61 | },
62 | {
63 | "start": 120,
64 | "end": 121
65 | }
66 | ]
67 | }
68 | },
69 | "nodes": [
70 | {
71 | "raws": {
72 | "semicolon": true,
73 | "after": "\n\t"
74 | },
75 | "type": "root",
76 | "nodes": [
77 | {
78 | "raws": {
79 | "before": "\n\t\t",
80 | "between": ": "
81 | },
82 | "type": "decl",
83 | "source": {
84 | "start": {
85 | "line": 5,
86 | "column": 3
87 | },
88 | "input": {
89 | "file": "styled-components-nesting2.js"
90 | },
91 | "end": {
92 | "line": 5,
93 | "column": 17
94 | }
95 | },
96 | "prop": "color",
97 | "value": "#b02d00"
98 | }
99 | ],
100 | "source": {
101 | "input": {
102 | "file": "styled-components-nesting2.js"
103 | },
104 | "start": {
105 | "line": 4,
106 | "column": 8
107 | },
108 | "inline": false,
109 | "lang": "css"
110 | }
111 | }
112 | ]
113 | }
114 | ],
115 | "source": {
116 | "input": {
117 | "file": "styled-components-nesting2.js",
118 | "quasis": [
119 | {
120 | "start": 74,
121 | "end": 92
122 | },
123 | {
124 | "start": 120,
125 | "end": 121
126 | }
127 | ]
128 | },
129 | "start": {
130 | "line": 2,
131 | "column": 26
132 | },
133 | "inline": false,
134 | "lang": "template-literal",
135 | "syntax": {}
136 | }
137 | }
138 | ],
139 | "source": {
140 | "input": {
141 | "file": "styled-components-nesting2.js"
142 | },
143 | "start": {
144 | "line": 1,
145 | "column": 1
146 | },
147 | "lang": "jsx"
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components-nesting3.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | const Message1 = styled.p`padding: 10px;${css`color: #b02d00;`}`;
3 | const Message2 = styled.p`
4 | padding: 10px;
5 |
6 |
7 |
8 | ${css`color: #b02d00;`}`;
9 |
10 | const Button = styled.a`
11 | /* This renders the buttons above... Edit me! */
12 | display: inline-block;
13 | border-radius: 3px;
14 | padding: 0.5rem 0;
15 | margin: 0.5rem 1rem;
16 | width: 11rem;
17 | background: transparent;
18 | color: white;
19 | border: 2px solid white;
20 |
21 | /* The GitHub button is a primary button
22 | * edit this to target it specifically! */
23 | ${props => props.primary && css`
24 | background: white;
25 | color: palevioletred;
26 | `}
27 | `
28 | const Message3 = styled.p`
29 | padding: 10px;
30 | ${props => props.a ? css`color: red;` : css`color: blue;` }`;
31 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const styled = require("styled-components");
3 | const Button = styled.button`
4 | /* Adapt the colours based on primary prop */
5 | background: ${props => props.primary ? "palevioletred" : "white"};
6 | color: ${props => props.primary ? "white" : "palevioletred"};
7 |
8 | font-size: 1em;
9 | margin: 1em;
10 | padding: 0.25em 1em;
11 | border: 2px solid palevioletred;
12 | border-radius: 3px;
13 | `;
14 | require("styled");
15 | const StyledCounter = require("styled-components").div;
16 | StyledCounter(require("styled-components").div.b);
17 | module.exports = Button;
18 |
--------------------------------------------------------------------------------
/test/fixtures/styled-components.js.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": true,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n",
15 | "left": " ",
16 | "right": " "
17 | },
18 | "type": "comment",
19 | "source": {
20 | "start": {
21 | "line": 4,
22 | "column": 1
23 | },
24 | "input": {
25 | "file": "styled-components.js",
26 | "quasis": [
27 | {
28 | "start": 88,
29 | "end": 147
30 | },
31 | {
32 | "start": 200,
33 | "end": 209
34 | },
35 | {
36 | "start": 262,
37 | "end": 368
38 | }
39 | ]
40 | },
41 | "end": {
42 | "line": 4,
43 | "column": 45
44 | }
45 | },
46 | "text": "Adapt the colours based on primary prop"
47 | },
48 | {
49 | "raws": {
50 | "before": "\n",
51 | "between": ": "
52 | },
53 | "type": "decl",
54 | "source": {
55 | "start": {
56 | "line": 5,
57 | "column": 1
58 | },
59 | "input": {
60 | "file": "styled-components.js",
61 | "quasis": [
62 | {
63 | "start": 88,
64 | "end": 147
65 | },
66 | {
67 | "start": 200,
68 | "end": 209
69 | },
70 | {
71 | "start": 262,
72 | "end": 368
73 | }
74 | ]
75 | },
76 | "end": {
77 | "line": 5,
78 | "column": 66
79 | }
80 | },
81 | "prop": "background",
82 | "value": "${props => props.primary ? \"palevioletred\" : \"white\"}"
83 | },
84 | {
85 | "raws": {
86 | "before": "\n",
87 | "between": ": "
88 | },
89 | "type": "decl",
90 | "source": {
91 | "start": {
92 | "line": 6,
93 | "column": 1
94 | },
95 | "input": {
96 | "file": "styled-components.js",
97 | "quasis": [
98 | {
99 | "start": 88,
100 | "end": 147
101 | },
102 | {
103 | "start": 200,
104 | "end": 209
105 | },
106 | {
107 | "start": 262,
108 | "end": 368
109 | }
110 | ]
111 | },
112 | "end": {
113 | "line": 6,
114 | "column": 61
115 | }
116 | },
117 | "prop": "color",
118 | "value": "${props => props.primary ? \"white\" : \"palevioletred\"}"
119 | },
120 | {
121 | "raws": {
122 | "before": "\n\n",
123 | "between": ": "
124 | },
125 | "type": "decl",
126 | "source": {
127 | "start": {
128 | "line": 8,
129 | "column": 1
130 | },
131 | "input": {
132 | "file": "styled-components.js",
133 | "quasis": [
134 | {
135 | "start": 88,
136 | "end": 147
137 | },
138 | {
139 | "start": 200,
140 | "end": 209
141 | },
142 | {
143 | "start": 262,
144 | "end": 368
145 | }
146 | ]
147 | },
148 | "end": {
149 | "line": 8,
150 | "column": 15
151 | }
152 | },
153 | "prop": "font-size",
154 | "value": "1em"
155 | },
156 | {
157 | "raws": {
158 | "before": "\n",
159 | "between": ": "
160 | },
161 | "type": "decl",
162 | "source": {
163 | "start": {
164 | "line": 9,
165 | "column": 1
166 | },
167 | "input": {
168 | "file": "styled-components.js",
169 | "quasis": [
170 | {
171 | "start": 88,
172 | "end": 147
173 | },
174 | {
175 | "start": 200,
176 | "end": 209
177 | },
178 | {
179 | "start": 262,
180 | "end": 368
181 | }
182 | ]
183 | },
184 | "end": {
185 | "line": 9,
186 | "column": 12
187 | }
188 | },
189 | "prop": "margin",
190 | "value": "1em"
191 | },
192 | {
193 | "raws": {
194 | "before": "\n",
195 | "between": ": "
196 | },
197 | "type": "decl",
198 | "source": {
199 | "start": {
200 | "line": 10,
201 | "column": 1
202 | },
203 | "input": {
204 | "file": "styled-components.js",
205 | "quasis": [
206 | {
207 | "start": 88,
208 | "end": 147
209 | },
210 | {
211 | "start": 200,
212 | "end": 209
213 | },
214 | {
215 | "start": 262,
216 | "end": 368
217 | }
218 | ]
219 | },
220 | "end": {
221 | "line": 10,
222 | "column": 20
223 | }
224 | },
225 | "prop": "padding",
226 | "value": "0.25em 1em"
227 | },
228 | {
229 | "raws": {
230 | "before": "\n",
231 | "between": ": "
232 | },
233 | "type": "decl",
234 | "source": {
235 | "start": {
236 | "line": 11,
237 | "column": 1
238 | },
239 | "input": {
240 | "file": "styled-components.js",
241 | "quasis": [
242 | {
243 | "start": 88,
244 | "end": 147
245 | },
246 | {
247 | "start": 200,
248 | "end": 209
249 | },
250 | {
251 | "start": 262,
252 | "end": 368
253 | }
254 | ]
255 | },
256 | "end": {
257 | "line": 11,
258 | "column": 32
259 | }
260 | },
261 | "prop": "border",
262 | "value": "2px solid palevioletred"
263 | },
264 | {
265 | "raws": {
266 | "before": "\n",
267 | "between": ": "
268 | },
269 | "type": "decl",
270 | "source": {
271 | "start": {
272 | "line": 12,
273 | "column": 1
274 | },
275 | "input": {
276 | "file": "styled-components.js",
277 | "quasis": [
278 | {
279 | "start": 88,
280 | "end": 147
281 | },
282 | {
283 | "start": 200,
284 | "end": 209
285 | },
286 | {
287 | "start": 262,
288 | "end": 368
289 | }
290 | ]
291 | },
292 | "end": {
293 | "line": 12,
294 | "column": 19
295 | }
296 | },
297 | "prop": "border-radius",
298 | "value": "3px"
299 | }
300 | ],
301 | "source": {
302 | "input": {
303 | "file": "styled-components.js",
304 | "quasis": [
305 | {
306 | "start": 88,
307 | "end": 147
308 | },
309 | {
310 | "start": 200,
311 | "end": 209
312 | },
313 | {
314 | "start": 262,
315 | "end": 368
316 | }
317 | ]
318 | },
319 | "start": {
320 | "line": 3,
321 | "column": 30
322 | },
323 | "inline": false,
324 | "lang": "template-literal",
325 | "syntax": {}
326 | }
327 | }
328 | ],
329 | "source": {
330 | "input": {
331 | "file": "styled-components.js"
332 | },
333 | "start": {
334 | "line": 1,
335 | "column": 1
336 | },
337 | "lang": "jsx"
338 | }
339 | }
340 |
--------------------------------------------------------------------------------
/test/fixtures/styled-opts.mjs:
--------------------------------------------------------------------------------
1 | import styled from "astroturf";
2 | import Footer from "footer";
3 |
4 | const Button = styled(Footer, { allowAs: true })`
5 | position: relative;
6 | display: flex;
7 | `;
8 |
9 | export default Button;
10 |
--------------------------------------------------------------------------------
/test/fixtures/styled-opts.mjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": true,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 5,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "styled-opts.mjs"
25 | },
26 | "end": {
27 | "line": 5,
28 | "column": 20
29 | }
30 | },
31 | "prop": "position",
32 | "value": "relative"
33 | },
34 | {
35 | "raws": {
36 | "before": "\n\t",
37 | "between": ": "
38 | },
39 | "type": "decl",
40 | "source": {
41 | "start": {
42 | "line": 6,
43 | "column": 2
44 | },
45 | "input": {
46 | "file": "styled-opts.mjs"
47 | },
48 | "end": {
49 | "line": 6,
50 | "column": 15
51 | }
52 | },
53 | "prop": "display",
54 | "value": "flex"
55 | }
56 | ],
57 | "source": {
58 | "input": {
59 | "file": "styled-opts.mjs"
60 | },
61 | "start": {
62 | "line": 4,
63 | "column": 50
64 | },
65 | "inline": false,
66 | "lang": "css",
67 | "syntax": {}
68 | }
69 | }
70 | ],
71 | "source": {
72 | "input": {
73 | "file": "styled-opts.mjs"
74 | },
75 | "start": {
76 | "line": 1,
77 | "column": 1
78 | },
79 | "lang": "jsx"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/fixtures/styled-props.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Component = styled.div.attrs({
4 | className: "someClassName",
5 | })`
6 | color: red;
7 | `;
8 |
9 | export default Component;
10 |
--------------------------------------------------------------------------------
/test/fixtures/styled-props.jsx.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": true,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 6,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "styled-props.jsx"
25 | },
26 | "end": {
27 | "line": 6,
28 | "column": 12
29 | }
30 | },
31 | "prop": "color",
32 | "value": "red"
33 | }
34 | ],
35 | "source": {
36 | "input": {
37 | "file": "styled-props.jsx"
38 | },
39 | "start": {
40 | "line": 5,
41 | "column": 4
42 | },
43 | "inline": false,
44 | "lang": "css",
45 | "syntax": {}
46 | }
47 | }
48 | ],
49 | "source": {
50 | "input": {
51 | "file": "styled-props.jsx"
52 | },
53 | "start": {
54 | "line": 1,
55 | "column": 1
56 | },
57 | "lang": "jsx"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/fixtures/toLocaleString.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | (positionsChecked / scansCount).toLocaleString("de-DE", { style: "percent" });
3 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-decl.mjs:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const prop = "prop";
4 | const value = "value";
5 |
6 | export const Row = styled.div`
7 | prop {
8 | ${prop}: value
9 | }
10 | prop prefix {
11 | prefix-${prop}: value
12 | }
13 | prop suffix {
14 | ${prop}-suffix: value
15 | }
16 | value {
17 | prop: ${value}
18 | }
19 | value prefix {
20 | prop: prefix-${value}
21 | }
22 | value suffix {
23 | prop: ${value}-suffix
24 | }
25 | value semicolon {
26 | prop: ${value};
27 | }
28 | value prefix semicolon {
29 | prop: prefix-${value};
30 | }
31 | value suffix semicolon {
32 | prop: ${value}-suffix;
33 | }
34 | `;
35 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-in-tpl.mjs:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const color = "#ddd";
4 |
5 | export const Row = styled.div`
6 | border-bottom: ${(props) => (props.border ? `1px solid ${color}` : "0")};
7 | `;
8 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-in-tpl.mjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": true,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": ": "
16 | },
17 | "type": "decl",
18 | "source": {
19 | "start": {
20 | "line": 6,
21 | "column": 2
22 | },
23 | "input": {
24 | "file": "tpl-in-tpl.mjs",
25 | "quasis": [
26 | {
27 | "start": 94,
28 | "end": 111
29 | },
30 | {
31 | "start": 168,
32 | "end": 170
33 | }
34 | ]
35 | },
36 | "end": {
37 | "line": 6,
38 | "column": 74
39 | }
40 | },
41 | "prop": "border-bottom",
42 | "value": "${(props) => (props.border ? `1px solid ${color}` : \"0\")}"
43 | }
44 | ],
45 | "source": {
46 | "input": {
47 | "file": "tpl-in-tpl.mjs",
48 | "quasis": [
49 | {
50 | "start": 94,
51 | "end": 111
52 | },
53 | {
54 | "start": 168,
55 | "end": 170
56 | }
57 | ]
58 | },
59 | "start": {
60 | "line": 5,
61 | "column": 31
62 | },
63 | "inline": false,
64 | "lang": "template-literal",
65 | "syntax": {}
66 | }
67 | }
68 | ],
69 | "source": {
70 | "input": {
71 | "file": "tpl-in-tpl.mjs"
72 | },
73 | "start": {
74 | "line": 1,
75 | "column": 1
76 | },
77 | "lang": "jsx"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-selector.mjs:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const selector = "div";
4 |
5 | export const Row = styled.div`
6 | ${selector} a {
7 | display: block
8 | }
9 | a ${selector} {
10 | display: block
11 | }
12 | ${selector}
13 | a {
14 | display: block
15 | }
16 | a
17 | ${selector} {
18 | display: block
19 | }
20 | ${selector},
21 | a {
22 | display: block
23 | }
24 | a,
25 | ${selector} {
26 | display: block
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-selector.mjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n\t",
15 | "between": " ",
16 | "semicolon": false,
17 | "after": "\n\t"
18 | },
19 | "type": "rule",
20 | "nodes": [
21 | {
22 | "raws": {
23 | "before": "\n\t\t",
24 | "between": ": "
25 | },
26 | "type": "decl",
27 | "source": {
28 | "start": {
29 | "line": 7,
30 | "column": 3
31 | },
32 | "input": {
33 | "file": "tpl-selector.mjs",
34 | "quasis": [
35 | {
36 | "start": 96,
37 | "end": 98
38 | },
39 | {
40 | "start": 109,
41 | "end": 137
42 | },
43 | {
44 | "start": 148,
45 | "end": 172
46 | },
47 | {
48 | "start": 183,
49 | "end": 213
50 | },
51 | {
52 | "start": 224,
53 | "end": 248
54 | },
55 | {
56 | "start": 259,
57 | "end": 291
58 | },
59 | {
60 | "start": 302,
61 | "end": 325
62 | }
63 | ]
64 | },
65 | "end": {
66 | "line": 7,
67 | "column": 16
68 | }
69 | },
70 | "prop": "display",
71 | "value": "block"
72 | }
73 | ],
74 | "source": {
75 | "start": {
76 | "line": 6,
77 | "column": 2
78 | },
79 | "input": {
80 | "file": "tpl-selector.mjs",
81 | "quasis": [
82 | {
83 | "start": 96,
84 | "end": 98
85 | },
86 | {
87 | "start": 109,
88 | "end": 137
89 | },
90 | {
91 | "start": 148,
92 | "end": 172
93 | },
94 | {
95 | "start": 183,
96 | "end": 213
97 | },
98 | {
99 | "start": 224,
100 | "end": 248
101 | },
102 | {
103 | "start": 259,
104 | "end": 291
105 | },
106 | {
107 | "start": 302,
108 | "end": 325
109 | }
110 | ]
111 | },
112 | "end": {
113 | "line": 8,
114 | "column": 2
115 | }
116 | },
117 | "selector": "${selector} a"
118 | },
119 | {
120 | "raws": {
121 | "before": "\n\t",
122 | "between": " ",
123 | "semicolon": false,
124 | "after": "\n\t"
125 | },
126 | "type": "rule",
127 | "nodes": [
128 | {
129 | "raws": {
130 | "before": "\n\t\t",
131 | "between": ": "
132 | },
133 | "type": "decl",
134 | "source": {
135 | "start": {
136 | "line": 10,
137 | "column": 3
138 | },
139 | "input": {
140 | "file": "tpl-selector.mjs",
141 | "quasis": [
142 | {
143 | "start": 96,
144 | "end": 98
145 | },
146 | {
147 | "start": 109,
148 | "end": 137
149 | },
150 | {
151 | "start": 148,
152 | "end": 172
153 | },
154 | {
155 | "start": 183,
156 | "end": 213
157 | },
158 | {
159 | "start": 224,
160 | "end": 248
161 | },
162 | {
163 | "start": 259,
164 | "end": 291
165 | },
166 | {
167 | "start": 302,
168 | "end": 325
169 | }
170 | ]
171 | },
172 | "end": {
173 | "line": 10,
174 | "column": 16
175 | }
176 | },
177 | "prop": "display",
178 | "value": "block"
179 | }
180 | ],
181 | "source": {
182 | "start": {
183 | "line": 9,
184 | "column": 2
185 | },
186 | "input": {
187 | "file": "tpl-selector.mjs",
188 | "quasis": [
189 | {
190 | "start": 96,
191 | "end": 98
192 | },
193 | {
194 | "start": 109,
195 | "end": 137
196 | },
197 | {
198 | "start": 148,
199 | "end": 172
200 | },
201 | {
202 | "start": 183,
203 | "end": 213
204 | },
205 | {
206 | "start": 224,
207 | "end": 248
208 | },
209 | {
210 | "start": 259,
211 | "end": 291
212 | },
213 | {
214 | "start": 302,
215 | "end": 325
216 | }
217 | ]
218 | },
219 | "end": {
220 | "line": 11,
221 | "column": 2
222 | }
223 | },
224 | "selector": "a ${selector}"
225 | },
226 | {
227 | "raws": {
228 | "before": "\n\t",
229 | "between": " ",
230 | "semicolon": false,
231 | "after": "\n\t"
232 | },
233 | "type": "rule",
234 | "nodes": [
235 | {
236 | "raws": {
237 | "before": "\n\t\t",
238 | "between": ": "
239 | },
240 | "type": "decl",
241 | "source": {
242 | "start": {
243 | "line": 14,
244 | "column": 3
245 | },
246 | "input": {
247 | "file": "tpl-selector.mjs",
248 | "quasis": [
249 | {
250 | "start": 96,
251 | "end": 98
252 | },
253 | {
254 | "start": 109,
255 | "end": 137
256 | },
257 | {
258 | "start": 148,
259 | "end": 172
260 | },
261 | {
262 | "start": 183,
263 | "end": 213
264 | },
265 | {
266 | "start": 224,
267 | "end": 248
268 | },
269 | {
270 | "start": 259,
271 | "end": 291
272 | },
273 | {
274 | "start": 302,
275 | "end": 325
276 | }
277 | ]
278 | },
279 | "end": {
280 | "line": 14,
281 | "column": 16
282 | }
283 | },
284 | "prop": "display",
285 | "value": "block"
286 | }
287 | ],
288 | "source": {
289 | "start": {
290 | "line": 12,
291 | "column": 2
292 | },
293 | "input": {
294 | "file": "tpl-selector.mjs",
295 | "quasis": [
296 | {
297 | "start": 96,
298 | "end": 98
299 | },
300 | {
301 | "start": 109,
302 | "end": 137
303 | },
304 | {
305 | "start": 148,
306 | "end": 172
307 | },
308 | {
309 | "start": 183,
310 | "end": 213
311 | },
312 | {
313 | "start": 224,
314 | "end": 248
315 | },
316 | {
317 | "start": 259,
318 | "end": 291
319 | },
320 | {
321 | "start": 302,
322 | "end": 325
323 | }
324 | ]
325 | },
326 | "end": {
327 | "line": 15,
328 | "column": 2
329 | }
330 | },
331 | "selector": "${selector}\n\ta"
332 | },
333 | {
334 | "raws": {
335 | "before": "\n\t",
336 | "between": " ",
337 | "semicolon": false,
338 | "after": "\n\t"
339 | },
340 | "type": "rule",
341 | "nodes": [
342 | {
343 | "raws": {
344 | "before": "\n\t\t",
345 | "between": ": "
346 | },
347 | "type": "decl",
348 | "source": {
349 | "start": {
350 | "line": 18,
351 | "column": 3
352 | },
353 | "input": {
354 | "file": "tpl-selector.mjs",
355 | "quasis": [
356 | {
357 | "start": 96,
358 | "end": 98
359 | },
360 | {
361 | "start": 109,
362 | "end": 137
363 | },
364 | {
365 | "start": 148,
366 | "end": 172
367 | },
368 | {
369 | "start": 183,
370 | "end": 213
371 | },
372 | {
373 | "start": 224,
374 | "end": 248
375 | },
376 | {
377 | "start": 259,
378 | "end": 291
379 | },
380 | {
381 | "start": 302,
382 | "end": 325
383 | }
384 | ]
385 | },
386 | "end": {
387 | "line": 18,
388 | "column": 16
389 | }
390 | },
391 | "prop": "display",
392 | "value": "block"
393 | }
394 | ],
395 | "source": {
396 | "start": {
397 | "line": 16,
398 | "column": 2
399 | },
400 | "input": {
401 | "file": "tpl-selector.mjs",
402 | "quasis": [
403 | {
404 | "start": 96,
405 | "end": 98
406 | },
407 | {
408 | "start": 109,
409 | "end": 137
410 | },
411 | {
412 | "start": 148,
413 | "end": 172
414 | },
415 | {
416 | "start": 183,
417 | "end": 213
418 | },
419 | {
420 | "start": 224,
421 | "end": 248
422 | },
423 | {
424 | "start": 259,
425 | "end": 291
426 | },
427 | {
428 | "start": 302,
429 | "end": 325
430 | }
431 | ]
432 | },
433 | "end": {
434 | "line": 19,
435 | "column": 2
436 | }
437 | },
438 | "selector": "a\n\t${selector}"
439 | },
440 | {
441 | "raws": {
442 | "before": "\n\t",
443 | "between": " ",
444 | "semicolon": false,
445 | "after": "\n\t"
446 | },
447 | "type": "rule",
448 | "nodes": [
449 | {
450 | "raws": {
451 | "before": "\n\t\t",
452 | "between": ": "
453 | },
454 | "type": "decl",
455 | "source": {
456 | "start": {
457 | "line": 22,
458 | "column": 3
459 | },
460 | "input": {
461 | "file": "tpl-selector.mjs",
462 | "quasis": [
463 | {
464 | "start": 96,
465 | "end": 98
466 | },
467 | {
468 | "start": 109,
469 | "end": 137
470 | },
471 | {
472 | "start": 148,
473 | "end": 172
474 | },
475 | {
476 | "start": 183,
477 | "end": 213
478 | },
479 | {
480 | "start": 224,
481 | "end": 248
482 | },
483 | {
484 | "start": 259,
485 | "end": 291
486 | },
487 | {
488 | "start": 302,
489 | "end": 325
490 | }
491 | ]
492 | },
493 | "end": {
494 | "line": 22,
495 | "column": 16
496 | }
497 | },
498 | "prop": "display",
499 | "value": "block"
500 | }
501 | ],
502 | "source": {
503 | "start": {
504 | "line": 20,
505 | "column": 2
506 | },
507 | "input": {
508 | "file": "tpl-selector.mjs",
509 | "quasis": [
510 | {
511 | "start": 96,
512 | "end": 98
513 | },
514 | {
515 | "start": 109,
516 | "end": 137
517 | },
518 | {
519 | "start": 148,
520 | "end": 172
521 | },
522 | {
523 | "start": 183,
524 | "end": 213
525 | },
526 | {
527 | "start": 224,
528 | "end": 248
529 | },
530 | {
531 | "start": 259,
532 | "end": 291
533 | },
534 | {
535 | "start": 302,
536 | "end": 325
537 | }
538 | ]
539 | },
540 | "end": {
541 | "line": 23,
542 | "column": 2
543 | }
544 | },
545 | "selector": "${selector},\n\ta"
546 | },
547 | {
548 | "raws": {
549 | "before": "\n\t",
550 | "between": " ",
551 | "semicolon": false,
552 | "after": "\n\t"
553 | },
554 | "type": "rule",
555 | "nodes": [
556 | {
557 | "raws": {
558 | "before": "\n\t\t",
559 | "between": ": "
560 | },
561 | "type": "decl",
562 | "source": {
563 | "start": {
564 | "line": 26,
565 | "column": 3
566 | },
567 | "input": {
568 | "file": "tpl-selector.mjs",
569 | "quasis": [
570 | {
571 | "start": 96,
572 | "end": 98
573 | },
574 | {
575 | "start": 109,
576 | "end": 137
577 | },
578 | {
579 | "start": 148,
580 | "end": 172
581 | },
582 | {
583 | "start": 183,
584 | "end": 213
585 | },
586 | {
587 | "start": 224,
588 | "end": 248
589 | },
590 | {
591 | "start": 259,
592 | "end": 291
593 | },
594 | {
595 | "start": 302,
596 | "end": 325
597 | }
598 | ]
599 | },
600 | "end": {
601 | "line": 26,
602 | "column": 16
603 | }
604 | },
605 | "prop": "display",
606 | "value": "block"
607 | }
608 | ],
609 | "source": {
610 | "start": {
611 | "line": 24,
612 | "column": 2
613 | },
614 | "input": {
615 | "file": "tpl-selector.mjs",
616 | "quasis": [
617 | {
618 | "start": 96,
619 | "end": 98
620 | },
621 | {
622 | "start": 109,
623 | "end": 137
624 | },
625 | {
626 | "start": 148,
627 | "end": 172
628 | },
629 | {
630 | "start": 183,
631 | "end": 213
632 | },
633 | {
634 | "start": 224,
635 | "end": 248
636 | },
637 | {
638 | "start": 259,
639 | "end": 291
640 | },
641 | {
642 | "start": 302,
643 | "end": 325
644 | }
645 | ]
646 | },
647 | "end": {
648 | "line": 27,
649 | "column": 2
650 | }
651 | },
652 | "selector": "a,\n\t${selector}"
653 | }
654 | ],
655 | "source": {
656 | "input": {
657 | "file": "tpl-selector.mjs",
658 | "quasis": [
659 | {
660 | "start": 96,
661 | "end": 98
662 | },
663 | {
664 | "start": 109,
665 | "end": 137
666 | },
667 | {
668 | "start": 148,
669 | "end": 172
670 | },
671 | {
672 | "start": 183,
673 | "end": 213
674 | },
675 | {
676 | "start": 224,
677 | "end": 248
678 | },
679 | {
680 | "start": 259,
681 | "end": 291
682 | },
683 | {
684 | "start": 302,
685 | "end": 325
686 | }
687 | ]
688 | },
689 | "start": {
690 | "line": 5,
691 | "column": 31
692 | },
693 | "inline": false,
694 | "lang": "template-literal",
695 | "syntax": {}
696 | }
697 | }
698 | ],
699 | "source": {
700 | "input": {
701 | "file": "tpl-selector.mjs"
702 | },
703 | "start": {
704 | "line": 1,
705 | "column": 1
706 | },
707 | "lang": "jsx"
708 | }
709 | }
710 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-special.mjs:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const img = "/images/logo.png";
4 |
5 | export const Row = styled.div`
6 | img[${img}] {
7 | background-image: url(${img});
8 | }
9 | img[att="${img}"] {
10 | background-image: url("${img}");
11 | }
12 | `;
13 |
--------------------------------------------------------------------------------
/test/fixtures/tpl-special.mjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "raws": {},
3 | "type": "root",
4 | "nodes": [
5 | {
6 | "raws": {
7 | "semicolon": false,
8 | "after": "\n"
9 | },
10 | "type": "root",
11 | "nodes": [
12 | {
13 | "raws": {
14 | "before": "\n",
15 | "between": " ",
16 | "semicolon": true,
17 | "after": "\n"
18 | },
19 | "type": "rule",
20 | "nodes": [
21 | {
22 | "raws": {
23 | "before": "\n\t",
24 | "between": ": "
25 | },
26 | "type": "decl",
27 | "source": {
28 | "start": {
29 | "line": 7,
30 | "column": 2
31 | },
32 | "input": {
33 | "file": "tpl-special.mjs",
34 | "quasis": [
35 | {
36 | "start": 104,
37 | "end": 109
38 | },
39 | {
40 | "start": 115,
41 | "end": 142
42 | },
43 | {
44 | "start": 148,
45 | "end": 162
46 | },
47 | {
48 | "start": 168,
49 | "end": 197
50 | },
51 | {
52 | "start": 203,
53 | "end": 209
54 | }
55 | ]
56 | },
57 | "end": {
58 | "line": 7,
59 | "column": 31
60 | }
61 | },
62 | "prop": "background-image",
63 | "value": "url(${img})"
64 | }
65 | ],
66 | "source": {
67 | "start": {
68 | "line": 6,
69 | "column": 1
70 | },
71 | "input": {
72 | "file": "tpl-special.mjs",
73 | "quasis": [
74 | {
75 | "start": 104,
76 | "end": 109
77 | },
78 | {
79 | "start": 115,
80 | "end": 142
81 | },
82 | {
83 | "start": 148,
84 | "end": 162
85 | },
86 | {
87 | "start": 168,
88 | "end": 197
89 | },
90 | {
91 | "start": 203,
92 | "end": 209
93 | }
94 | ]
95 | },
96 | "end": {
97 | "line": 8,
98 | "column": 1
99 | }
100 | },
101 | "selector": "img[${img}]"
102 | },
103 | {
104 | "raws": {
105 | "before": "\n",
106 | "between": " ",
107 | "semicolon": true,
108 | "after": "\n"
109 | },
110 | "type": "rule",
111 | "nodes": [
112 | {
113 | "raws": {
114 | "before": "\n\t",
115 | "between": ": "
116 | },
117 | "type": "decl",
118 | "source": {
119 | "start": {
120 | "line": 10,
121 | "column": 2
122 | },
123 | "input": {
124 | "file": "tpl-special.mjs",
125 | "quasis": [
126 | {
127 | "start": 104,
128 | "end": 109
129 | },
130 | {
131 | "start": 115,
132 | "end": 142
133 | },
134 | {
135 | "start": 148,
136 | "end": 162
137 | },
138 | {
139 | "start": 168,
140 | "end": 197
141 | },
142 | {
143 | "start": 203,
144 | "end": 209
145 | }
146 | ]
147 | },
148 | "end": {
149 | "line": 10,
150 | "column": 33
151 | }
152 | },
153 | "prop": "background-image",
154 | "value": "url(\"${img}\")"
155 | }
156 | ],
157 | "source": {
158 | "start": {
159 | "line": 9,
160 | "column": 1
161 | },
162 | "input": {
163 | "file": "tpl-special.mjs",
164 | "quasis": [
165 | {
166 | "start": 104,
167 | "end": 109
168 | },
169 | {
170 | "start": 115,
171 | "end": 142
172 | },
173 | {
174 | "start": 148,
175 | "end": 162
176 | },
177 | {
178 | "start": 168,
179 | "end": 197
180 | },
181 | {
182 | "start": 203,
183 | "end": 209
184 | }
185 | ]
186 | },
187 | "end": {
188 | "line": 11,
189 | "column": 1
190 | }
191 | },
192 | "selector": "img[att=\"${img}\"]"
193 | }
194 | ],
195 | "source": {
196 | "input": {
197 | "file": "tpl-special.mjs",
198 | "quasis": [
199 | {
200 | "start": 104,
201 | "end": 109
202 | },
203 | {
204 | "start": 115,
205 | "end": 142
206 | },
207 | {
208 | "start": 148,
209 | "end": 162
210 | },
211 | {
212 | "start": 168,
213 | "end": 197
214 | },
215 | {
216 | "start": 203,
217 | "end": 209
218 | }
219 | ]
220 | },
221 | "start": {
222 | "line": 5,
223 | "column": 31
224 | },
225 | "inline": false,
226 | "lang": "template-literal",
227 | "syntax": {}
228 | }
229 | }
230 | ],
231 | "source": {
232 | "input": {
233 | "file": "tpl-special.mjs"
234 | },
235 | "start": {
236 | "line": 1,
237 | "column": 1
238 | },
239 | "lang": "jsx"
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/test/glamorous.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const syntax = require('../');
5 |
6 | describe('javascript tests', () => {
7 | it('glamorous', () => {
8 | const filename = require.resolve('./fixtures/glamorous.jsx');
9 | let code = fs.readFileSync(filename);
10 |
11 | const document = syntax.parse(code, {
12 | from: filename,
13 | });
14 |
15 | code = code.toString();
16 |
17 | expect(document.toString(syntax)).toBe(code);
18 | expect(document.nodes).toHaveLength(5);
19 |
20 | document.nodes.forEach((root) => {
21 | expect(root.source).toHaveProperty('input');
22 |
23 | expect(code).toEqual(expect.stringContaining(root.source.input.css));
24 | expect(root.source.input.css.length).toBeLessThan(code.length);
25 | expect(root.source.start.line).toBeGreaterThan(1);
26 |
27 | root.walk((node) => {
28 | expect(node).toHaveProperty('source');
29 |
30 | expect(node.source.input.css).toBe(root.source.input.css);
31 |
32 | expect(node.source).toHaveProperty('start.line');
33 | expect(node.source).toHaveProperty('end.line');
34 | });
35 | });
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/literals.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const syntax = require('../');
5 |
6 | describe('template literals', () => {
7 | it('template literals inside template literals', () => {
8 | const file = require.resolve('./fixtures/tpl-in-tpl.mjs');
9 | let code = fs.readFileSync(file);
10 |
11 | const document = syntax.parse(code, {
12 | from: file,
13 | });
14 |
15 | code = code.toString();
16 | expect(document.toString()).toBe(code);
17 | expect(document.source).toHaveProperty('lang', 'jsx');
18 |
19 | expect(document.nodes).toHaveLength(1);
20 | expect(document.first.nodes).toHaveLength(1);
21 |
22 | document.first.nodes.forEach((decl) => {
23 | expect(decl).toHaveProperty('type', 'decl');
24 | expect(decl).toHaveProperty('prop', 'border-bottom');
25 | expect(decl).toHaveProperty(
26 | 'value',
27 | '${(props) => (props.border ? `1px solid ${color}` : "0")}',
28 | );
29 | });
30 | });
31 |
32 | it('multiline arrow function', () => {
33 | const file = require.resolve('./fixtures/multiline-arrow-function.mjs');
34 | let code = fs.readFileSync(file);
35 |
36 | const document = syntax.parse(code, {
37 | from: file,
38 | });
39 |
40 | code = code.toString();
41 | expect(document.toString()).toBe(code);
42 | expect(document.source).toHaveProperty('lang', 'jsx');
43 |
44 | expect(document.nodes).toHaveLength(1);
45 | expect(document.first.nodes).toHaveLength(1);
46 |
47 | document.first.nodes.forEach((decl) => {
48 | expect(decl).toHaveProperty('type', 'decl');
49 | expect(decl).toHaveProperty('prop', 'color');
50 | expect(decl).toHaveProperty(
51 | 'value', // prettier-ignore
52 | ['${(props) =>', '(props.status === "signed" && "red") ||', '"blue"}'].join('\n\t\t'),
53 | );
54 | });
55 | });
56 |
57 | it('interpolation as the only content of a component', () => {
58 | const file = require.resolve('./fixtures/interpolation-content.mjs');
59 | let code = fs.readFileSync(file);
60 |
61 | const document = syntax.parse(code, {
62 | from: file,
63 | });
64 |
65 | code = code.toString();
66 | expect(document.toString()).toBe(code);
67 | expect(document.source).toHaveProperty('lang', 'jsx');
68 |
69 | expect(document.nodes).toHaveLength(9);
70 | /* eslint-disable jest/no-conditional-expect -- in the current state, the related test fixture
71 | * is likely too complicated to resolve (up to 45 checks). we should consider different approaches
72 | * in the future. for more info, see https://github.com/stylelint/postcss-css-in-js/pull/80
73 | */
74 | document.nodes.forEach((root, i) => {
75 | switch (i) {
76 | case 0: {
77 | expect(root.nodes).toHaveLength(1);
78 | root.nodes.forEach((decl) => {
79 | expect(decl).toHaveProperty('type', 'decl');
80 | expect(decl).toHaveProperty('prop', 'display');
81 | expect(decl).toHaveProperty('value', 'inline-block');
82 | });
83 |
84 | return;
85 | }
86 | case 1:
87 | case 2: {
88 | expect(root.nodes).toHaveLength(2);
89 | expect(root.first).toHaveProperty('type', 'literal');
90 | expect(root.first).toHaveProperty('text', '${buttonStyles}');
91 | expect(root.last).toHaveProperty('type', 'decl');
92 | expect(root.last).toHaveProperty('prop', 'color');
93 | expect(root.last).toHaveProperty('value', 'red');
94 |
95 | return;
96 | }
97 | case 3:
98 | case 4: {
99 | expect(root.nodes).toHaveLength(2);
100 | expect(root.first).toHaveProperty('type', 'decl');
101 | expect(root.first).toHaveProperty('prop', 'color');
102 | expect(root.first).toHaveProperty('value', 'red');
103 | expect(root.last).toHaveProperty('type', 'literal');
104 | expect(root.last).toHaveProperty('text', '${buttonStyles}');
105 | }
106 | }
107 | });
108 | /* eslint-enable jest/no-conditional-expect */
109 | });
110 |
111 | it('selector', () => {
112 | const file = require.resolve('./fixtures/tpl-selector.mjs');
113 | let code = fs.readFileSync(file);
114 |
115 | const document = syntax.parse(code, {
116 | from: file,
117 | });
118 |
119 | code = code.toString();
120 | expect(document.toString()).toBe(code);
121 | expect(document.source).toHaveProperty('lang', 'jsx');
122 |
123 | expect(document.nodes).toHaveLength(1);
124 | expect(document.first.nodes).toHaveLength(6);
125 | document.first.nodes.forEach((rule) => {
126 | expect(rule).toHaveProperty('type', 'rule');
127 | expect(rule).toHaveProperty('selector');
128 | expect(rule.selector).toMatch(/(?:^|\s)\$\{selector\}(?=,|\s|$)/);
129 | });
130 | });
131 |
132 | describe('decl', () => {
133 | const file = require.resolve('./fixtures/tpl-decl.mjs');
134 | const code = fs.readFileSync(file);
135 |
136 | syntax
137 | .parse(code, {
138 | from: file,
139 | })
140 | .first.nodes.forEach((rule) => {
141 | it(`${rule.selector}`, () => {
142 | expect(rule.nodes).toHaveLength(1);
143 | const decl = rule.first;
144 |
145 | expect(decl).toHaveProperty(
146 | 'prop',
147 | /\bprop\b/.test(rule.selector)
148 | ? `${/\bprefix\b/.test(rule.selector) ? 'prefix-' : ''}\${prop}${
149 | /\bsuffix\b/.test(rule.selector) ? '-suffix' : ''
150 | }`
151 | : 'prop',
152 | );
153 | expect(decl).toHaveProperty(
154 | 'value',
155 | /\bvalue\b/.test(rule.selector)
156 | ? `${/\bprefix\b/.test(rule.selector) ? 'prefix-' : ''}\${value}${
157 | /\bsuffix\b/.test(rule.selector) ? '-suffix' : ''
158 | }`
159 | : 'value',
160 | );
161 | });
162 | });
163 | });
164 |
165 | it('non-literals', () => {
166 | const file = require.resolve('./fixtures/tpl-special.mjs');
167 | let code = fs.readFileSync(file);
168 |
169 | const document = syntax.parse(code, {
170 | from: file,
171 | });
172 |
173 | code = code.toString();
174 | expect(document.toString()).toBe(code);
175 | expect(document.source).toHaveProperty('lang', 'jsx');
176 |
177 | document.walk((node) => {
178 | expect(node).toHaveProperty('type');
179 | expect(node.type).not.toBe('literal');
180 | });
181 | });
182 |
183 | describe('template-safe-parse', () => {
184 | [
185 | './fixtures/tpl-in-tpl.mjs',
186 | './fixtures/multiline-arrow-function.mjs',
187 | './fixtures/interpolation-content.mjs',
188 | './fixtures/tpl-selector.mjs',
189 | './fixtures/tpl-decl.mjs',
190 | './fixtures/tpl-special.mjs',
191 | ].forEach((file) => {
192 | it(`${file}`, () => {
193 | file = require.resolve(file);
194 | const code = fs.readFileSync(file);
195 |
196 | expect(() =>
197 | syntax({
198 | css: 'safe-parser',
199 | }).parse(code, {
200 | from: 'styled-safe-parse.js',
201 | }),
202 | ).not.toThrow();
203 | });
204 | });
205 | });
206 | });
207 |
--------------------------------------------------------------------------------
/test/non-style.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const spawnSync = require('child_process').spawnSync;
5 | const files = spawnSync('git', ['ls-files'], { encoding: 'utf8' }).stdout.match(/^.+\.js$/gm);
6 | const syntax = require('../');
7 |
8 | describe('not throw error for non-style js file', () => {
9 | files.forEach((file) => {
10 | it(`${file}`, () => {
11 | const code = fs.readFileSync(file);
12 | const document = syntax.parse(code, {
13 | from: file,
14 | });
15 |
16 | expect(document.source).toHaveProperty('lang', 'jsx');
17 | expect(document.toString()).toBe(code.toString());
18 | });
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/test/react-native.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const syntax = require('../');
5 |
6 | describe('react-native', () => {
7 | it('StyleSheet', () => {
8 | const filename = require.resolve('./fixtures/react-native.mjs');
9 | let code = fs.readFileSync(filename);
10 |
11 | const document = syntax.parse(code, {
12 | from: filename,
13 | });
14 |
15 | code = code.toString();
16 |
17 | expect(document.toString(syntax)).toBe(code);
18 | expect(document.nodes).toHaveLength(1);
19 | expect(document.first.nodes).toHaveLength(1);
20 | expect(document.first.first.nodes).toHaveLength(2);
21 | expect(document.first.first.first).toHaveProperty('type', 'rule');
22 | expect(document.first.first.first).toHaveProperty('selector', 'box');
23 | expect(document.first.first.last).toHaveProperty('type', 'rule');
24 | expect(document.first.first.last).toHaveProperty('selector', 'text');
25 |
26 | document.nodes.forEach((root) => {
27 | expect(root.source).toHaveProperty('input');
28 |
29 | expect(code).toEqual(expect.stringContaining(root.source.input.css));
30 | expect(root.source.input.css.length).toBeLessThan(code.length);
31 | expect(root.source.start.line).toBeGreaterThan(1);
32 |
33 | root.walk((node) => {
34 | expect(node).toHaveProperty('source');
35 |
36 | expect(node.source.input.css).toBe(root.source.input.css);
37 |
38 | expect(node.source).toHaveProperty('start.line');
39 | expect(node.source).toHaveProperty('end.line');
40 | });
41 | });
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/test/react.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const syntax = require('../');
4 |
5 | describe('react', () => {
6 | it('first line indentation handle', () => {
7 | const code = `
8 | export default
![]()
;
15 | `;
16 |
17 | const document = syntax.parse(code, {
18 | from: 'before.js',
19 | });
20 |
21 | expect(document.toString(syntax)).toBe(code);
22 | expect(document.nodes).toHaveLength(1);
23 | expect(document.first.source.input.css).toMatch(/^\s+\{/);
24 | expect(document.first.source.start.column).toBe(1);
25 | expect(document.first.raws.beforeStart).toMatch(/\n$/);
26 | expect(document.first.first.raws.before).toMatch(/^\s+$/);
27 | expect(document.first.first.source.start.column).toBeGreaterThan(1);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/test/styled-components.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const syntax = require('../');
5 |
6 | describe('styled-components', () => {
7 | it('basic', () => {
8 | const file = require.resolve('./fixtures/styled-components');
9 | let code = fs.readFileSync(file);
10 |
11 | const document = syntax.parse(code, {
12 | from: file,
13 | });
14 |
15 | code = code.toString();
16 | expect(document.toString()).toBe(code);
17 | expect(document.source).toHaveProperty('lang', 'jsx');
18 |
19 | expect(document.nodes).toHaveLength(1);
20 | expect(document.first.nodes).toHaveLength(8);
21 |
22 | // this was previously a .forEach over every line within styled.button
23 | // instead, we unwound the loop to satisfy jest/no-conditional-expect
24 | // we expect the first line to be a comment, and the next 7 to be decls
25 | // see https://github.com/stylelint/postcss-css-in-js/pull/80 for more details
26 | expect(document.first.nodes[0]).toHaveProperty('type', 'comment');
27 | expect(document.first.nodes[1]).toHaveProperty('type', 'decl');
28 | expect(document.first.nodes[2]).toHaveProperty('type', 'decl');
29 | expect(document.first.nodes[3]).toHaveProperty('type', 'decl');
30 | expect(document.first.nodes[4]).toHaveProperty('type', 'decl');
31 | expect(document.first.nodes[5]).toHaveProperty('type', 'decl');
32 | expect(document.first.nodes[6]).toHaveProperty('type', 'decl');
33 | expect(document.first.nodes[7]).toHaveProperty('type', 'decl');
34 | });
35 |
36 | it('interpolation with css template literal', () => {
37 | const code = [
38 | "import styled, { css } from 'styled-components';",
39 |
40 | 'const Message = styled.p`',
41 | ' padding: 10px;',
42 |
43 | ' ${css`',
44 | ' color: #b02d00;',
45 | ' `}',
46 | '`;',
47 | ].join('\n');
48 | const document = syntax.parse(code, {
49 | from: undefined,
50 | });
51 |
52 | expect(document.toString()).toBe(code);
53 | expect(document.source).toHaveProperty('lang', 'jsx');
54 | expect(document.nodes).toHaveLength(1);
55 | });
56 |
57 | it('interpolation with two css template literals', () => {
58 | const code = [
59 | "import styled, { css } from 'styled-components';",
60 |
61 | 'const Message = styled.p`',
62 | ' padding: 10px;',
63 |
64 | ' ${(props) => css`',
65 | ' color: #b02d00;',
66 | ' `}',
67 |
68 | ' ${(props2) => css`',
69 | ' border-color: red;',
70 | ' `}',
71 | '`;',
72 | ].join('\n');
73 | const document = syntax.parse(code, {
74 | from: undefined,
75 | });
76 |
77 | expect(document.toString()).toBe(code);
78 | expect(document.source).toHaveProperty('lang', 'jsx');
79 | expect(document.nodes).toHaveLength(1);
80 | });
81 |
82 | it('empty template literal', () => {
83 | // prettier-ignore
84 | const code = [
85 | "function test() {",
86 | " alert`debug`",
87 | " return ``;",
88 | "}",
89 | "",
90 | ].join("\n");
91 | const document = syntax.parse(code, {
92 | from: 'empty_template_literal.js',
93 | });
94 |
95 | expect(document.toString()).toBe(code);
96 | expect(document.source).toHaveProperty('lang', 'jsx');
97 | expect(document.nodes).toHaveLength(0);
98 | });
99 |
100 | it('skip javascript syntax error', () => {
101 | const code = '\\`';
102 | const document = syntax.parse(code, {
103 | from: 'syntax_error.js',
104 | });
105 |
106 | expect(document.toString()).toBe(code);
107 | expect(document.source).toHaveProperty('lang', 'jsx');
108 | expect(document.nodes).toHaveLength(0);
109 | });
110 |
111 | it('skip @babel/traverse error', () => {
112 | const code = 'let a;let a';
113 | const document = syntax.parse(code, {
114 | from: 'traverse_error.js',
115 | });
116 |
117 | expect(document.toString()).toBe(code);
118 | expect(document.source).toHaveProperty('lang', 'jsx');
119 | expect(document.nodes).toHaveLength(0);
120 | });
121 |
122 | it('illegal template literal', () => {
123 | // prettier-ignore
124 | const code = [
125 | "const styled = require(\"styled-components\");",
126 | "styled.div`$\n{display: block}\n${g} {}`",
127 | ].join("\n");
128 | const document = syntax.parse(code, {
129 | from: 'illegal_template_literal.js',
130 | });
131 |
132 | expect(document.toString()).toBe(code);
133 | expect(document.source).toHaveProperty('lang', 'jsx');
134 | expect(document.nodes).toHaveLength(1);
135 | expect(document.first.nodes).toHaveLength(2);
136 | expect(document.first.first).toHaveProperty('type', 'rule');
137 | expect(document.first.first).toHaveProperty('selector', '$');
138 | expect(document.last.last).toHaveProperty('type', 'rule');
139 | expect(document.last.last).toHaveProperty('selector', '${g}');
140 | });
141 |
142 | it('styled.img', () => {
143 | // prettier-ignore
144 | const code = [
145 | "const styled = require(\"styled-components\");",
146 | "const Image1 = styled.img.attrs({ src: 'url' })`",
147 | " bad-selector {",
148 | " color: red;",
149 | " }",
150 | "`;",
151 | ].join("\n");
152 | const root = syntax.parse(code, {
153 | from: 'styled.img.js',
154 | });
155 |
156 | expect(root.toString()).toBe(code);
157 | });
158 |
159 | it('throw CSS syntax error', () => {
160 | // prettier-ignore
161 | const code = [
162 | "const styled = require(\"styled-components\");",
163 | "styled.div`a{`;",
164 | ].join("\n");
165 |
166 | expect(() => {
167 | syntax.parse(code, {
168 | from: 'css_syntax_error.js',
169 | });
170 | }).toThrow('css_syntax_error.js:2:12: Unclosed block');
171 | });
172 |
173 | it('not skip empty template literal', () => {
174 | // prettier-ignore
175 | const code = [
176 | "const styled = require(\"styled-components\");",
177 | "styled.div``;",
178 | ].join("\n");
179 | const root = syntax.parse(code, {
180 | from: 'empty_template_literal.js',
181 | });
182 |
183 | expect(root.toString()).toBe(code);
184 | expect(root.nodes).toHaveLength(1);
185 | });
186 |
187 | it('fix CSS syntax error', () => {
188 | // prettier-ignore
189 | const code = [
190 | "const styled = require(\"styled-components\");",
191 | "styled.div`a{`;",
192 | ].join("\n");
193 | const document = syntax({
194 | css: 'safe-parser',
195 | }).parse(code, {
196 | from: 'postcss-safe-parser.js',
197 | });
198 |
199 | // prettier-ignore
200 | expect(document.toString()).toBe([
201 | "const styled = require(\"styled-components\");",
202 | "styled.div`a{}`;",
203 | ].join("\n"));
204 | expect(document.source).toHaveProperty('lang', 'jsx');
205 | expect(document.nodes).toHaveLength(1);
206 | expect(document.first.nodes).toHaveLength(1);
207 | expect(document.first.first).toHaveProperty('type', 'rule');
208 | expect(document.first.first).toHaveProperty('selector', 'a');
209 | });
210 |
211 | it('fix styled syntax error', () => {
212 | // prettier-ignore
213 | const code = [
214 | "const styled = require(\"styled-components\");",
215 | "styled.div`${ a } {`",
216 | ].join("\n");
217 | const document = syntax({
218 | css: 'safe-parser',
219 | }).parse(code, {
220 | from: 'styled-safe-parse.js',
221 | });
222 |
223 | // prettier-ignore
224 | expect(document.toString()).toBe([
225 | "const styled = require(\"styled-components\");",
226 | "styled.div`${ a } {}`",
227 | ].join("\n"));
228 | expect(document.source).toHaveProperty('lang', 'jsx');
229 | expect(document.nodes).toHaveLength(1);
230 | expect(document.first.nodes).toHaveLength(1);
231 | expect(document.first.first).toHaveProperty('type', 'rule');
232 | expect(document.first.first).toHaveProperty('selector', '${ a }');
233 | });
234 |
235 | it('template literal in prop', () => {
236 | // prettier-ignore
237 | const code = [
238 | "const styled = require(\"styled-components\");",
239 | "styled.div`margin-${/* sc-custom 'left' */ rtlSwitch}: 12.5px;`",
240 | ].join("\n");
241 | const document = syntax.parse(code, {
242 | from: 'template_literal_in_prop.js',
243 | });
244 |
245 | expect(document.toString()).toBe(code);
246 | expect(document.source).toHaveProperty('lang', 'jsx');
247 | expect(document.nodes).toHaveLength(1);
248 | expect(document.first.first).toHaveProperty(
249 | 'prop',
250 | "margin-${/* sc-custom 'left' */ rtlSwitch}",
251 | );
252 | });
253 |
254 | it('lazy assignment', () => {
255 | // prettier-ignore
256 | const code = [
257 | "let myDiv;",
258 | "myDiv = require(\"styled-components\").div;",
259 | "myDiv`a{}`;",
260 | ].join("\n");
261 | const document = syntax.parse(code, {
262 | from: 'lazy_assign.js',
263 | });
264 |
265 | expect(document.toString()).toBe(code);
266 | expect(document.source).toHaveProperty('lang', 'jsx');
267 | expect(document.nodes).toHaveLength(1);
268 | });
269 |
270 | it('lazy assignment without init', () => {
271 | // prettier-ignore
272 | const code = [
273 | "myDiv = require(\"styled-components\").div;",
274 | "myDiv`a{}`;",
275 | ].join("\n");
276 | const document = syntax.parse(code, {
277 | from: 'lazy_assign_no_init.js',
278 | });
279 |
280 | expect(document.toString()).toBe(code);
281 | expect(document.source).toHaveProperty('lang', 'jsx');
282 | expect(document.nodes).toHaveLength(1);
283 | });
284 |
285 | it('array destructuring assignment', () => {
286 | // prettier-ignore
287 | const code = [
288 | "const [",
289 | "\tstyledDiv,",
290 | "\t...c",
291 | "] = require(\"styled-components\");",
292 | "styledDiv`a{}`;",
293 | ].join("\n");
294 | const document = syntax.parse(code, {
295 | from: 'arr_destructuring.js',
296 | });
297 |
298 | expect(document.toString()).toBe(code);
299 | expect(document.source).toHaveProperty('lang', 'jsx');
300 | expect(document.nodes).toHaveLength(1);
301 | });
302 |
303 | it('object destructuring assignment', () => {
304 | // prettier-ignore
305 | const code = [
306 | "const {",
307 | "\t// commit",
308 | "\t['div']: styledDiv,",
309 | "\ta,",
310 | "\t...styled",
311 | "} = require(\"styled-components\");",
312 | "styledDiv`a{}`;",
313 | "styled.div`a{}`;",
314 | "a`a{}`;",
315 | ].join("\n");
316 | const document = syntax.parse(code, {
317 | from: 'obj_destructuring.js',
318 | });
319 |
320 | expect(document.toString()).toBe(code);
321 | expect(document.source).toHaveProperty('lang', 'jsx');
322 | expect(document.nodes).toHaveLength(3);
323 | });
324 | });
325 |
--------------------------------------------------------------------------------
/test/supports.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const syntax = require('../');
6 |
7 | function clean(node) {
8 | if (node.raws) {
9 | delete node.raws.node;
10 | delete node.raws.beforeStart;
11 | delete node.raws.afterEnd;
12 | }
13 |
14 | if (node.source) {
15 | delete node.source.opts;
16 | delete node.source.input.css;
17 | delete node.source.input.hasBOM;
18 | delete node.source.input.parseOptions;
19 | delete node.source.input.templateLiteralStyles;
20 | node.source.input.file = path.basename(node.source.input.file);
21 | }
22 |
23 | delete node.indexes;
24 | delete node.lastEach;
25 | delete node.rawCache;
26 | delete node.document;
27 |
28 | if (node.nodes) {
29 | node.nodes = node.nodes.map(clean);
30 | }
31 |
32 | return node;
33 | }
34 |
35 | describe('should support for each CSS in JS package', () => {
36 | [
37 | 'emotion-10.jsx',
38 | 'glamorous.jsx',
39 | 'interpolation-content.mjs',
40 | 'jsx.jsx',
41 | 'lit-css.mjs',
42 | 'react-emotion.jsx',
43 | 'react-native.mjs',
44 | 'styled-components-nesting-expr.js',
45 | 'styled-components-nesting.js',
46 | 'styled-components-nesting2.js',
47 | 'styled-components-nesting3.js',
48 | 'styled-components-nesting-nesting.js',
49 | 'styled-components-nesting-template-literal.js',
50 | 'styled-components.js',
51 | 'styled-opts.mjs',
52 | 'styled-props.jsx',
53 | 'tpl-decl.mjs',
54 | 'tpl-in-tpl.mjs',
55 | 'tpl-selector.mjs',
56 | 'tpl-special.mjs',
57 | 'material-ui.jsx',
58 | ].forEach((file) => {
59 | it(`${file}`, () => {
60 | file = require.resolve(`./fixtures/${file}`);
61 | const code = fs.readFileSync(file);
62 | const document = syntax.parse(code, {
63 | from: file,
64 | });
65 |
66 | expect(document.source).toHaveProperty('lang', 'jsx');
67 | expect(document.toString()).toBe(code.toString());
68 | expect(document.nodes.length).toBeGreaterThan(0);
69 | const parsed = JSON.stringify(clean(document), 0, '\t');
70 |
71 | // fs.writeFileSync(file + ".json", parsed + "\n");
72 | expect(parsed).toBe(fs.readFileSync(`${file}.json`, 'utf8').trim());
73 | });
74 | });
75 | });
76 |
77 | describe('should support for each CSS in JS package with .babelrc', () => {
78 | beforeAll(() => {
79 | fs.writeFileSync(
80 | '.babelrc',
81 | `{
82 | "plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]]
83 | }`,
84 | );
85 | });
86 |
87 | afterAll(() => {
88 | fs.unlinkSync('.babelrc');
89 | });
90 |
91 | [
92 | 'emotion-10.jsx',
93 | 'glamorous.jsx',
94 | 'interpolation-content.mjs',
95 | 'jsx.jsx',
96 | 'lit-css.mjs',
97 | 'react-emotion.jsx',
98 | 'react-native.mjs',
99 | 'styled-components-nesting-expr.js',
100 | 'styled-components-nesting.js',
101 | 'styled-components-nesting2.js',
102 | 'styled-components-nesting3.js',
103 | 'styled-components-nesting-nesting.js',
104 | 'styled-components-nesting-template-literal.js',
105 | 'styled-components.js',
106 | 'styled-opts.mjs',
107 | 'styled-props.jsx',
108 | 'tpl-decl.mjs',
109 | 'tpl-in-tpl.mjs',
110 | 'tpl-selector.mjs',
111 | 'tpl-special.mjs',
112 | 'material-ui.jsx',
113 | ].forEach((file) => {
114 | it(`${file}`, () => {
115 | file = require.resolve(`./fixtures/${file}`);
116 | const code = fs.readFileSync(file);
117 | const document = syntax.parse(code, {
118 | from: file,
119 | });
120 |
121 | expect(document.source).toHaveProperty('lang', 'jsx');
122 | expect(document.toString()).toBe(code.toString());
123 | expect(document.nodes.length).toBeGreaterThan(0);
124 | const parsed = JSON.stringify(clean(document), 0, '\t');
125 |
126 | // fs.writeFileSync(file + ".json", parsed + "\n");
127 | expect(parsed).toBe(fs.readFileSync(`${file}.json`, 'utf8').trim());
128 | });
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/un-camel-case.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function unCamelCase(str) {
4 | return str.replace(/[\w-]+/g, (s) => {
5 | return /^[A-Z]?[a-z]*(?:[A-Z][a-z]*)+$/.test(s)
6 | ? s
7 | .replace(/[A-Z]/g, (casedStr) => `-${casedStr.toLowerCase()}`)
8 | .replace(/^(o|ms|moz|khtml|epub|(\w+-?)*webkit)(?=-)/i, '-$1') // eslint-disable-line regexp/no-super-linear-backtracking -- TODO: fix
9 | : s;
10 | });
11 | }
12 |
13 | module.exports = unCamelCase;
14 |
--------------------------------------------------------------------------------