├── .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 | [![NPM version](https://img.shields.io/npm/v/@stylelint/postcss-css-in-js.svg)](https://www.npmjs.org/package/@stylelint/postcss-css-in-js) [![Build Status](https://github.com/stylelint/postcss-css-in-js/workflows/CI/badge.svg)](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 |
125 | some other text 126 | Hello 127 |
128 | ); 129 | `); 130 | 131 | expect(parsed.nodes).toHaveLength(3); 132 | }); 133 | 134 | it('works with css object functions', () => { 135 | // Just like the previous test, both inline and variable styles should be parsed. It should 136 | // also parse objects if they are defined in a arrow function, which is for example what is 137 | // used by emotion-theming. 138 | // See also: 139 | // - https://github.com/gucong3000/postcss-jsx/issues/69 140 | // - https://github.com/stylelint/postcss-css-in-js/issues/22 141 | const parsed = syntax.parse(` 142 | import React from 'react'; 143 | 144 | const spreaded = { 145 | width: 100, 146 | padding: 40, 147 | } 148 | 149 | const notInline = theme => ({ 150 | ...spreaded, 151 | margin: 60, 152 | color: theme.color.primary, 153 | }); 154 | 155 | const Component = () => ( 156 |
({ 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 | 20 | 21 | 24 | Some text. 25 | 26 | 29 | Some other text. 30 | 31 | 32 | 33 | ); 34 | const app = document.getElementById("root"); 35 | const myStyle = css` 36 | color: rebeccapurple; 37 | `; 38 | app.classList.add(myStyle); 39 | 40 | export default { 41 | SomeComponent, 42 | AnotherComponent, 43 | }; 44 | -------------------------------------------------------------------------------- /test/fixtures/emotion-10.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": 7, 21 | "column": 2 22 | }, 23 | "input": { 24 | "file": "emotion-10.jsx", 25 | "quasis": [ 26 | { 27 | "start": 145, 28 | "end": 181 29 | }, 30 | { 31 | "start": 204, 32 | "end": 206 33 | } 34 | ] 35 | }, 36 | "end": { 37 | "line": 7, 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": 8, 53 | "column": 2 54 | }, 55 | "input": { 56 | "file": "emotion-10.jsx", 57 | "quasis": [ 58 | { 59 | "start": 145, 60 | "end": 181 61 | }, 62 | { 63 | "start": 204, 64 | "end": 206 65 | } 66 | ] 67 | }, 68 | "end": { 69 | "line": 8, 70 | "column": 43 71 | } 72 | }, 73 | "prop": "background-color", 74 | "value": "${props => props.color}" 75 | } 76 | ], 77 | "source": { 78 | "input": { 79 | "file": "emotion-10.jsx", 80 | "quasis": [ 81 | { 82 | "start": 145, 83 | "end": 181 84 | }, 85 | { 86 | "start": 204, 87 | "end": 206 88 | } 89 | ] 90 | }, 91 | "start": { 92 | "line": 6, 93 | "column": 34 94 | }, 95 | "inline": false, 96 | "lang": "template-literal", 97 | "syntax": {} 98 | } 99 | }, 100 | { 101 | "raws": {}, 102 | "source": { 103 | "input": { 104 | "file": "emotion-10.jsx" 105 | }, 106 | "start": { 107 | "line": 12, 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": "emotion-10.jsx" 147 | }, 148 | "start": { 149 | "line": 13, 150 | "column": 2, 151 | "index": 251 152 | }, 153 | "end": { 154 | "line": 13, 155 | "column": 18, 156 | "index": 267 157 | } 158 | } 159 | } 160 | ], 161 | "source": { 162 | "input": { 163 | "file": "emotion-10.jsx" 164 | }, 165 | "start": { 166 | "line": 12, 167 | "column": 1, 168 | "index": 247 169 | }, 170 | "end": { 171 | "line": 14, 172 | "column": 2, 173 | "index": 271 174 | } 175 | } 176 | } 177 | ] 178 | }, 179 | { 180 | "raws": {}, 181 | "source": { 182 | "input": { 183 | "file": "emotion-10.jsx" 184 | }, 185 | "start": { 186 | "line": 15, 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": "emotion-10.jsx" 226 | }, 227 | "start": { 228 | "line": 15, 229 | "column": 13, 230 | "index": 286 231 | }, 232 | "end": { 233 | "line": 15, 234 | "column": 29, 235 | "index": 302 236 | } 237 | } 238 | } 239 | ], 240 | "source": { 241 | "input": { 242 | "file": "emotion-10.jsx" 243 | }, 244 | "start": { 245 | "line": 15, 246 | "column": 11, 247 | "index": 284 248 | }, 249 | "end": { 250 | "line": 15, 251 | "column": 31, 252 | "index": 304 253 | } 254 | } 255 | } 256 | ] 257 | }, 258 | { 259 | "raws": { 260 | "semicolon": true, 261 | "after": "\n\t\t\t" 262 | }, 263 | "type": "root", 264 | "nodes": [ 265 | { 266 | "raws": { 267 | "before": "\n\t\t\t\t", 268 | "between": ": " 269 | }, 270 | "type": "decl", 271 | "source": { 272 | "start": { 273 | "line": 22, 274 | "column": 5 275 | }, 276 | "input": { 277 | "file": "emotion-10.jsx" 278 | }, 279 | "end": { 280 | "line": 22, 281 | "column": 22 282 | } 283 | }, 284 | "prop": "color", 285 | "value": "sarahgreen" 286 | } 287 | ], 288 | "source": { 289 | "input": { 290 | "file": "emotion-10.jsx" 291 | }, 292 | "start": { 293 | "line": 21, 294 | "column": 19 295 | }, 296 | "inline": false, 297 | "lang": "css", 298 | "syntax": {} 299 | } 300 | }, 301 | { 302 | "raws": {}, 303 | "source": { 304 | "input": { 305 | "file": "emotion-10.jsx" 306 | }, 307 | "start": { 308 | "line": 26, 309 | "column": 14 310 | }, 311 | "inline": false, 312 | "lang": "object-literal", 313 | "syntax": {} 314 | }, 315 | "type": "root", 316 | "nodes": [ 317 | { 318 | "raws": { 319 | "after": "\n\t\t\t", 320 | "semicolon": true, 321 | "before": "" 322 | }, 323 | "type": "object", 324 | "nodes": [ 325 | { 326 | "raws": { 327 | "prop": { 328 | "prefix": "", 329 | "suffix": "", 330 | "raw": "color", 331 | "value": "color" 332 | }, 333 | "value": { 334 | "prefix": "\"", 335 | "suffix": "\"", 336 | "raw": "sarahgreen", 337 | "value": "sarahgreen" 338 | }, 339 | "between": ": ", 340 | "before": "\n\t\t\t\t" 341 | }, 342 | "prop": "color", 343 | "value": "sarahgreen", 344 | "type": "decl", 345 | "source": { 346 | "input": { 347 | "file": "emotion-10.jsx" 348 | }, 349 | "start": { 350 | "line": 27, 351 | "column": 4, 352 | "index": 476 353 | }, 354 | "end": { 355 | "line": 27, 356 | "column": 23, 357 | "index": 495 358 | } 359 | } 360 | } 361 | ], 362 | "source": { 363 | "input": { 364 | "file": "emotion-10.jsx" 365 | }, 366 | "start": { 367 | "line": 26, 368 | "column": 14, 369 | "index": 470 370 | }, 371 | "end": { 372 | "line": 28, 373 | "column": 4, 374 | "index": 501 375 | } 376 | } 377 | } 378 | ] 379 | }, 380 | { 381 | "raws": { 382 | "semicolon": true, 383 | "after": "\n" 384 | }, 385 | "type": "root", 386 | "nodes": [ 387 | { 388 | "raws": { 389 | "before": "\n\t", 390 | "between": ": " 391 | }, 392 | "type": "decl", 393 | "source": { 394 | "start": { 395 | "line": 36, 396 | "column": 2 397 | }, 398 | "input": { 399 | "file": "emotion-10.jsx" 400 | }, 401 | "end": { 402 | "line": 36, 403 | "column": 22 404 | } 405 | }, 406 | "prop": "color", 407 | "value": "rebeccapurple" 408 | } 409 | ], 410 | "source": { 411 | "input": { 412 | "file": "emotion-10.jsx" 413 | }, 414 | "start": { 415 | "line": 35, 416 | "column": 21 417 | }, 418 | "inline": false, 419 | "lang": "css", 420 | "syntax": {} 421 | } 422 | } 423 | ], 424 | "source": { 425 | "input": { 426 | "file": "emotion-10.jsx" 427 | }, 428 | "start": { 429 | "line": 1, 430 | "column": 1 431 | }, 432 | "lang": "jsx" 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /test/fixtures/glamorous.jsx: -------------------------------------------------------------------------------- 1 | import glm from "glamorous"; 2 | 3 | const minWidth = 700; 4 | const a = 1; 5 | const Component1 = glm.a( 6 | /* start */ 7 | { 8 | // stylelint-disable-next-line 9 | "unknownProperty1": "1.8em", // must not trigger any warnings 10 | unknownProperty2: "1.8em", // must not trigger any warnings 11 | [`unknownPropertyaa${a}`]: "1.8em", // must not trigger any warnings 12 | ["unknownProperty" + 1 + "a"]: "1.8em", // must not trigger any warnings 13 | display: "inline-block", 14 | [`@media (min-width: ${minWidth}px)`]: { 15 | color: "red", 16 | }, 17 | // unkown pseudo class selector 18 | ":focused": { 19 | backgroundColor: "red", 20 | }, 21 | "@fontFace": { 22 | "fontFamily": "diyfont", 23 | }, 24 | "@page:first": { 25 | margin: "300px", 26 | }, 27 | "@charset": "utf-8", 28 | }, 29 | // end 30 | ({ primary }) => ({ 31 | unknownProperty: "1.8em", // unknown prop 32 | ...minWidth.length, 33 | color: primary ? "#fff" : "#DA233C", 34 | }) 35 | ); 36 | 37 | const Component2 = glm(Component1, { 38 | displayName: "Component2", 39 | forwardProps: ["shouldRender"], 40 | rootEl: "div", 41 | })(props => ({ 42 | fontFamily: "Arial, Arial, sans-serif", // duplicate font-family names 43 | fontSize: props.big ? 36 : 24, 44 | })); 45 | 46 | const Component3 = glm.div({ 47 | padding: "8px 12px", 48 | ...Component2, 49 | }); 50 | 51 | export default { 52 | Component1, 53 | Component2, 54 | Component3, 55 | }; 56 | -------------------------------------------------------------------------------- /test/fixtures/interpolation-content.mjs: -------------------------------------------------------------------------------- 1 | import styled, { css } from "styled-components"; 2 | 3 | export const buttonStyles = css` 4 | display: inline-block; 5 | `; 6 | 7 | export const ButtonStyled1 = styled.button` 8 | ${buttonStyles} 9 | color: red; 10 | `; 11 | 12 | export const ButtonStyled2 = styled.button` 13 | ${buttonStyles}; 14 | color: red; 15 | `; 16 | 17 | export const ButtonStyled3 = styled.button` 18 | ; 19 | color: red; 20 | ${buttonStyles} 21 | `; 22 | 23 | export const ButtonStyled4 = styled.button` 24 | ; 25 | color: red; 26 | ${buttonStyles}; 27 | `; 28 | 29 | export const ButtonStyled5 = styled.button` 30 | ${buttonStyles 31 | } 32 | color: red; 33 | `; 34 | 35 | export const ButtonStyled6 = styled.button` 36 | ${buttonStyles 37 | }; 38 | color: red; 39 | `; 40 | 41 | export const ButtonStyled7 = styled.button` 42 | ; 43 | color: red; 44 | ${buttonStyles 45 | } 46 | `; 47 | 48 | export const ButtonStyled8 = styled.button` 49 | ; 50 | color: red; 51 | ${buttonStyles 52 | }; 53 | `; 54 | -------------------------------------------------------------------------------- /test/fixtures/interpolation-content.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": 4, 21 | "column": 2 22 | }, 23 | "input": { 24 | "file": "interpolation-content.mjs" 25 | }, 26 | "end": { 27 | "line": 4, 28 | "column": 23 29 | } 30 | }, 31 | "prop": "display", 32 | "value": "inline-block" 33 | } 34 | ], 35 | "source": { 36 | "input": { 37 | "file": "interpolation-content.mjs" 38 | }, 39 | "start": { 40 | "line": 3, 41 | "column": 33 42 | }, 43 | "inline": false, 44 | "lang": "css", 45 | "syntax": {} 46 | } 47 | }, 48 | { 49 | "raws": { 50 | "semicolon": true, 51 | "after": "\n" 52 | }, 53 | "type": "root", 54 | "nodes": [ 55 | { 56 | "raws": { 57 | "before": "\n\t" 58 | }, 59 | "text": "${buttonStyles}", 60 | "type": "literal", 61 | "source": { 62 | "start": { 63 | "line": 8, 64 | "column": 2 65 | }, 66 | "input": { 67 | "file": "interpolation-content.mjs", 68 | "quasis": [ 69 | { 70 | "start": 154, 71 | "end": 156 72 | }, 73 | { 74 | "start": 171, 75 | "end": 185 76 | } 77 | ] 78 | } 79 | } 80 | }, 81 | { 82 | "raws": { 83 | "before": "\n\t", 84 | "between": ": " 85 | }, 86 | "type": "decl", 87 | "source": { 88 | "start": { 89 | "line": 9, 90 | "column": 2 91 | }, 92 | "input": { 93 | "file": "interpolation-content.mjs", 94 | "quasis": [ 95 | { 96 | "start": 154, 97 | "end": 156 98 | }, 99 | { 100 | "start": 171, 101 | "end": 185 102 | } 103 | ] 104 | }, 105 | "end": { 106 | "line": 9, 107 | "column": 12 108 | } 109 | }, 110 | "prop": "color", 111 | "value": "red" 112 | } 113 | ], 114 | "source": { 115 | "input": { 116 | "file": "interpolation-content.mjs", 117 | "quasis": [ 118 | { 119 | "start": 154, 120 | "end": 156 121 | }, 122 | { 123 | "start": 171, 124 | "end": 185 125 | } 126 | ] 127 | }, 128 | "start": { 129 | "line": 7, 130 | "column": 44 131 | }, 132 | "inline": false, 133 | "lang": "template-literal", 134 | "syntax": {} 135 | } 136 | }, 137 | { 138 | "raws": { 139 | "semicolon": true, 140 | "after": "\n" 141 | }, 142 | "type": "root", 143 | "nodes": [ 144 | { 145 | "raws": { 146 | "before": "\n\t", 147 | "ownSemicolon": ";" 148 | }, 149 | "text": "${buttonStyles}", 150 | "type": "literal", 151 | "source": { 152 | "start": { 153 | "line": 13, 154 | "column": 2 155 | }, 156 | "input": { 157 | "file": "interpolation-content.mjs", 158 | "quasis": [ 159 | { 160 | "start": 232, 161 | "end": 234 162 | }, 163 | { 164 | "start": 249, 165 | "end": 264 166 | } 167 | ] 168 | } 169 | } 170 | }, 171 | { 172 | "raws": { 173 | "before": "\n\t", 174 | "between": ": " 175 | }, 176 | "type": "decl", 177 | "source": { 178 | "start": { 179 | "line": 14, 180 | "column": 2 181 | }, 182 | "input": { 183 | "file": "interpolation-content.mjs", 184 | "quasis": [ 185 | { 186 | "start": 232, 187 | "end": 234 188 | }, 189 | { 190 | "start": 249, 191 | "end": 264 192 | } 193 | ] 194 | }, 195 | "end": { 196 | "line": 14, 197 | "column": 12 198 | } 199 | }, 200 | "prop": "color", 201 | "value": "red" 202 | } 203 | ], 204 | "source": { 205 | "input": { 206 | "file": "interpolation-content.mjs", 207 | "quasis": [ 208 | { 209 | "start": 232, 210 | "end": 234 211 | }, 212 | { 213 | "start": 249, 214 | "end": 264 215 | } 216 | ] 217 | }, 218 | "start": { 219 | "line": 12, 220 | "column": 44 221 | }, 222 | "inline": false, 223 | "lang": "template-literal", 224 | "syntax": {} 225 | } 226 | }, 227 | { 228 | "raws": { 229 | "semicolon": false, 230 | "after": "\n" 231 | }, 232 | "type": "root", 233 | "nodes": [ 234 | { 235 | "raws": { 236 | "before": "\n;\n\t", 237 | "between": ": " 238 | }, 239 | "type": "decl", 240 | "source": { 241 | "start": { 242 | "line": 19, 243 | "column": 2 244 | }, 245 | "input": { 246 | "file": "interpolation-content.mjs", 247 | "quasis": [ 248 | { 249 | "start": 311, 250 | "end": 328 251 | }, 252 | { 253 | "start": 343, 254 | "end": 344 255 | } 256 | ] 257 | }, 258 | "end": { 259 | "line": 19, 260 | "column": 12 261 | } 262 | }, 263 | "prop": "color", 264 | "value": "red" 265 | }, 266 | { 267 | "raws": { 268 | "before": "\n\t" 269 | }, 270 | "text": "${buttonStyles}", 271 | "type": "literal", 272 | "source": { 273 | "start": { 274 | "line": 20, 275 | "column": 2 276 | }, 277 | "input": { 278 | "file": "interpolation-content.mjs", 279 | "quasis": [ 280 | { 281 | "start": 311, 282 | "end": 328 283 | }, 284 | { 285 | "start": 343, 286 | "end": 344 287 | } 288 | ] 289 | } 290 | } 291 | } 292 | ], 293 | "source": { 294 | "input": { 295 | "file": "interpolation-content.mjs", 296 | "quasis": [ 297 | { 298 | "start": 311, 299 | "end": 328 300 | }, 301 | { 302 | "start": 343, 303 | "end": 344 304 | } 305 | ] 306 | }, 307 | "start": { 308 | "line": 17, 309 | "column": 44 310 | }, 311 | "inline": false, 312 | "lang": "template-literal", 313 | "syntax": {} 314 | } 315 | }, 316 | { 317 | "raws": { 318 | "semicolon": false, 319 | "after": "\n" 320 | }, 321 | "type": "root", 322 | "nodes": [ 323 | { 324 | "raws": { 325 | "before": "\n;\n\t", 326 | "between": ": " 327 | }, 328 | "type": "decl", 329 | "source": { 330 | "start": { 331 | "line": 25, 332 | "column": 2 333 | }, 334 | "input": { 335 | "file": "interpolation-content.mjs", 336 | "quasis": [ 337 | { 338 | "start": 391, 339 | "end": 408 340 | }, 341 | { 342 | "start": 423, 343 | "end": 425 344 | } 345 | ] 346 | }, 347 | "end": { 348 | "line": 25, 349 | "column": 12 350 | } 351 | }, 352 | "prop": "color", 353 | "value": "red" 354 | }, 355 | { 356 | "raws": { 357 | "before": "\n\t", 358 | "ownSemicolon": ";" 359 | }, 360 | "text": "${buttonStyles}", 361 | "type": "literal", 362 | "source": { 363 | "start": { 364 | "line": 26, 365 | "column": 2 366 | }, 367 | "input": { 368 | "file": "interpolation-content.mjs", 369 | "quasis": [ 370 | { 371 | "start": 391, 372 | "end": 408 373 | }, 374 | { 375 | "start": 423, 376 | "end": 425 377 | } 378 | ] 379 | } 380 | } 381 | } 382 | ], 383 | "source": { 384 | "input": { 385 | "file": "interpolation-content.mjs", 386 | "quasis": [ 387 | { 388 | "start": 391, 389 | "end": 408 390 | }, 391 | { 392 | "start": 423, 393 | "end": 425 394 | } 395 | ] 396 | }, 397 | "start": { 398 | "line": 23, 399 | "column": 44 400 | }, 401 | "inline": false, 402 | "lang": "template-literal", 403 | "syntax": {} 404 | } 405 | }, 406 | { 407 | "raws": { 408 | "semicolon": true, 409 | "after": "\n" 410 | }, 411 | "type": "root", 412 | "nodes": [ 413 | { 414 | "raws": { 415 | "before": "\n\t" 416 | }, 417 | "text": "${buttonStyles\n\t}", 418 | "type": "literal", 419 | "source": { 420 | "start": { 421 | "line": 30, 422 | "column": 2 423 | }, 424 | "input": { 425 | "file": "interpolation-content.mjs", 426 | "quasis": [ 427 | { 428 | "start": 472, 429 | "end": 474 430 | }, 431 | { 432 | "start": 491, 433 | "end": 505 434 | } 435 | ] 436 | } 437 | } 438 | }, 439 | { 440 | "raws": { 441 | "before": "\n\t", 442 | "between": ": " 443 | }, 444 | "type": "decl", 445 | "source": { 446 | "start": { 447 | "line": 32, 448 | "column": 2 449 | }, 450 | "input": { 451 | "file": "interpolation-content.mjs", 452 | "quasis": [ 453 | { 454 | "start": 472, 455 | "end": 474 456 | }, 457 | { 458 | "start": 491, 459 | "end": 505 460 | } 461 | ] 462 | }, 463 | "end": { 464 | "line": 32, 465 | "column": 12 466 | } 467 | }, 468 | "prop": "color", 469 | "value": "red" 470 | } 471 | ], 472 | "source": { 473 | "input": { 474 | "file": "interpolation-content.mjs", 475 | "quasis": [ 476 | { 477 | "start": 472, 478 | "end": 474 479 | }, 480 | { 481 | "start": 491, 482 | "end": 505 483 | } 484 | ] 485 | }, 486 | "start": { 487 | "line": 29, 488 | "column": 44 489 | }, 490 | "inline": false, 491 | "lang": "template-literal", 492 | "syntax": {} 493 | } 494 | }, 495 | { 496 | "raws": { 497 | "semicolon": true, 498 | "after": "\n" 499 | }, 500 | "type": "root", 501 | "nodes": [ 502 | { 503 | "raws": { 504 | "before": "\n\t", 505 | "ownSemicolon": ";" 506 | }, 507 | "text": "${buttonStyles\n\t}", 508 | "type": "literal", 509 | "source": { 510 | "start": { 511 | "line": 36, 512 | "column": 2 513 | }, 514 | "input": { 515 | "file": "interpolation-content.mjs", 516 | "quasis": [ 517 | { 518 | "start": 552, 519 | "end": 554 520 | }, 521 | { 522 | "start": 571, 523 | "end": 586 524 | } 525 | ] 526 | } 527 | } 528 | }, 529 | { 530 | "raws": { 531 | "before": "\n\t", 532 | "between": ": " 533 | }, 534 | "type": "decl", 535 | "source": { 536 | "start": { 537 | "line": 38, 538 | "column": 2 539 | }, 540 | "input": { 541 | "file": "interpolation-content.mjs", 542 | "quasis": [ 543 | { 544 | "start": 552, 545 | "end": 554 546 | }, 547 | { 548 | "start": 571, 549 | "end": 586 550 | } 551 | ] 552 | }, 553 | "end": { 554 | "line": 38, 555 | "column": 12 556 | } 557 | }, 558 | "prop": "color", 559 | "value": "red" 560 | } 561 | ], 562 | "source": { 563 | "input": { 564 | "file": "interpolation-content.mjs", 565 | "quasis": [ 566 | { 567 | "start": 552, 568 | "end": 554 569 | }, 570 | { 571 | "start": 571, 572 | "end": 586 573 | } 574 | ] 575 | }, 576 | "start": { 577 | "line": 35, 578 | "column": 44 579 | }, 580 | "inline": false, 581 | "lang": "template-literal", 582 | "syntax": {} 583 | } 584 | }, 585 | { 586 | "raws": { 587 | "semicolon": false, 588 | "after": "\n" 589 | }, 590 | "type": "root", 591 | "nodes": [ 592 | { 593 | "raws": { 594 | "before": "\n;\n\t", 595 | "between": ": " 596 | }, 597 | "type": "decl", 598 | "source": { 599 | "start": { 600 | "line": 43, 601 | "column": 2 602 | }, 603 | "input": { 604 | "file": "interpolation-content.mjs", 605 | "quasis": [ 606 | { 607 | "start": 633, 608 | "end": 650 609 | }, 610 | { 611 | "start": 667, 612 | "end": 668 613 | } 614 | ] 615 | }, 616 | "end": { 617 | "line": 43, 618 | "column": 12 619 | } 620 | }, 621 | "prop": "color", 622 | "value": "red" 623 | }, 624 | { 625 | "raws": { 626 | "before": "\n\t" 627 | }, 628 | "text": "${buttonStyles\n\t}", 629 | "type": "literal", 630 | "source": { 631 | "start": { 632 | "line": 44, 633 | "column": 2 634 | }, 635 | "input": { 636 | "file": "interpolation-content.mjs", 637 | "quasis": [ 638 | { 639 | "start": 633, 640 | "end": 650 641 | }, 642 | { 643 | "start": 667, 644 | "end": 668 645 | } 646 | ] 647 | } 648 | } 649 | } 650 | ], 651 | "source": { 652 | "input": { 653 | "file": "interpolation-content.mjs", 654 | "quasis": [ 655 | { 656 | "start": 633, 657 | "end": 650 658 | }, 659 | { 660 | "start": 667, 661 | "end": 668 662 | } 663 | ] 664 | }, 665 | "start": { 666 | "line": 41, 667 | "column": 44 668 | }, 669 | "inline": false, 670 | "lang": "template-literal", 671 | "syntax": {} 672 | } 673 | }, 674 | { 675 | "raws": { 676 | "semicolon": false, 677 | "after": "\n" 678 | }, 679 | "type": "root", 680 | "nodes": [ 681 | { 682 | "raws": { 683 | "before": "\n;\n\t", 684 | "between": ": " 685 | }, 686 | "type": "decl", 687 | "source": { 688 | "start": { 689 | "line": 50, 690 | "column": 2 691 | }, 692 | "input": { 693 | "file": "interpolation-content.mjs", 694 | "quasis": [ 695 | { 696 | "start": 715, 697 | "end": 732 698 | }, 699 | { 700 | "start": 749, 701 | "end": 751 702 | } 703 | ] 704 | }, 705 | "end": { 706 | "line": 50, 707 | "column": 12 708 | } 709 | }, 710 | "prop": "color", 711 | "value": "red" 712 | }, 713 | { 714 | "raws": { 715 | "before": "\n\t", 716 | "ownSemicolon": ";" 717 | }, 718 | "text": "${buttonStyles\n\t}", 719 | "type": "literal", 720 | "source": { 721 | "start": { 722 | "line": 51, 723 | "column": 2 724 | }, 725 | "input": { 726 | "file": "interpolation-content.mjs", 727 | "quasis": [ 728 | { 729 | "start": 715, 730 | "end": 732 731 | }, 732 | { 733 | "start": 749, 734 | "end": 751 735 | } 736 | ] 737 | } 738 | } 739 | } 740 | ], 741 | "source": { 742 | "input": { 743 | "file": "interpolation-content.mjs", 744 | "quasis": [ 745 | { 746 | "start": 715, 747 | "end": 732 748 | }, 749 | { 750 | "start": 749, 751 | "end": 751 752 | } 753 | ] 754 | }, 755 | "start": { 756 | "line": 48, 757 | "column": 44 758 | }, 759 | "inline": false, 760 | "lang": "template-literal", 761 | "syntax": {} 762 | } 763 | } 764 | ], 765 | "source": { 766 | "input": { 767 | "file": "interpolation-content.mjs" 768 | }, 769 | "start": { 770 | "line": 1, 771 | "column": 1 772 | }, 773 | "lang": "jsx" 774 | } 775 | } 776 | -------------------------------------------------------------------------------- /test/fixtures/jsx.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | /* eslint comma-dangle: ["error", "never"] */ 3 | /* global notExist */ 4 | const imgUrl = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"; 5 | const baseStyle = { 6 | color: "blue" 7 | }; 8 | let divStyle; 9 | require(); 10 | 11 | function HelloWorldComponent () { 12 | divStyle = { 13 | backgroundImage: `url(${imgUrl})`, 14 | ...baseStyle 15 | }; 16 | return
17 | Hello World! 18 | Hello World! 19 |
; 20 | } 21 | 22 | divStyle = { 23 | backgroundImage: `url(${imgUrl})` 24 | }; 25 | 26 | const App = props => ( 27 |
42 | ); 43 | 44 | function ObjectShorthandComponent({color}) { 45 | return
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 | --------------------------------------------------------------------------------