├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .prettierignore
├── .stylelintrc.js
├── .travis.yml
├── LICENSE
├── README.md
├── package.json
├── src
├── extract.js
├── helpers.js
├── ignoredRules.js
└── index.js
├── test
├── cssProp.test.js
├── factory.test.js
├── fixtures
│ ├── CssProp.js
│ ├── Factory.js
│ ├── pojo.js
│ └── styled.js
├── pojo.test.js
├── styled.test.js
└── sytax-error.test.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "4.1.0"
8 | }
9 | }
10 | ],
11 | "@babel/preset-react"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "jest": true,
5 | "node": true
6 | },
7 | "rules": {
8 | "max-len": "off",
9 | "function-paren-newline": "off",
10 | "react/prop-types": "off",
11 | "react/jsx-filename-extension": "off",
12 | "no-use-before-define": "off"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | yarn-error.log
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .stylelintrc.js
2 | src
3 | test
4 | yarn-error.log
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | processors: [path.join(__dirname, './lib/index.js')],
5 | extends: ['stylelint-config-standard'],
6 | };
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | install:
5 | - npm install
6 | script:
7 | - npm run lint
8 | - npm run test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ardamis Yeshak
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # stylelint-processor-glamorous
2 | > Lint [glamorous](https://github.com/paypal/glamorous) and related css-in-js with [stylelint](https://github.com/stylelint/stylelint)
3 |
4 | [](https://travis-ci.org/zabute/stylelint-processor-glamorous)
5 |
6 | ## Installation
7 |
8 | ```sh
9 | $ yarn add stylelint stylelint-config-standard stylelint-processor-glamorous --dev
10 | ```
11 |
12 | > You can use `styleiint-config-recomended` or your own custom config. Certain rules that enforce formatting rules will be [ignored](/src/ignoredRules.js).
13 |
14 |
15 | ### Add ```.stylelintrc``` to the root of your project:
16 | ```json
17 | {
18 | "processors": ["stylelint-processor-glamorous"],
19 | "extends": "stylelint-config-standard"
20 | }
21 | ```
22 |
23 | That's it. You can now run stylelint from the command line.
24 |
25 | ```sh
26 | $ yarn stylelint 'src/**/*.js'
27 | ```
28 |
29 |
30 |
31 | ## What gets linted
32 | - Glamorous component factories
33 | ```js
34 | import glamorous from 'glamorous'; // choose any name for the defaut export
35 |
36 | const Component = glamorous.div({ ... });
37 | const OtherComponent = glamorous('div')({ ... })
38 | ```
39 |
40 | - CSS attributes
41 |
42 | ```jsx
43 |
44 | ```
45 |
46 | - Annotated object literals.
47 | ```js
48 | export const styles =
49 | // @css
50 | {
51 | ...
52 | }
53 | ```
54 |
55 | The `@css` comment tells the processor that its a style object. Make sure you put it right before the opening brace.
56 |
57 |
58 |
59 | ## Integrating with other css-in-js libraries
60 | You can use `@css` to lint any object. Hoverver, if you stick to the `styled` pattern, you won't need to add annotations to your code.
61 |
62 | ```js
63 | import styled from 'my-fav-cssinjs-lib';
64 |
65 | const Component = styled.div({ ... })
66 | const OtherComponent = styled('div')({ ... })
67 | ```
68 |
69 |
70 |
71 | ## Contributing
72 | Contributions of any kind are always welcome.
73 |
74 |
75 |
76 | LICENSE: [MIT](/LICENSE)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stylelint-processor-glamorous",
3 | "version": "0.3.0",
4 | "description": "Lint glamorous and related css-in-js with stylelint",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/zabute/stylelint-processor-glamorous"
8 | },
9 | "author": {
10 | "name": "Ardamis Yeshak",
11 | "email": "arde987@gmail.com",
12 | "url": "https://github.com/zabute"
13 | },
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/zabute/stylelint-processor-glamorous/issues"
17 | },
18 | "main": "lib/index.js",
19 | "keywords": [
20 | "stylelint",
21 | "stylelint-processor",
22 | "glamorous"
23 | ],
24 | "dependencies": {
25 | "@babel/generator": "^7.0.0-beta.40",
26 | "@babel/traverse": "^7.0.0-beta.40",
27 | "@babel/types": "^7.0.0-beta.40",
28 | "babylon": "^6.18.0",
29 | "json5": "^0.5.1",
30 | "postcss": "^6.0.18",
31 | "postcss-js": "^1.0.1",
32 | "shortid": "^2.2.8"
33 | },
34 | "devDependencies": {
35 | "@babel/cli": "^7.0.0-beta.36",
36 | "@babel/core": "^7.0.0-beta.36",
37 | "@babel/preset-env": "^7.0.0-beta.36",
38 | "@babel/preset-react": "^7.0.0-beta.38",
39 | "babel-core": "^7.0.0-0",
40 | "babel-eslint": "^8.1.2",
41 | "babel-jest": "^22.0.4",
42 | "eslint": "^3.6.0",
43 | "eslint-config-airbnb": "^13.0.0",
44 | "eslint-plugin-import": "^2.1.0",
45 | "eslint-plugin-jsx-a11y": "^2.2.3",
46 | "eslint-plugin-react": "^6.3.0",
47 | "glamor": "^2.20.40",
48 | "glamorous": "^4.11.4",
49 | "husky": "^0.14.3",
50 | "jest": "^22.0.4",
51 | "prettier-eslint": "^8.8.1",
52 | "prettier-eslint-cli": "^4.7.1",
53 | "prop-types": "^15.6.0",
54 | "react": "^16.2.0",
55 | "react-dom": "^16.2.0",
56 | "rimraf": "^2.6.2",
57 | "stylelint": "^8.4.0",
58 | "stylelint-config-standard": "^18.0.0"
59 | },
60 | "scripts": {
61 | "test": "yarn build && jest test/*.test.js",
62 | "lint": "eslint .",
63 | "format": "prettier-eslint --write \"./**/*.js\"",
64 | "precommit": "yarn format && yarn lint && yarn test",
65 | "build": "rimraf lib && babel src -d lib"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/extract.js:
--------------------------------------------------------------------------------
1 | import * as t from '@babel/types';
2 | import traverse from '@babel/traverse';
3 | import gen from '@babel/generator';
4 | import postcss from 'postcss';
5 | import postcssJs from 'postcss-js';
6 | import json5 from 'json5';
7 | import id from 'shortid';
8 | import {
9 | hasLeadingComment,
10 | isCssAttribute,
11 | isAnnotatedExpression,
12 | extractDeclarations,
13 | extractValues,
14 | } from './helpers';
15 |
16 | let sourceMap;
17 |
18 | export default (ast) => {
19 | sourceMap = new Map();
20 | let glamorousImport;
21 | let rules = [];
22 |
23 | traverse(ast, {
24 | enter(path) {
25 | if (isCssAttribute(path) || isAnnotatedExpression(path)) {
26 | rules = rules.concat([path]);
27 | }
28 |
29 | if (path.isImportDeclaration()) {
30 | if (path.node.source.value === 'glamorous') {
31 | glamorousImport = path
32 | .get('specifiers')
33 | .filter(specifier => specifier.isImportDefaultSpecifier())[0];
34 | }
35 | }
36 |
37 | if (path.isCallExpression()) {
38 | let importName;
39 |
40 | if (glamorousImport) {
41 | importName = glamorousImport.node.local.name;
42 | }
43 |
44 | const { object, callee } = path.node.callee;
45 |
46 | if (
47 | (object &&
48 | (object.name === importName || object.name === 'styled')) ||
49 | (callee && (callee.name === importName || callee.name === 'styled'))
50 | ) {
51 | path.get('arguments').forEach((arg) => {
52 | if (arg.isObjectExpression()) {
53 | rules = rules.concat([arg]);
54 | } else if (arg.isFunction()) {
55 | if (arg.get('body').isObjectExpression()) {
56 | rules = rules.concat(arg.get('body'));
57 | } else {
58 | const rule = Object.assign(
59 | {},
60 | t.objectExpression(extractDeclarations(arg.get('body'))),
61 | { loc: path.node.loc },
62 | );
63 | path.replaceWith(rule);
64 | rules = rules.concat(path);
65 | }
66 | }
67 | });
68 | }
69 | }
70 | },
71 | });
72 |
73 | let styles = [];
74 |
75 | rules.forEach((rule) => {
76 | sourceMap.set(sourceMap.size + 1, rule.node.loc.start);
77 | processRule(rule);
78 |
79 | const cssString = getCssString(rule.node);
80 | styles = styles.concat([
81 | `.${id.generate()}{${cssString && '\n'}${cssString}}`,
82 | ]);
83 | });
84 |
85 | return { css: styles.join('\n'), sourceMap };
86 | };
87 |
88 | const processRule = (path) => {
89 | if (path.get('properties')) {
90 | path.get('properties').forEach((prop) => {
91 | if (
92 | prop.isObjectProperty() &&
93 | !hasLeadingComment(prop, /^\s*stylelint-disable-next-line\s*$/)
94 | ) {
95 | sourceMap.set(sourceMap.size + 1, prop.node.key.loc.start);
96 |
97 | if (
98 | !prop.get('key').isIdentifier() &&
99 | !prop.get('key').isStringLiteral()
100 | ) {
101 | prop.replaceWith(
102 | t.objectProperty(
103 | t.stringLiteral(`.${id.generate()}`),
104 | prop.node.value,
105 | ),
106 | );
107 | } else if (prop.node.key.value) {
108 | // hyphenate strings like minWidth and @fontFace
109 | prop
110 | .get('key')
111 | .replaceWith(
112 | t.stringLiteral(
113 | prop.node.key.value.replace(/([A-Z])/, '-$1').toLowerCase(),
114 | ),
115 | );
116 |
117 | if (!prop.get('value').isObjectExpression()) {
118 | prop.get('value').replaceWith(t.objectExpression([]));
119 | }
120 | }
121 |
122 | if (prop.get('value').isObjectExpression()) {
123 | processRule(prop.get('value'));
124 | } else if (
125 | !prop.get('value').isNumericLiteral() &&
126 | !prop.get('value').isStringLiteral()
127 | ) {
128 | const values = extractValues(prop.get('value'));
129 |
130 | if (
131 | prop.get('value').isConditionalExpression() &&
132 | values.length > 0
133 | ) {
134 | // Create multiple declarations for conditional expressions.
135 | // Example: { color: prop.primary ? 'red' : 'blue' }
136 | // will be replaced with { color: 'red', color: 'blue'}
137 | const declarations = values.map((value, index) => {
138 | const key = Object.assign(
139 | {},
140 | // Attach a random id to the prop so it becomes valid js.
141 | t.stringLiteral(`${prop.node.key.name}$${id.generate()}`),
142 | { loc: prop.node.loc },
143 | );
144 |
145 | if (index > 0) {
146 | sourceMap.set(sourceMap.size + 1, prop.node.key.loc.start);
147 | }
148 | return t.objectProperty(key, value);
149 | });
150 |
151 | prop.replaceWithMultiple(declarations);
152 | } else {
153 | prop.get('value').replaceWith(t.stringLiteral('placeholderValue'));
154 | }
155 | }
156 | } else {
157 | prop.node.leadingComments = []; // eslint-disable-line
158 | prop.remove();
159 | }
160 | });
161 | }
162 | };
163 |
164 | const getCssString = (node) => {
165 | try {
166 | const extractedCss = postcss().process(json5.parse(gen(node).code), {
167 | parser: postcssJs,
168 | }).css;
169 |
170 | return (
171 | extractedCss &&
172 | // Collapse closing braces to the end of the last declaration in the block. Makes
173 | // generating the source map a lot easier: simply map every object property to a line in the css.
174 | extractedCss
175 | .replace(/\n\s*(?=\s*})/g, '')
176 | // Remove Indentations
177 | .replace(/\n\s*/g, '\n')
178 | // Remove random id if present
179 | .replace(/(\$.*)(?=.*:)/g, '')
180 | );
181 | } catch (e) {
182 | e.message = `Parsing Failed. Make sure you're not using unsupported syntax. ${
183 | e.message
184 | }`;
185 |
186 | throw e;
187 | }
188 | };
189 |
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 | export const hasLeadingComment = (path, pattern) =>
2 | path.node.leadingComments &&
3 | path.node.leadingComments.filter(comment => pattern.test(comment.value))[0];
4 |
5 | export const isTopLevelExpression = path =>
6 | path.isObjectExpression() && !path.findParent(p => p.isObjectExpression());
7 |
8 | export const isCssAttribute = path =>
9 | isTopLevelExpression(path) &&
10 | path.findParent(
11 | p => p.isJSXAttribute() && p.node.name && p.node.name.name === 'css',
12 | );
13 |
14 | export const isAnnotatedExpression = path =>
15 | path.isObjectExpression() && hasLeadingComment(path, /^\s*@css\s*$/);
16 |
17 | export const extractDeclarations = (path) => {
18 | let declarations = [];
19 |
20 | path.traverse({
21 | ObjectExpression(p) {
22 | if (!p.findParent(parent => parent.isObjectProperty())) {
23 | p.node.properties.forEach((prop) => {
24 | declarations = declarations.concat([prop]);
25 | });
26 | }
27 | },
28 | });
29 |
30 | return declarations;
31 | };
32 |
33 | export const extractValues = (path) => {
34 | let values = [];
35 | path.traverse({
36 | Literal(p) {
37 | if (p.node.value) {
38 | values = values.concat([p.node]);
39 | }
40 | },
41 | });
42 |
43 | return values;
44 | };
45 |
--------------------------------------------------------------------------------
/src/ignoredRules.js:
--------------------------------------------------------------------------------
1 | export default [
2 | 'indentation',
3 | 'no-empty-source',
4 | 'block-no-empty',
5 | 'selector-list-comma-newline-after',
6 | 'declaration-block-trailing-semicolon',
7 | 'no-missing-end-of-source-newline',
8 | 'rule-empty-line-before',
9 | 'declaration-colon-space-after',
10 | 'declaration-empty-line-before',
11 | 'block-opening-brace-newline-after',
12 | 'block-closing-brace-newline-before',
13 | 'at-rule-empty-line-before',
14 | 'block-opening-brace-space-before',
15 | 'block-opening-brace-space-after',
16 | 'block-closing-brace-space-after',
17 | 'block-closing-brace-space-before',
18 | ];
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { parse as babylon } from 'babylon';
2 | import extract from './extract';
3 | import ignoredRules from './ignoredRules';
4 |
5 | const parse = (input) => {
6 | const options = {
7 | sourceType: 'module',
8 | plugins: [
9 | 'jsx',
10 | 'flow',
11 | 'objectRestSpread',
12 | 'typescript',
13 | 'decorators',
14 | 'classProperties',
15 | 'exportExtensions',
16 | 'asyncGenerators',
17 | 'functionBind',
18 | 'functionSent',
19 | 'dynamicImport',
20 | ],
21 | };
22 |
23 | return babylon(input, options);
24 | };
25 |
26 | const sourceMaps = {};
27 |
28 | export default () => ({
29 | code: (input, path) => {
30 | try {
31 | const { css, sourceMap } = extract(parse(input));
32 | sourceMaps[path] = sourceMap;
33 |
34 | return css;
35 | } catch (e) {
36 | if (e.name === 'SyntaxError') {
37 | return '';
38 | }
39 | throw e;
40 | }
41 | },
42 |
43 | result: (result, path) =>
44 | Object.assign({}, result, {
45 | warnings: result.warnings
46 | .filter(warning => !ignoredRules.includes(warning.rule))
47 | .map(warning =>
48 | Object.assign({}, warning, {
49 | line: sourceMaps[path].get(warning.line).line,
50 | column: sourceMaps[path].get(warning.line).column + warning.column,
51 | }),
52 | ),
53 | }),
54 | });
55 |
--------------------------------------------------------------------------------
/test/cssProp.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import stylelint from 'stylelint';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom/server';
5 | import CssProp from './fixtures/CssProp';
6 |
7 | describe('CssProp', () => {
8 | let warnings;
9 |
10 | beforeAll((done) => {
11 | stylelint
12 | .lint({
13 | files: [path.join(__dirname, '/fixtures/CssProp.js')],
14 | })
15 | .then(({ results: stylelintResults }) => {
16 | warnings = stylelintResults[0].warnings;
17 | done();
18 | })
19 | .catch((error) => {
20 | console.log(error); // eslint-disable-line
21 | done();
22 | });
23 | });
24 |
25 | it('should render without crashing', () => {
26 | ReactDOM.renderToString();
27 | });
28 |
29 | it('should have warnings', () => {
30 | expect(warnings.length).toBeGreaterThan(0);
31 | });
32 |
33 | it('should have 3 warnings', () => {
34 | expect(warnings.length).toEqual(3);
35 | });
36 |
37 | it('should have a prop override warning on line 18 column 9', () => {
38 | const propOverrideWarnings = warnings.filter(
39 | warning =>
40 | warning.rule === 'declaration-block-no-shorthand-property-overrides',
41 | );
42 | expect(propOverrideWarnings).toHaveLength(1);
43 | expect(propOverrideWarnings[0].line).toEqual(18);
44 | expect(propOverrideWarnings[0].column).toEqual(9);
45 | });
46 |
47 | it('should have a prop warning on line 20 column 11', () => {
48 | const propWarnings = warnings.filter(
49 | warning => warning.rule === 'property-no-unknown',
50 | );
51 |
52 | expect(propWarnings).toHaveLength(1);
53 | expect(propWarnings[0].line).toEqual(20);
54 | expect(propWarnings[0].column).toEqual(11);
55 | });
56 |
57 | it('should have a hex warning on line 22', () => {
58 | const hexWarnings = warnings.filter(
59 | warning => warning.rule === 'color-no-invalid-hex',
60 | );
61 |
62 | expect(hexWarnings).toHaveLength(1);
63 | expect(hexWarnings[0].line).toEqual(22);
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/test/factory.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import stylelint from 'stylelint';
3 | import React from 'react';
4 | import ReactDOM from 'react-dom/server';
5 | import Factory from './fixtures/Factory';
6 |
7 | describe('Factory', () => {
8 | let warnings;
9 |
10 | beforeAll((done) => {
11 | stylelint
12 | .lint({
13 | files: [path.join(__dirname, '/fixtures/Factory.js')],
14 | })
15 | .then(({ results: stylelintResults }) => {
16 | warnings = stylelintResults[0].warnings;
17 | done();
18 | })
19 | .catch((error) => {
20 | console.log(error); // eslint-disable-line
21 | done();
22 | });
23 | });
24 |
25 | it('should render without crashing', () => {
26 | ReactDOM.renderToString();
27 | });
28 |
29 | it('should have warnings', () => {
30 | expect(warnings.length).toBeGreaterThan(0);
31 | });
32 |
33 | it('should have 4 warnings', () => {
34 | expect(warnings.length).toEqual(4);
35 | });
36 |
37 | it('should have a selector warning on line 15 column 5', () => {
38 | const selectorWarnings = warnings.filter(
39 | warning => warning.rule === 'selector-pseudo-class-no-unknown',
40 | );
41 |
42 | expect(selectorWarnings).toHaveLength(1);
43 | expect(selectorWarnings[0].line).toEqual(15);
44 | expect(selectorWarnings[0].column).toEqual(5);
45 | });
46 |
47 | it('should have a prop warning on line 15 column 5', () => {
48 | const propWarnings = warnings.filter(
49 | warning => warning.rule === 'property-no-unknown',
50 | );
51 |
52 | expect(propWarnings).toHaveLength(1);
53 | expect(propWarnings[0].line).toEqual(20);
54 | expect(propWarnings[0].column).toEqual(5);
55 | });
56 |
57 | it('should have a hex case warning on line 21', () => {
58 | const hexCaseWarnings = warnings.filter(
59 | warning => warning.rule === 'color-hex-case',
60 | );
61 |
62 | expect(hexCaseWarnings).toHaveLength(1);
63 | expect(hexCaseWarnings[0].line).toEqual(21);
64 | });
65 |
66 | it('should have a font family warning on line 30 column 23', () => {
67 | const fontFamilyWarnings = warnings.filter(
68 | warning => warning.rule === 'font-family-no-duplicate-names',
69 | );
70 |
71 | expect(fontFamilyWarnings).toHaveLength(1);
72 | expect(fontFamilyWarnings[0].line).toEqual(30);
73 | expect(fontFamilyWarnings[0].column).toEqual(23);
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/test/fixtures/CssProp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Div } from 'glamorous';
3 |
4 | const styles = {
5 | color: 'red',
6 | };
7 |
8 | const App = props => (
9 |
10 |
26 | {props.content}
27 |
28 |
29 | );
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/test/fixtures/Factory.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import glm from 'glamorous';
3 |
4 | const minWidth = 700;
5 |
6 | const Component1 = glm.a(
7 | {
8 | // stylelint-disable-next-line
9 | unknownProperty: '1.8em', // must not trigger any warnings
10 | display: 'inline-block',
11 | [`@media (minWidth: ${minWidth}px)`]: {
12 | color: 'red',
13 | },
14 | // unkown pseudo class selector
15 | ':focused': {
16 | backgroundColor: 'red',
17 | },
18 | },
19 | ({ primary }) => ({
20 | unknownProperty: '1.8em', // unknown prop
21 | color: primary ? '#fff' : '#DA233C',
22 | }),
23 | );
24 |
25 | const Component2 = glm(Component1, {
26 | displayName: 'Component2',
27 | forwardProps: ['shouldRender'],
28 | rootEl: 'div',
29 | })(props => ({
30 | fontFamily: 'Arial, Arial, sans-serif', // duplicate font-family names
31 | fontSize: props.big ? 36 : 24,
32 | }));
33 |
34 | const Component3 = glm.div({
35 | padding: '8px 12px',
36 | });
37 |
38 | export default () => (
39 |
40 |
41 |
42 |
43 |
44 | );
45 |
--------------------------------------------------------------------------------
/test/fixtures/pojo.js:
--------------------------------------------------------------------------------
1 | export const mustNotBeLinted = {
2 | paddingTop: '4p',
3 | color: '#FFFFFFF',
4 | };
5 |
6 | export const noErrors =
7 | // @css
8 | {
9 | display: 'inline-block',
10 | paddingTop: '4px',
11 | color: '#fff',
12 | ':hover': {
13 | background: '#000',
14 | '@media (minWidth: 700px)': {
15 | color: 'red',
16 | },
17 | },
18 | };
19 |
20 | export const withErrors =
21 | // @css
22 | {
23 | display: 'inline-block',
24 | paddingTop: '4p', // unknwon unit
25 | color: '#f40dfaa444', // invalid hex
26 | padding: '6px 12px', // shorthand property override
27 | ':hover': {
28 | background: '#000',
29 | '@media (minWidth: 700px)': { colors: 'red' }, // unknwon prop "colors"
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/test/fixtures/styled.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable
2 | import/no-unresolved,
3 | import/extensions,
4 | import/no-extraneous-dependencies */
5 |
6 | import styled from 'whatever';
7 |
8 | export const Component = styled('div')({
9 | // unknown prop
10 | colors: 'red',
11 | });
12 |
13 | export const Component2 = styled.div({
14 | // unknown pseudo class selector
15 | ':hovering': {
16 | color: 'red',
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/test/pojo.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import stylelint from 'stylelint';
3 |
4 | describe('pojo', () => {
5 | let warnings;
6 |
7 | beforeAll((done) => {
8 | stylelint
9 | .lint({
10 | files: [path.join(__dirname, '/fixtures/pojo.js')],
11 | })
12 | .then(({ results: stylelintResults }) => {
13 | warnings = stylelintResults[0].warnings;
14 | done();
15 | })
16 | .catch((error) => {
17 | console.log(error); // eslint-disable-line
18 | done();
19 | });
20 | });
21 |
22 | it('should have warnings', () => {
23 | expect(warnings.length).toBeGreaterThan(0);
24 | });
25 |
26 | it('should have 5 warnings', () => {
27 | expect(warnings.length).toEqual(4);
28 | });
29 |
30 | it('should have one unit-no-unknown warning on line 24 column 18', () => {
31 | const unitWarnings = warnings.filter(
32 | warning => warning.rule === 'unit-no-unknown',
33 | );
34 |
35 | expect(unitWarnings.length).toEqual(1);
36 | expect(unitWarnings[0].line).toEqual(24);
37 | expect(unitWarnings[0].column).toEqual(18);
38 | });
39 |
40 | it('should have one color-no-invalid-hex warning on line 25 column 12', () => {
41 | const hexWarnings = warnings.filter(
42 | warning => warning.rule === 'color-no-invalid-hex',
43 | );
44 |
45 | expect(hexWarnings.length).toEqual(1);
46 | expect(hexWarnings[0].line).toEqual(25);
47 | expect(hexWarnings[0].column).toEqual(12);
48 | });
49 |
50 | it('should have one no-shorthand-property-overrides warning on line 26 column 5', () => {
51 | const overrideWarnings = warnings.filter(
52 | warning =>
53 | warning.rule === 'declaration-block-no-shorthand-property-overrides',
54 | );
55 |
56 | expect(overrideWarnings.length).toEqual(1);
57 | expect(overrideWarnings[0].line).toEqual(26);
58 | expect(overrideWarnings[0].column).toEqual(5);
59 | });
60 |
61 | it('should have one property-no-unknown warning on line 29 column 37', () => {
62 | const propWarnings = warnings.filter(
63 | warning => warning.rule === 'property-no-unknown',
64 | );
65 |
66 | expect(propWarnings.length).toEqual(1);
67 | expect(propWarnings[0].line).toEqual(29);
68 | expect(propWarnings[0].column).toEqual(37);
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/test/styled.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import stylelint from 'stylelint';
3 |
4 | describe('Factory', () => {
5 | let warnings;
6 |
7 | beforeAll((done) => {
8 | stylelint
9 | .lint({
10 | files: [path.join(__dirname, '/fixtures/styled.js')],
11 | })
12 | .then(({ results: stylelintResults }) => {
13 | warnings = stylelintResults[0].warnings;
14 | done();
15 | })
16 | .catch((error) => {
17 | console.log(error); // eslint-disable-line
18 | done();
19 | });
20 | });
21 |
22 | it('should have warnings', () => {
23 | expect(warnings.length).toBeGreaterThan(0);
24 | });
25 |
26 | it('should have 2 warnings', () => {
27 | expect(warnings.length).toEqual(2);
28 | });
29 |
30 | it('should have an unknown prop warning on line 10 column 3', () => {
31 | const propWarnings = warnings.filter(
32 | warning => warning.rule === 'property-no-unknown',
33 | );
34 |
35 | expect(propWarnings.length).toEqual(1);
36 | expect(propWarnings[0].line).toEqual(10);
37 | expect(propWarnings[0].column).toEqual(3);
38 | });
39 |
40 | it('should have a pseudo class selector warning on line 15 column 3', () => {
41 | const propWarnings = warnings.filter(
42 | warning => warning.rule === 'selector-pseudo-class-no-unknown',
43 | );
44 |
45 | expect(propWarnings.length).toEqual(1);
46 | expect(propWarnings[0].line).toEqual(15);
47 | expect(propWarnings[0].column).toEqual(3);
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/test/sytax-error.test.js:
--------------------------------------------------------------------------------
1 | import stylelint from 'stylelint';
2 |
3 | describe('js-syntax-error', () => {
4 | it('should pass', async () => {
5 | await stylelint.lint({
6 | code: ']',
7 | });
8 | });
9 | });
10 |
--------------------------------------------------------------------------------