Skip to content
This repository was archived by the owner on May 14, 2021. It is now read-only.

The feature to fix 'em all! #70

Merged
merged 15 commits into from
Aug 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 117 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `stylelint-processor-styled-components`

Lint the CSS in your [styled components](https://github.com/styled-components/styled-components) with [stylelint](http://stylelint.io/)!
Lint your [styled components](https://github.com/styled-components/styled-components) with [stylelint](http://stylelint.io/)!

[![Build Status][build-badge]][build-url]
[![Coverage Status][coverage-badge]][coverage-url]
Expand All @@ -9,21 +9,23 @@ Lint the CSS in your [styled components](https://github.com/styled-components/st

![Video of project in use](http://imgur.com/br9zdHb.gif)

**NOTE**: This is currently in beta. We're getting close to being fully production ready, and have now covered most normal use cases. There are still some edge cases though, but we are working hard on getting them fixed for a v1.0 release in the near future. Please keep using it and submit bug reports!

## Usage

### Installation

You need:

- `stylelint` (duh)
- This processor (to add `styled-components` support)
- The [stylelint-config-styled-components-processor](https://github.com/styled-components/stylelint-config-styled-components-processor) shareable config
- The standard config for stylelint (or any config you like)
- This processor, to extract styles from `styled-components`
- The [`stylelint-config-styled-components-processor`](https://github.com/styled-components/stylelint-config-styled-components-processor) to disable stylelint rules that clash with `styled-components`
- Your favorite `stylelint` config! (for example [`stylelint-config-standard`](https://github.com/stylelint/stylelint-config-standard))

```
npm install --save-dev stylelint-processor-styled-components stylelint-config-styled-components-processor stylelint stylelint-config-standard
(npm install --save-dev
stylelint
stylelint-processor-styled-components
stylelint-config-styled-components-processor
stylelint-config-standard)
```

### Setup
Expand All @@ -41,90 +43,154 @@ Add a `.stylelintrc` file to the root of your project:
}
```

> Setting the `syntax` to `scss` is needed for nesting and interpolation support!

Then you need to actually run `stylelint`.
> **NOTE:** Setting the `syntax` to `scss` is needed for nesting and interpolation support!

Add a `lint:css` script to your `package.json`. This script will run `stylelint` with a path to all of your files containing `styled-components` code:
Then you need to run `stylelint`. Add a `lint:css` script to your `package.json` which runs `stylelint` with a glob to all of your styled components:

```JSON
{
"scripts": {
"lint:css": "stylelint './components/**/*.js'"
"lint:css": "stylelint './src/**/*.js'"
}
}
```

> **NOTE:** Don't worry about passing in files that don't contain any styled-components code – we take care of that.
> **NOTE:** The processor ignores javascript files that don't contain any `styled-components`, so don't worry about being too broad as long as you restrict it to javascript (or TypeScript).

Now you can lint your CSS by running this script! 🎉
Now you can lint your CSS by running the script! 🎉

```
npm run lint:css
```

#### Webpack

If you want to lint on build, rather than as a separate command, you can use the [`stylelint-custom-processor-loader`](https://github.com/emilgoldsmith/stylelint-custom-processor-loader) for webpack.

### Processor specific stylelint rules

When using this processor a couple of stylelint rules throw errors that you cannot prevent. Like
'[no-empty-source](https://stylelint.io/user-guide/rules/no-empty-source)' or
'[no-missing-end-of-source-newline](https://stylelint.io/user-guide/rules/no-missing-end-of-source-newline)'.
When using this processor a couple of stylelint rules throw errors that cannot be prevented, like [`no-empty-source`](https://stylelint.io/user-guide/rules/no-empty-source) or [`no-missing-end-of-source-newline`](https://stylelint.io/user-guide/rules/no-missing-end-of-source-newline). There's also a couple rules which we need to enforce, like [`no-vendor-prefix` rules](https://stylelint.io/user-guide/rules/property-no-vendor-prefix). (`styled-components` automatically vendor prefixes your code, so you don't need to do it manually)

The [stylelint-config-styled-components-processor](https://github.com/styled-components/stylelint-config-styled-components-processor)
shareable config will automatically disable rules that cause unresolvable conflicts. Besides those
rules vendor prefixed [properties](https://stylelint.io/user-guide/rules/property-no-vendor-prefix)
and [values](https://stylelint.io/user-guide/rules/value-no-vendor-prefix) will throw an error since
styled-components automatically generates vendor prefixes for your css. Note that if you want to
change any of these rules you can always override them in your stylelint config.
The [`stylelint-config-styled-components-processor`](https://github.com/styled-components/stylelint-config-styled-components-processor) will automatically disable rules that cause conflicts.

### Webpack
> **NOTE:** You can override rules defined in shared configs in your custom `.stylelintrc`.

For use with Webpack you can use the [`stylelint-custom-processor-loader`](https://github.com/emilgoldsmith/stylelint-custom-processor-loader).
### Interpolation tagging

### Syntax notes
#### Turning rules off from within your CSS
Sometimes `stylelint` can throw an error (e.g. `CssSyntaxError`) even though nothing is wrong with your CSS. This is often due to an interpolation, more specifically the fact that the processor doesn't know what you're interpolating.

Turning off rules with `stylelint-disable`-like comments (see the [stylelint documentation](https://stylelint.io/user-guide/configuration/#turning-rules-off-from-within-your-css) for all allowed syntax) is fully supported inside and outside of the tagged template literals, do note though that what actually happens behind the scene is that all `stylelint-(disable|enable)` comments are moved into the compiled css that is actually linted, so something like this:
A simplified example:

```js
const something = 'background';

const Button = styled.div`
${something}: papayawhip;
`
```

When you have interpolations in your styles the processor can't know what they are, so it makes a good guess and replaces them with a syntactically equivalent placeholder value. Since `stylelint` is not a code flow analysis tool this doesn't cover all edge cases and the processor will get it wrong every now and then.

Interpolation tagging allows you to tell the processor what an interpolation is in case it guesses wrong; it can then replace the interpolation with a syntactically correct value based on your tag.

For example:

```js
/* stylelint-disable */
import React from 'react';
import styled from 'styled-components';
const something = 'background';

const Button = styled.div`
// Tell the processor that "something" is a property
${/* sc-prop */ something}: papayawhip;
`
```

Now the processor knows that the `something` interpolation is a property, and it can replace the interpolation with a property for linting.

To tag an interpolation add a comment at either the start or the end of the interpolation. (`${/* sc-tag */ foo}` or `${bar /* sc-tag */}`) Tags start with `sc-` and, if specified, a tag overrides the processors guess about what the interpolation is.

#### Tags

The full list of supported tags:

- `sc-block`
- `sc-selector`
- `sc-declaration`
- `sc-property`
- `sc-value`

> **NOTE:** If you are in doubt of the vocabulary you can refer to [this CSS vocabulary list](http://apps.workflower.fi/vocabs/css/en) with examples.

For example, when you interpolate another styled component, what you really interpolate is its unique selector. Since the processor doesn't know that, you can tell it to replace it with a selector when linting:

```js
const Wrapper = styled.div`
/* stylelint-disable */
background-color: red;
${/* sc-selector */ Button} {
color: red;
}
`;
```
or even
```js
/* stylelint-disable */
import React from 'react';
import styled from 'styled-components';

You can also use shorthand tags to avoid cluttering the code. For example:

```js
const Wrapper = styled.div`
/* stylelint-disable-next-line */
background-color: red;
${/* sc-sel */ Button} {
color: red;
}
`;
```

would throw a stylelint error similar to `All rules have already been disabled (CssSyntaxError)`.
##### `sc-custom`

#### Interpolation linting
**`sc-custom` is meant to be used as a last resort escape hatch. Prefer to use the standard tags if possible!**

We do not currently support linting interpolations as it could be a big performance hit though we aspire to have at least partial support in the future. You can of course lint your own mixins in their separate files, but it won't be linted in context, the implementation currently just inserts relevant dummy values. This, we are afraid, means you won't be able to lint cases such as `declaration-block-no-duplicate-properties` etc. and won't be able to lint outside mixins such as [polished](https://github.com/styled-components/polished).

#### Template literal style and indentation
On top of the above standard tags the processor also has the `sc-custom` tag to allow you to cover more unique and uncommon edge cases. With the `sc-custom` tag you can decide yourself what the placeholder value will be.

In order to have stylelint correctly apply indentation rules we need to do a bit of opinionated preprocessing on the `styled-components` styles, which results in us only officially supporting one coding style when it comes to `styled-components` tagged template literals. This style consists of always placing the closing backtick on the base level of indentation as follows:
For example:

**Right**
```js
// Switch between left and right based on language settings passed through via the theme
const rtlSwitch = props => props.theme.dir === 'rtl' ? 'left' : 'right';

const Button = styled.button`
color: red;
`
background: green;
// Tell the processor to replace the interpolation with "left"
// when linting
margin-${/* sc-custom 'left' */ rtlSwitch}: 12.5px;
`;
```

### Syntax notes

#### Turning rules off from within your JS/CSS

Turn off rules with `stylelint-disable` comments (see the [stylelint documentation](https://stylelint.io/user-guide/configuration/#turning-rules-off-from-within-your-css) for all allowed syntax) both inside and outside of the tagged template literals.

```js
import React from 'react';
import styled from 'styled-components';

// Disable stylelint from within the tagged template literal
const Wrapper = styled.div`
/* stylelint-disable */
background-color: 123;
`;

// Or from the JavaScript around the tagged template literal
/* stylelint-disable */
const Wrapper = styled.div`
background-color: 123;
`;
```

#### Template literal style and indentation

In order to have stylelint correctly apply indentation rules the processor needs to do a bit of opinionated preprocessing on the styles, which results in us only officially supporting one indentation style. (the supported style is the "default" one as shown in all the documentation)

The important thing is that you put the closing backtick on the base level of indentation as follows:

**Right**

```js
if (condition) {
const Button = styled.button`
Expand All @@ -134,6 +200,7 @@ if (condition) {
```

**Wrong**

```js
if (condition) {
const Button = styled.button`
Expand All @@ -153,7 +220,7 @@ It may be that other tagged template literal styles are coincidentally supported

## License

Licensed under the MIT License, Copyright © 2016 Maximilian Stoiber. See [LICENSE.md](./LICENSE.md) for more information!
Licensed under the MIT License, Copyright © 2017 Maximilian Stoiber. See [LICENSE.md](./LICENSE.md) for more information!

Based on Mapbox' excellent [`stylelint-processor-markdown`](https://github.com/mapbox/stylelint-processor-markdown), thanks to @davidtheclark!

Expand Down
4 changes: 2 additions & 2 deletions src/parsers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const getTTLContent = require('../utils/tagged-template-literal.js').getTaggedTe
const parseImports = require('../utils/parse').parseImports
const getSourceMap = require('../utils/parse').getSourceMap

const processStyledComponentsFile = ast => {
const processStyledComponentsFile = (ast, absolutePath) => {
const extractedCSS = []
let ignoreRuleComments = []
let importedNames = {
Expand All @@ -41,7 +41,7 @@ const processStyledComponentsFile = ast => {
}
const helper = isHelper(node, importedNames)
if (!helper && !isStyled(node, importedNames.default)) return
const content = getTTLContent(node)
const content = getTTLContent(node, absolutePath)
const fixedContent = fixIndentation(content).text
const wrapperFn = helper === 'keyframes' ? wrapKeyframes : wrapSelector
const wrappedContent = wrapperFn(fixedContent)
Expand Down
24 changes: 24 additions & 0 deletions src/utils/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,34 @@ const wrapKeyframes = content => `@keyframes {${content}}\n`
*/
const isStylelintComment = comment => /^\s*stylelint-(?:enable|disable)(?:\s.*)?$/.test(comment)

const extrapolateShortenedCommand = (commands, shortCommand, absolutePath, location) => {
let extrapolatedCommand = null
// We use .some so we can break the loop using return true
commands.some(singleCommand => {
if (singleCommand.substr(0, shortCommand.length) === shortCommand) {
if (extrapolatedCommand === null) {
// This is the first time we found a match
extrapolatedCommand = singleCommand
} else {
// We have already found another command which means this is not a unique short command.
// This will probably never throw, as all our current commands start with different letters
throw new Error(
`ERROR at ${absolutePath} line ${location.line} column ${location.column}:` +
'\nYou shortened a Styled Components interpolation tag ambiguously, add a few more characters to fix this error'
)
}
}
// continue loop
return false
})
return extrapolatedCommand
}

exports.wrapKeyframes = wrapKeyframes
exports.wrapSelector = wrapSelector
exports.fixIndentation = fixIndentation
exports.reverseString = reverseString
exports.nextNonWhitespaceChar = nextNonWhitespaceChar
exports.isLastDeclarationCompleted = isLastDeclarationCompleted
exports.isStylelintComment = isStylelintComment
exports.extrapolateShortenedCommand = extrapolateShortenedCommand
Loading