├── .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 | [![Build Status](https://travis-ci.org/zabute/stylelint-processor-glamorous.svg?branch=master)](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 | --------------------------------------------------------------------------------