Skip to content

Commit

Permalink
feat(stylelint): custom syntax for stylelint v14 (#1065)
Browse files Browse the repository at this point in the history
* wip

* finish some tests and stylelint-config-standard-linaria

* remove unnecessary files

* update placeholder logic

* add isSelector placeholder logic, fix bugs

* remove unnecessary atrule logic

* cleanup, utils and locationCorrection tests, placeholder consolidation

* support styled api, bug fix for ruleset with func in expr

* remove changelog, add docs

* update comment

* add additional style api specs, fix single line css, fix case when suffix is on end of value

* add location correction tests

* uncomment specs

* remove comment

* add @linaria namespace

* run pnpm changeset

* update linting docs, change config to js file

* use string instead of require

* remove unused eslint disable

* fix ts issue

* add type

Co-authored-by: Tim Kutnick <tim.kutnick@airbnb.com>
  • Loading branch information
kutnickclose and Tim Kutnick committed Sep 26, 2022
1 parent ab510e1 commit ce36da4
Show file tree
Hide file tree
Showing 22 changed files with 2,589 additions and 102 deletions.
6 changes: 6 additions & 0 deletions .changeset/sweet-camels-serve.md
@@ -0,0 +1,6 @@
---
'@linaria/postcss-linaria': patch
'@linaria/stylelint-config-standard-linaria': patch
---

Add stylelint v14 custom syntax support
75 changes: 71 additions & 4 deletions docs/LINTING.md
@@ -1,15 +1,17 @@
# Linting

## stylelint
There are separate installations based on whether you use stylelint v13 or stylelint v14

For linting styles with [stylelint](https://stylelint.io/), we provide our custom config tailored for linaria - `@linaria/stylelint`.
## Stylelint 13

For linting styles with [stylelint 13](https://stylelint.io/), use `@linaria/stylelint`.

### Installation

You need to install `stylelint` and optionally your favorite config (such as `stylelint-config-recommended`) in your project:
Install `stylelint` and optionally your favorite config (such as `stylelint-config-recommended`) in your project:

```bash
yarn add --dev stylelint stylelint-config-recommended
yarn add --dev stylelint stylelint-config-recommended @linaria/stylelint
```

### Configuring stylelint
Expand All @@ -31,6 +33,51 @@ Please refer to the [official stylelint documentation](https://stylelint.io/user

The preprocessor will use the [options from the configuration file](/docs/CONFIGURATION.md) for processing your files.

## Stylelint 14

For linting styles with [stylelint 14](https://stylelint.io/), use `@linaria/stylelint-config-standard-linaria`.

### Installation

Install `stylelint` and `@linaria/stylelint-config-standard-linaria`

```bash
yarn add --dev stylelint @linaria/stylelint-config-standard-linaria
```

### Configuring stylelint

For the standard configuration you can extend from `@linaria/stylelint-config-standard-linaria`.

Here's an example `.stylelintrc` configuration file:

```json
{
"extends": [
"@linaria/stylelint-config-standard-linaria"
]
}
```

`@linaria/stylelint-config-standard-linaria` extends `stylelint-config-standard` which extends `stylelint-config-recommended` so you do NOT need to add those separately. It also sets the customSyntax as `@linaria/postcss-linaria` and adds a few rules.

Alternatively, to just use the custom syntax you can add `@linaria/postcss-linaria`
Here's an example `.stylelintrc` configuration file:

```json
{
"customSyntax": "@linaria/postcss-linaria"
}
```

Please refer to the [official stylelint documentation](https://stylelint.io/user-guide/configuration/) for more info about configuration.

### Why did the configuration change between Stylelint v13 and v14?

Stylelint 14 encourages the use of a [custom syntax](https://stylelint.io/developer-guide/syntaxes/) instead of a processor. `@linaria/stylelint-config-standard-linaria` sets the custom syntax to `@linaria/postcss-linaria`, a custom syntax for linaria, whereas @linaria/stylelint uses a processor. The custom syntax has the benefit of being able to support `stylelint --fix` whereas the processor cannot.

## Usage

### Linting your files

Add the following to your `package.json` scripts:
Expand All @@ -42,3 +89,23 @@ Add the following to your `package.json` scripts:
Now, you can run `yarn lint:css` to lint the CSS in your JS files with stylelint.

For more information refer to [stylelint documentation](https://stylelint.io/user-guide/cli/).

## Editor Setup

In order to make the
[vscode-stylelint](https://github.com/stylelint/vscode-stylelint)
extension work with this syntax correctly, you must configure it
to validate the files you use linaria in by specifying an array of [language
identifiers](https://code.visualstudio.com/docs/languages/overview#_changing-the-language-for-the-selected-file).

You can do this by following these
[instructions](https://github.com/stylelint/vscode-stylelint#stylelintvalidate).

For example:

```json
{
"stylelint.validate": ["typescriptreact"]
}
```

1 change: 1 addition & 0 deletions jest.config.js
Expand Up @@ -2,4 +2,5 @@ module.exports = {
testEnvironment: 'node',
collectCoverageFrom: ['src/*.ts'],
transformIgnorePatterns: ['node_modules/(?!@linaria)'],
testPathIgnorePatterns: ['/__utils__/'],
};
132 changes: 132 additions & 0 deletions packages/postcss-linaria/__tests__/__utils__/index.ts
@@ -0,0 +1,132 @@
import type { Document, Node } from 'postcss';

import { parse } from '../../src/parse';

export function createTestAst(source: string): {
ast: Document;
source: string;
} {
const ast = parse(source) as Document;

return { ast, source };
}

export function getSourceForNodeByLoc(source: string, node: Node): string {
const loc = node.source;

if (!loc || !loc.start || !loc.end) {
return '';
}

const lines = source.split(/\r\n|\n/);
const result: string[] = [];
const startLineIndex = loc.start.line - 1;
const endLineIndex = loc.end.line - 1;

for (let i = startLineIndex; i < loc.end.line; i++) {
const line = lines[i];
if (line) {
let offsetStart = 0;
let offsetEnd = line.length;

if (i === startLineIndex) {
offsetStart = loc.start.column - 1;
}

if (i === endLineIndex) {
offsetEnd = loc.end.column;
}

result.push(line.substring(offsetStart, offsetEnd));
}
}

return result.join('\n');
}

export function getSourceForNodeByRange(source: string, node: Node): string {
if (!node.source || !node.source.start || !node.source.end) {
return '';
}

return source.substring(node.source.start.offset, node.source.end.offset + 1);
}

export const sourceWithExpression = {
ruleset: `
const expr = 'color: black';
css\`
$\{expr}
\`;
`,
singleLineRuleset: `
css\`
\${expr0} { \${expr1}: \${expr2} }
\`
`,
selectorOrAtRule: `
const expr = '@media (min-width: 100px)';
css\`
$\{expr} {
color: black;
}
\`;
`,
selectorBeforeExpression: `
const expr = '.classname';
css\`
.example $\{expr} {
color: black;
}
\`;
`,
selectorAfterExpression: `
const expr = '.classname';
css\`
$\{expr} .example {
color: black;
}
\`;
`,
declarationProperty: `
const expr = 'color';
css\`
\${expr}: black;
\`;
`,
declarationValue: `
const expr = 'black';
css\`
color: \${expr};
\`;
`,
declarationMultipleValues: `
const expr1 = '10px';
const expr2 = '5px';
css\`
margin: \${expr1} \${expr2} \${expr1} \${expr2};
\`;
`,
declarationMixedValues: `
const expr1 = '10px';
const expr2 = '5px';
css\`
margin: \${expr1} 7px \${expr2} 9px;
\`;
`,
combo: `
css\`
\${expr0}
.foo {
\${expr1}: \${expr2};
}
\${expr3} {
.bar {
color: black;
}
}
\${expr4}
\`;
`,
};

0 comments on commit ce36da4

Please sign in to comment.