Skip to content

Commit

Permalink
Make sure we don't explode on Vue and css-in-js solutions
Browse files Browse the repository at this point in the history
Increases the peerDependency to stylelint 9.10.0.

Stylelint 9.10.0 uses postcss-jsx to parse css-in-js solutions and
postcss-html to parse vue/markdown/html files, These two together mean
the funky languages landscape is a lot cleaner than a few months ago.

We skip passing js-like files into prettier as the css fragments aren't
parsable by prettier as they're either JS objects, or lists of
properties without a wrapping selector (i.e. `color: red; background:
green` instead of `.x { color: red; background: green; }`.

For Vue / html / markdown files we can use the parser name that
stylelint used as conviniently they all match up to parser names that
prettier has (well except sugarss but who uses that?). This allows
`<style lang="scss">` blocks to be parsed as scss.

This also adds a handful of e2e tests as which allow us to keep testing
these new languages.
  • Loading branch information
BPScott committed May 11, 2019
1 parent a599c3a commit 2accd79
Show file tree
Hide file tree
Showing 17 changed files with 531 additions and 205 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
!.eslintrc.js
node_modules

# This test file is deliberately unparsable
test/fixtures/check.unparsable.js
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ module.exports = {
root: true,
rules: {
'node/no-unpublished-require': ['error', {allowModules: ['stylelint']}],
'node/no-missing-require': ['error', {allowModules: ['styled-components']}],
},
};
13 changes: 13 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ Linting is ran as part of `yarn run test`. The build will fail if there are any

This plugin is used to lint itself. The style is checked when `npm test` is run, and the build will fail if there are any linting errors. You can use `npm run lint -- --fix` to fix some linting errors. To run the tests without running the linter, you can use `node_modules/.bin/mocha`.

### End to end tests

e2e test fixtures are in `test/fixtures`.

Running the e2e tests while trying to debug a problem can be annoying. To check
stylelint's output of a single fixture, run stylelint from within the fixtures
directory:

```sh
cd test/fixtures
../../node_modules/.bin/stylelint 'check*'
```

## Publishing

- Ensure you are on the master branch locally.
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package.json
test-e2e
test/fixtures

# this file doesn't exist, but we use it as a filename that should be ignored
# by prettier in the tests
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@
},
"peerDependencies": {
"prettier": ">= 0.11.0",
"stylelint": ">= 9.2.1"
"stylelint": ">= 9.10.0"
},
"devDependencies": {
"eslint": "^5.0.1",
"eslint-config-prettier": "^3.1.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-prettier": "^3.0.0",
"jest": "^23.6.0",
"prettier": "^1.13.7",
"stylelint": "^9.5.0",
"prettier": "^1.17.0",
"stylelint": "^9.10.0",
"stylelint-config-prettier": "^4.0.0"
},
"engines": {
Expand Down
26 changes: 26 additions & 0 deletions stylelint-prettier.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,32 @@ module.exports = stylelint.createPlugin(
initialOptions.parser = 'css';
}

// Stylelint suppports languages that may contain multiple types of style
// languages, thus we can't rely on guessing the parser based off the
// filename.

// JS may change the language based on if you're using a template literal
// or an object: `styled.a`color: red`;` or `style.a({color: 'red'})`.
// Prettier does not have a parser for any of these formats so do nothing
const parserBlockList = ['babel', 'flow', 'typescript'];
if (parserBlockList.indexOf(prettierFileInfo.inferredParser) !== -1) {
return;
}

// html and vue may change the language based on a style element's `lang`
// tag: `<style>` should be parsed as CSS while `<style lang="scss">`
// should be pared as SCSS.
// markdown may change the language based on the fenced code blocks'
// language hint: Blocks begining with "```scss" should be parsed as SCSS
//
// This is added to the options first, so that
// prettierRcOptions and stylelintPrettierOptions can still override
// the parser.
const parserInferLangList = ['vue', 'markdown', 'html'];
if (parserInferLangList.indexOf(prettierFileInfo.inferredParser) !== -1) {
initialOptions.parser = root.source.lang;
}

const prettierOptions = Object.assign(
{},
initialOptions,
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/check.invalid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.foo {
background-image: url("x");
}
27 changes: 27 additions & 0 deletions test/fixtures/check.invalid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<style>
.foo {
background-image: url("x");
}
</style>

<style lang="scss">
.foo {
background-image: url("x");
}

$map: (
'alpha': 10,
'beta': 20,
'gamma': 30
);
</style>
</head>

<body>
</body>
</html>
11 changes: 11 additions & 0 deletions test/fixtures/check.invalid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const styled = require('styled-components');

const Button = styled.div`
background-image: url("x");
color: red;
`;

const Button2 = styled.div({
'background-image': url("x"),
'color': 'red',
});
19 changes: 19 additions & 0 deletions test/fixtures/check.invalid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# TEST

```css
h2 {
background-image: url("x");
}
```

```scss
h2 {
background-image: url("x");
}

$map: (
'alpha': 10,
'beta': 20,
'gamma': 30
);
```
9 changes: 9 additions & 0 deletions test/fixtures/check.invalid.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.foo {
background-image: url("x");
}

$map: (
'alpha': 10,
'beta': 20,
'gamma': 30
);
21 changes: 21 additions & 0 deletions test/fixtures/check.invalid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<template>
<span>Hi</span>
</template>

<style>
.foo {
background-image: url("x");
}
</style>

<style lang="scss">
.foo {
background-image: url("x");
}
$map: (
'alpha': 10,
'beta': 20,
'gamma': 30
);
</style>
7 changes: 7 additions & 0 deletions test/fixtures/check.unparsable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const styled = require('styled-components');

const But{ton = styled.div`
background-image: url("x");
color: red;
`;

7 changes: 7 additions & 0 deletions test/fixtures/stylelint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
plugins: [`../..`],
extends: ['stylelint-config-prettier'],
rules: {
'prettier/prettier': [true, {singleQuote: true, trailingComma: 'all'}],
},
};
69 changes: 69 additions & 0 deletions test/stylelint-prettier-e2e.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const {spawnSync} = require('child_process');
const {resolve} = require('path');

describe('E2E Tests', () => {
test('CSS/SCSS files', () => {
const result = runStylelint('*.{css,scss}');

const expectedResult = `
check.invalid.css
2:25 ✖ Replace ""x"" with "'x'" prettier/prettier
check.invalid.scss
2:25 ✖ Replace ""x"" with "'x'" prettier/prettier
8:14 ✖ Insert "," prettier/prettier
`.trim();

expect(result.status).toEqual(2);
expect(result.output).toEqual(expectedResult);
});

test('HTML/Markdown/Vue files', () => {
const result = runStylelint('*.{html,md,vue}');

const expectedResult = `
check.invalid.html
8:25 ✖ Replace ""x"" with "'x'" prettier/prettier
14:25 ✖ Replace ""x"" with "'x'" prettier/prettier
20:14 ✖ Insert "," prettier/prettier
check.invalid.md
5:25 ✖ Replace ""x"" with "'x'" prettier/prettier
11:25 ✖ Replace ""x"" with "'x'" prettier/prettier
17:14 ✖ Insert "," prettier/prettier
check.invalid.vue
7:25 ✖ Replace ""x"" with "'x'" prettier/prettier
13:25 ✖ Replace ""x"" with "'x'" prettier/prettier
19:14 ✖ Insert "," prettier/prettier
`.trim();

expect(result.status).toEqual(2);
expect(result.output).toEqual(expectedResult);
});

/**
* Don't act upon CSS-in-JS files
*/
test('CSS-in-JS files', () => {
const result = runStylelint('*.js');

const expectedResult = ``;

expect(result.status).toEqual(0);
expect(result.output).toEqual(expectedResult);
});
});

function runStylelint(pattern) {
const stylelintCmd = resolve(`${__dirname}/../node_modules/.bin/stylelint`);

const result = spawnSync(stylelintCmd, [pattern], {
cwd: `${__dirname}/fixtures`,
});

return {
status: result.status,
output: result.stdout.toString().trim(),
};
}
24 changes: 0 additions & 24 deletions test/stylelint-prettier.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,30 +530,6 @@ testRule(rule, {
],
});

// Invalid syntax is reported
testRule(rule, {
ruleName: rule.ruleName,
config: [true, {trailingComma: 'all'}],
codeFilename: filename('default', 'dummy.js'),
accept: [],
reject: [
{
description: 'Prettier JS Invalid - Unparsable code',
code: `cons}t Button = styled.button\`\n background: tomato;\n\`;`,
message: `Parsing error: Unexpected token`,
line: 1,
column: 5,
},
{
description: 'Prettier JS Invalid - Unparsable code',
code: `const f = 1;\nconst bar = 3;\n\nco}nst Button = styled.button\`\n background: tomato;\n\`;`,
message: `Parsing error: Unexpected token`,
line: 4,
column: 3,
},
],
});

describe('stylelint configurations', () => {
const oldWarn = console.warn;
beforeEach(() => {
Expand Down
Loading

0 comments on commit 2accd79

Please sign in to comment.