├── .gitignore ├── src ├── groups │ ├── special.js │ ├── positioning.js │ ├── animation.js │ ├── misc.js │ ├── border.js │ ├── visual.js │ ├── boxModel.js │ └── typography.js ├── config │ ├── sorter.js │ └── factory.js └── index.js ├── prettier.config.js ├── README.md ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /src/groups/special.js: -------------------------------------------------------------------------------- 1 | module.exports = ['composes', '@import', '@extend', '@mixin', '@at-root']; 2 | -------------------------------------------------------------------------------- /src/groups/positioning.js: -------------------------------------------------------------------------------- 1 | module.exports = ['position', 'top', 'right', 'bottom', 'left', 'z-index']; 2 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | semi: false, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | plugins: ['.'], 7 | } 8 | -------------------------------------------------------------------------------- /src/config/sorter.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss') 2 | const sorting = require('postcss-sorting'); 3 | const createConfig = require('./factory'); 4 | 5 | module.exports = (value, opts = {}) => { 6 | return postcss({ 7 | plugins: [sorting(createConfig())] 8 | }).process(value, { from: undefined }).css; 9 | } -------------------------------------------------------------------------------- /src/groups/animation.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'transition', 3 | 'transition-delay', 4 | 'transition-timing-function', 5 | 'transition-duration', 6 | 'transition-property', 7 | 'animation', 8 | 'animation-name', 9 | 'animation-duration', 10 | 'animation-play-state', 11 | 'animation-timing-function', 12 | 'animation-delay', 13 | 'animation-iteration-count', 14 | 'animation-direction', 15 | 'animation-fill-mode', 16 | ]; 17 | -------------------------------------------------------------------------------- /src/groups/misc.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'appearance', 3 | 'content', 4 | 'clip', 5 | 'clip-path', 6 | 'counter-reset', 7 | 'counter-increment', 8 | 'resize', 9 | 'user-select', 10 | 'nav-index', 11 | 'nav-up', 12 | 'nav-right', 13 | 'nav-down', 14 | 'nav-left', 15 | 'pointer-events', 16 | 'quotes', 17 | 'touch-action', 18 | 'will-change', 19 | 'zoom', 20 | 'fill', 21 | 'fill-rule', 22 | 'clip-rule', 23 | 'stroke', 24 | ]; 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier/parser-postcss'); 2 | const sorter = require('./config/sorter') 3 | 4 | const { parsers } = prettier; 5 | 6 | const languages = Object.keys(parsers); 7 | 8 | exports.parsers = {} 9 | for (const lang of languages) { 10 | const parser = parsers[lang]; 11 | exports.parsers[lang] = { 12 | ...parser, 13 | preprocess(text, options) { 14 | if (parser.preprocess) { 15 | text = parser.preprocess(text, options) 16 | } 17 | return sorter(text) 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Style Order for Prettier 2 | 3 | __Style Order__ is a [Prettier](https://prettier.io/) plugin which sorts property declarations and groups related properties. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm i -D prettier-plugin-style-order 9 | ``` 10 | 11 | That's it! Prettier should automatically find/use the plugin. 12 | 13 | If it doesn't seem to be working, refer to Prettier's [plugin docs](https://prettier.io/docs/en/plugins.html) for more in-depth directions. 14 | 15 | ## Credits 16 | 17 | - [`postcss-sorting`](https://github.com/hudochenkov/postcss-sorting) 18 | - [`stylelint-config-rational-order`](https://github.com/constverum/stylelint-config-rational-order) 19 | - [`prettier-plugin-package`](https://github.com/shellscape/prettier-plugin-package) -------------------------------------------------------------------------------- /src/groups/border.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'border', 3 | 'border-color', 4 | 'border-style', 5 | 'border-width', 6 | 'border-top', 7 | 'border-top-color', 8 | 'border-top-width', 9 | 'border-top-style', 10 | 'border-right', 11 | 'border-right-color', 12 | 'border-right-width', 13 | 'border-right-style', 14 | 'border-bottom', 15 | 'border-bottom-color', 16 | 'border-bottom-width', 17 | 'border-bottom-style', 18 | 'border-left', 19 | 'border-left-color', 20 | 'border-left-width', 21 | 'border-left-style', 22 | 'border-radius', 23 | 'border-top-left-radius', 24 | 'border-top-right-radius', 25 | 'border-bottom-right-radius', 26 | 'border-bottom-left-radius', 27 | 'border-image', 28 | 'border-image-source', 29 | 'border-image-slice', 30 | 'border-image-width', 31 | 'border-image-outset', 32 | 'border-image-repeat', 33 | 'border-collapse', 34 | 'border-spacing', 35 | ]; 36 | -------------------------------------------------------------------------------- /src/config/factory.js: -------------------------------------------------------------------------------- 1 | const special = require('../groups/special') 2 | const positioning = require('../groups/positioning') 3 | const boxModel = require('../groups/boxModel') 4 | const typography = require('../groups/typography') 5 | const visual = require('../groups/visual') 6 | const animation = require('../groups/animation') 7 | const misc = require('../groups/misc') 8 | 9 | module.exports = () => 10 | [ 11 | ['Special', special], 12 | ['Positioning', positioning], 13 | ['Box Model', boxModel], 14 | ['Typography', typography], 15 | ['Visual', visual], 16 | ['Animation', animation], 17 | ['Misc', misc], 18 | ] 19 | .map(([groupName, properties]) => ({ 20 | emptyLineBefore: 'always', 21 | properties, 22 | groupName, 23 | })) 24 | .reduce( 25 | (acc, { properties }) => ({ 26 | 'properties-order': [...acc['properties-order'], ...properties], 27 | }), 28 | { 'order': ['custom-properties'], 'properties-order': [], 'unspecified-properties-position': 'bottom' }, 29 | ) 30 | -------------------------------------------------------------------------------- /src/groups/visual.js: -------------------------------------------------------------------------------- 1 | const borderProps = require('./border'); 2 | 3 | const a = [ 4 | 'list-style', 5 | 'list-style-position', 6 | 'list-style-type', 7 | 'list-style-image', 8 | 'table-layout', 9 | 'empty-cells', 10 | 'caption-side', 11 | 'background', 12 | 'background-color', 13 | 'background-image', 14 | 'background-repeat', 15 | 'background-position', 16 | 'background-position-x', 17 | 'background-position-y', 18 | 'background-size', 19 | 'background-clip', 20 | 'background-origin', 21 | 'background-attachment', 22 | 'background-blend-mode', 23 | ]; 24 | 25 | const b = [ 26 | 'outline', 27 | 'outline-width', 28 | 'outline-style', 29 | 'outline-color', 30 | 'outline-offset', 31 | 'box-shadow', 32 | 'box-decoration-break', 33 | 'transform', 34 | 'transform-origin', 35 | 'transform-style', 36 | 'backface-visibility', 37 | 'perspective', 38 | 'perspective-origin', 39 | 'visibility', 40 | 'cursor', 41 | 'opacity', 42 | 'filter', 43 | 'isolation', 44 | 'backdrop-filter', 45 | 'mix-blend-mode', 46 | ]; 47 | 48 | module.exports = [].concat(a, borderProps, b); 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier-plugin-style-order", 3 | "version": "0.1.0", 4 | "description": "Prettier plugin which sorts related style property declarations", 5 | "keywords": [ 6 | "prettier", 7 | "plugin", 8 | "style", 9 | "css", 10 | "order", 11 | "group" 12 | ], 13 | "license": "MIT", 14 | "author": { 15 | "name": "Nate Moore", 16 | "email": "nate@natemoo.re", 17 | "url": "https://natemoo.re/" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/natemoo-re/prettier-plugin-style-order.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/natemoo-re/prettier-plugin-style-order/issues" 25 | }, 26 | "homepage": "https://github.com/natemoo-re/prettier-plugin-style-order", 27 | "files": [ 28 | "src" 29 | ], 30 | "main": "src/index.js", 31 | "directories": { 32 | "src": "src", 33 | "test": "test" 34 | }, 35 | "scripts": { 36 | "test": "node test" 37 | }, 38 | "dependencies": { 39 | "postcss-sorting": "5.0.1" 40 | }, 41 | "devDependencies": { 42 | "prettier": "1.19.1" 43 | }, 44 | "peerDependencies": { 45 | "prettier": ">= 1.13" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/groups/boxModel.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'display', 3 | 'flex', 4 | 'flex-basis', 5 | 'flex-direction', 6 | 'flex-flow', 7 | 'flex-grow', 8 | 'flex-shrink', 9 | 'flex-wrap', 10 | 'grid', 11 | 'grid-area', 12 | 'grid-auto-rows', 13 | 'grid-auto-columns', 14 | 'grid-auto-flow', 15 | 'grid-gap', 16 | 'grid-row', 17 | 'grid-row-start', 18 | 'grid-row-end', 19 | 'grid-row-gap', 20 | 'grid-column', 21 | 'grid-column-start', 22 | 'grid-column-end', 23 | 'grid-column-gap', 24 | 'grid-template', 25 | 'grid-template-areas', 26 | 'grid-template-rows', 27 | 'grid-template-columns', 28 | 'gap', 29 | 'align-content', 30 | 'align-items', 31 | 'align-self', 32 | 'justify-content', 33 | 'justify-items', 34 | 'justify-self', 35 | 'order', 36 | 'float', 37 | 'clear', 38 | 'box-sizing', 39 | 'width', 40 | 'min-width', 41 | 'max-width', 42 | 'height', 43 | 'min-height', 44 | 'max-height', 45 | 'margin', 46 | 'margin-top', 47 | 'margin-right', 48 | 'margin-bottom', 49 | 'margin-left', 50 | 'padding', 51 | 'padding-top', 52 | 'padding-right', 53 | 'padding-bottom', 54 | 'padding-left', 55 | 'object-fit', 56 | 'object-position', 57 | 'overflow', 58 | 'overflow-x', 59 | 'overflow-y', 60 | ] -------------------------------------------------------------------------------- /src/groups/typography.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'color', 3 | 'font', 4 | 'font-weight', 5 | 'font-size', 6 | 'font-family', 7 | 'font-style', 8 | 'font-variant', 9 | 'font-size-adjust', 10 | 'font-stretch', 11 | 'font-effect', 12 | 'font-emphasize', 13 | 'font-emphasize-position', 14 | 'font-emphasize-style', 15 | 'font-smooth', 16 | 'line-height', 17 | 'direction', 18 | 'letter-spacing', 19 | 'white-space', 20 | 'text-align', 21 | 'text-align-last', 22 | 'text-transform', 23 | 'text-decoration', 24 | 'text-emphasis', 25 | 'text-emphasis-color', 26 | 'text-emphasis-style', 27 | 'text-emphasis-position', 28 | 'text-indent', 29 | 'text-justify', 30 | 'text-outline', 31 | 'text-wrap', 32 | 'text-overflow', 33 | 'text-overflow-ellipsis', 34 | 'text-overflow-mode', 35 | 'text-orientation', 36 | 'text-shadow', 37 | 'vertical-align', 38 | 'word-wrap', 39 | 'word-break', 40 | 'word-spacing', 41 | 'overflow-wrap', 42 | 'tab-size', 43 | 'hyphens', 44 | 'unicode-bidi', 45 | 'columns', 46 | 'column-count', 47 | 'column-fill', 48 | 'column-gap', 49 | 'column-rule', 50 | 'column-rule-color', 51 | 'column-rule-style', 52 | 'column-rule-width', 53 | 'column-span', 54 | 'column-width', 55 | 'page-break-after', 56 | 'page-break-before', 57 | 'page-break-inside', 58 | 'src', 59 | ]; 60 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const prettier = require('prettier'); 2 | 3 | const red = str => '\033[31m' + str + '\033[39m' 4 | const green = str => '\033[32m' + str + '\033[39m'; 5 | 6 | const tests = { 7 | css: { 8 | input: `a { 9 | width: auto; 10 | height: auto; 11 | display: block; 12 | margin: 10px; 13 | position: relative; 14 | color: red; 15 | padding: 10px; 16 | border: 1px solid blue; 17 | background: white; 18 | } 19 | `, 20 | expected: `a { 21 | position: relative; 22 | display: block; 23 | width: auto; 24 | height: auto; 25 | margin: 10px; 26 | padding: 10px; 27 | color: red; 28 | background: white; 29 | border: 1px solid blue; 30 | } 31 | `, 32 | }, 33 | scss: { 34 | input: `a { 35 | width: auto; 36 | height: auto; 37 | display: block; 38 | margin: 10px; 39 | position: relative; 40 | color: red; 41 | padding: 10px; 42 | border: 1px solid blue; 43 | background: white; 44 | 45 | &:hover { 46 | color: red; 47 | padding: 10px; 48 | border: 1px solid blue; 49 | background: white; 50 | position: absolute; 51 | } 52 | } 53 | `, 54 | expected: `a { 55 | position: relative; 56 | display: block; 57 | width: auto; 58 | height: auto; 59 | margin: 10px; 60 | padding: 10px; 61 | color: red; 62 | background: white; 63 | border: 1px solid blue; 64 | 65 | &:hover { 66 | position: absolute; 67 | padding: 10px; 68 | color: red; 69 | background: white; 70 | border: 1px solid blue; 71 | } 72 | } 73 | `, 74 | }, 75 | } 76 | 77 | const color = passed => passed ? green : red; 78 | 79 | async function testGroup(ext) { 80 | const files = ['style', 'foo/bar/style', 'foo\\bar\\styles']; 81 | console.group(ext) 82 | files 83 | .map(n => `${n}.${ext}`) 84 | .forEach(filepath => { 85 | const { input, expected } = tests[ext]; 86 | const output = prettier.format(input, { 87 | filepath, 88 | plugins: ['.'], 89 | }) 90 | const passed = output === expected; 91 | console.log(color(passed)(`${passed ? '✔' : '✖'} ${filepath}`)); 92 | if (!passed) { 93 | console.log(output, expected); 94 | process.exitCode = 1 95 | } 96 | }) 97 | console.groupEnd() 98 | } 99 | 100 | async function run() { 101 | const exts = Object.keys(tests); 102 | exts.forEach(ext => testGroup(ext)); 103 | } 104 | 105 | run(); --------------------------------------------------------------------------------