diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3778ea8..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/fixtures/*.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 4a159e3..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": [ - "eslint-config-hudochenkov", - "eslint-config-prettier" - ], - "env": { - "browser": false, - "node": true, - "es6": true, - "jest": true - }, - "globals": { - "groupTest": true, - "runTest": true - } -} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..8a9a7b2 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,27 @@ +name: Lint + +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +jobs: + lint: + name: Lint on Node.js LTS + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 'lts/*' + + - run: npm ci + + - name: Lint and Test + run: npm run lint diff --git a/.github/workflows/nodejs.yml b/.github/workflows/test.yml similarity index 60% rename from .github/workflows/nodejs.yml rename to .github/workflows/test.yml index bb6758b..188033b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: CI +name: Test on: push: @@ -10,23 +10,23 @@ on: jobs: test: - name: Lint and test on Node.js ${{ matrix.node }} + name: Test on Node.js ${{ matrix.node }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - node: [12, 14, 16] + node: [16, 18] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - run: npm ci - - name: Lint and Test + - name: Test run: npm test diff --git a/.husky/pre-commit b/.husky/pre-commit index 36af219..c27d889 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx lint-staged +lint-staged diff --git a/CHANGELOG.md b/CHANGELOG.md index a50d8d8..9990bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## 9.1.0 +* Added more shorthand data + +## 9.0.0 +* Ignore properties case when sorting declarations. +* Added more shorthands properties to look for. Will affect alphabetical order. + +## 8.0.2 +* Fixed sorting inside CSS-in-JS `css` helper +* Fixed crash when using postcss-sass syntax + +## 8.0.1 +* Fix regression causing root of CSS or SCSS to be sorted. + +## 8.0.0 +* Dropped Node.js 12 and 14 support. +* Added support for `postcss-styled-syntax`. + +## 7.0.1 +* Internal refactoring for compatibility with newer stylelint-order version. No public API changes. + ## 7.0.0 * Dropped Node.js 10 support. * Reduced package install size. diff --git a/README.md b/README.md index b3ea16e..89b9ec7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Lint and autofix stylesheet order with [stylelint-order]. * Sorts properties. * Sorts at-rules by different options. * Groups properties, custom properties, dollar variables, nested rules, nested at-rules. -* Supports CSS, SCSS (using [postcss-scss]), CSS-in-JS (with [@stylelint/postcss-css-in-js]), HTML (with [postcss-html]), and most likely any other syntax added by other PostCSS plugins. +* Supports CSS, SCSS (using [postcss-scss]), CSS-in-JS (with [postcss-styled-syntax]), HTML (with [postcss-html]), and most likely any other syntax added by other PostCSS plugins. ## Installation @@ -87,7 +87,7 @@ Create a `postcss.config.js` with PostCSS Sorting configuration: ```js module.exports = { - plugins: [ + plugins: { 'postcss-sorting': { order: [ 'custom-properties', @@ -101,7 +101,7 @@ module.exports = { 'unspecified-properties-position': 'bottom', }, - ], + }, }; ``` @@ -167,7 +167,7 @@ I recommend [Prettier] for formatting stylesheets. [gulp-postcss]: https://github.com/postcss/gulp-postcss [postcss-scss]: https://github.com/postcss/postcss-scss [postcss-html]: https://github.com/gucong3000/postcss-html -[@stylelint/postcss-css-in-js]: https://github.com/stylelint/postcss-css-in-js +[postcss-styled-syntax]: https://github.com/hudochenkov/postcss-styled-syntax [Prettier]: https://prettier.io/ [stylelint]: https://stylelint.io/ [stylelint-order]: https://github.com/hudochenkov/stylelint-order diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..2bf904e --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,22 @@ +import { configs } from 'eslint-config-hudochenkov'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import globals from 'globals'; + +export default [ + { + ignores: ['**/fixtures/*.js'], + }, + ...configs.main, + eslintConfigPrettier, + { + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, 'off'])), + ...globals.node, + ...globals.jest, + groupTest: true, + runTest: true, + }, + }, + }, +]; diff --git a/index.js b/index.js index 93f7020..5624a1c 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,10 @@ -const order = require('./lib/order'); -const propertiesOrder = require('./lib/properties-order'); const validateOptions = require('./lib/validateOptions'); const isString = require('./lib/isString'); +const getContainingNode = require('./lib/getContainingNode'); +const sortNode = require('./lib/order/sortNode'); +const sortNodeProperties = require('./lib/properties-order/sortNodeProperties'); -module.exports = (opts) => { +module.exports = function postcssSorting(opts) { return { postcssPlugin: 'postcss-sorting', Root(css) { @@ -37,10 +38,21 @@ function plugin(css, opts) { } if (opts.order) { - order(css, opts); + css.walk((input) => { + const node = getContainingNode(input); + + sortNode(node, opts.order); + }); } if (opts['properties-order']) { - propertiesOrder(css, opts); + css.walk((input) => { + const node = getContainingNode(input); + + sortNodeProperties(node, { + order: opts['properties-order'], + unspecifiedPropertiesPosition: opts['unspecified-properties-position'] || 'bottom', + }); + }); } } diff --git a/jest-setup.js b/jest-setup.js index e207a8e..5a61ec7 100644 --- a/jest-setup.js +++ b/jest-setup.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const plugin = require('.'); const postcss = require('postcss'); -const cssInJS = require('@stylelint/postcss-css-in-js'); // eslint-disable-line import/no-extraneous-dependencies +const cssInJS = require('postcss-styled-syntax'); // eslint-disable-line import/no-extraneous-dependencies const html = require('postcss-html'); // eslint-disable-line import/no-extraneous-dependencies global.groupTest = function groupTest(testGroups) { @@ -19,7 +19,7 @@ global.groupTest = function groupTest(testGroups) { .process(item.fixture, { from: undefined }) .then((root) => { expect(root.css).toEqual(item.expected); - }) + }), ); }); }); @@ -44,13 +44,13 @@ global.runTest = function runTest(input, opts, dirname) { try { inputCSS = fs.readFileSync(inputPath, 'utf8'); - } catch (error) { + } catch { fs.writeFileSync(inputPath, inputCSS); } try { expectCSS = fs.readFileSync(expectPath, 'utf8'); - } catch (error) { + } catch { fs.writeFileSync(expectPath, expectCSS); } diff --git a/lib/__tests__/test.js b/lib/__tests__/test.js index a2a3e3c..6519cdb 100644 --- a/lib/__tests__/test.js +++ b/lib/__tests__/test.js @@ -11,14 +11,7 @@ test(`Should throw an error if config has error`, () => { order: 'Justice Rains From Above', }; - const pluginRun = postcss([plugin(opts)]) - .process('', { from: undefined }) - .then(() => { - expect('Plugin should throw an error').toBeFalst(); - }) - .catch((err) => { - throw err; - }); - - expect(pluginRun).rejects.toBeTruthy(); + return expect(postcss([plugin(opts)]).process('', { from: undefined })).rejects.toThrow( + 'postcss-sorting: order: Should be an array', + ); }); diff --git a/lib/__tests__/vendor.test.js b/lib/__tests__/vendor.test.js deleted file mode 100644 index 8e78ece..0000000 --- a/lib/__tests__/vendor.test.js +++ /dev/null @@ -1,15 +0,0 @@ -let vendor = require('../vendor'); - -const VALUE = '-1px -1px 1px rgba(0, 0, 0, 0.2) inset'; - -it('returns prefix', () => { - expect(vendor.prefix('-moz-color')).toEqual('-moz-'); - expect(vendor.prefix('color')).toEqual(''); - expect(vendor.prefix(VALUE)).toEqual(''); -}); - -it('returns unprefixed version', () => { - expect(vendor.unprefixed('-moz-color')).toEqual('color'); - expect(vendor.unprefixed('color')).toEqual('color'); - expect(vendor.unprefixed(VALUE)).toEqual(VALUE); -}); diff --git a/lib/getComments.js b/lib/getComments.js index 5546c96..82dffff 100644 --- a/lib/getComments.js +++ b/lib/getComments.js @@ -23,7 +23,7 @@ function beforeNode(comments, previousNode, node, currentInitialIndex) { previousNode.position = node.position; previousNode.initialIndex = initialIndex - 0.0001; - const newComments = [previousNode].concat(comments); + const newComments = [previousNode, ...comments]; return beforeNode(newComments, previousNode.prev(), node, previousNode.initialIndex); } @@ -43,7 +43,7 @@ function afterNode(comments, nextNode, node, currentInitialIndex) { nextNode.position = node.position; nextNode.initialIndex = initialIndex + 0.0001; - return afterNode(comments.concat(nextNode), nextNode.next(), node, nextNode.initialIndex); + return afterNode([...comments, nextNode], nextNode.next(), node, nextNode.initialIndex); } // eslint-disable-next-line max-params @@ -70,7 +70,7 @@ function beforeDeclaration(comments, previousNode, nodeData, currentInitialIndex // add a marker previousNode.sortProperty = true; - const newComments = [commentData].concat(comments); + const newComments = [commentData, ...comments]; return beforeDeclaration(newComments, previousNode.prev(), nodeData, commentData.initialIndex); } @@ -100,9 +100,9 @@ function afterDeclaration(comments, nextNode, nodeData, currentInitialIndex) { nextNode.sortProperty = true; return afterDeclaration( - comments.concat(commentData), + [...comments, commentData], nextNode.next(), nodeData, - commentData.initialIndex + commentData.initialIndex, ); } diff --git a/lib/getContainingNode.js b/lib/getContainingNode.js index 89b9402..848194b 100644 --- a/lib/getContainingNode.js +++ b/lib/getContainingNode.js @@ -1,12 +1,15 @@ module.exports = function getContainingNode(node) { - // For styled-components declarations are children of Root node - if ( - node.type !== 'rule' && - node.type !== 'atrule' && - node.parent.document && - node.parent.document.nodes && - node.parent.document.nodes.some((item) => item.type === 'root') - ) { + if (node.type === 'rule' || node.type === 'atrule') { + return node; + } + + // postcss-styled-syntax: declarations are children of Root node + if (node.parent?.type === 'root' && node.parent?.raws.isRuleLike) { + return node.parent; + } + + // @stylelint/postcss-css-in-js: declarations are children of Root node + if (node.parent?.document?.nodes?.some((item) => item.type === 'root')) { return node.parent; } diff --git a/lib/isAllowedToProcess.js b/lib/isAllowedToProcess.js new file mode 100644 index 0000000..dc50782 --- /dev/null +++ b/lib/isAllowedToProcess.js @@ -0,0 +1,28 @@ +const atRuleExcludes = ['function', 'if', 'else', 'for', 'each', 'while']; + +module.exports = function isAllowedToProcess(node) { + if (node.type === 'atrule' && atRuleExcludes.includes(node.name)) { + return false; + } + + if (!node?.nodes?.length) { + return false; + } + + // postcss-styled-syntax: Interpolations at the end of node + if (node.raws.after?.includes('${')) { + return false; + } + + // postcss-styled-syntax: Interpolations among children of a node + if (node.nodes.some((item) => item.raws.before.includes('${'))) { + return false; + } + + // @stylelint/postcss-css-in-js only + if (node.nodes.some((item) => item.type === 'literal')) { + return false; + } + + return true; +}; diff --git a/lib/isRuleWithNodes.js b/lib/isRuleWithNodes.js deleted file mode 100644 index 2aab318..0000000 --- a/lib/isRuleWithNodes.js +++ /dev/null @@ -1,9 +0,0 @@ -const atRuleExcludes = ['function', 'if', 'else', 'for', 'each', 'while']; - -module.exports = function isRuleWithNodes(node) { - if (node.type === 'atrule' && atRuleExcludes.includes(node.name)) { - return false; - } - - return node.nodes && node.nodes.length && !node.nodes.some((item) => item.type === 'literal'); -}; diff --git a/lib/order/__tests__/order.js b/lib/order/__tests__/order.js index 680de85..3c7a8a3 100644 --- a/lib/order/__tests__/order.js +++ b/lib/order/__tests__/order.js @@ -17,7 +17,7 @@ test('Should assign comments before and after nodes correctly (order)', () => { order: ['custom-properties', 'dollar-variables', 'at-variables', 'declarations'], }, - __dirname + __dirname, )); test('Should sort by keywords', () => @@ -33,7 +33,7 @@ test('Should sort by keywords', () => 'at-rules', ], }, - __dirname + __dirname, )); test('At-rules combination from most specified to least specified', () => @@ -70,7 +70,7 @@ test('At-rules combination from most specified to least specified', () => }, ], }, - __dirname + __dirname, )); test('At-rules mixed combination', () => @@ -104,7 +104,7 @@ test('At-rules mixed combination', () => }, ], }, - __dirname + __dirname, )); test('Should sort inside nested rules', () => @@ -113,7 +113,7 @@ test('Should sort inside nested rules', () => { order: ['custom-properties', 'declarations', 'rules'], }, - __dirname + __dirname, )); test('Should sort inside nested at-rules', () => @@ -122,7 +122,7 @@ test('Should sort inside nested at-rules', () => { order: ['custom-properties', 'declarations', 'at-rules'], }, - __dirname + __dirname, )); test('Should move unspecified nodes to the bottom', () => @@ -131,7 +131,7 @@ test('Should move unspecified nodes to the bottom', () => { order: ['custom-properties', 'declarations'], }, - __dirname + __dirname, )); test('Should preserve indentation', () => @@ -140,7 +140,7 @@ test('Should preserve indentation', () => { order: ['declarations', 'rules', 'at-rules'], }, - __dirname + __dirname, )); groupTest([ @@ -496,6 +496,17 @@ groupTest([ } `, }, + { + description: 'should not change in the root', + fixture: ` + display: none; + @include hello; + `, + expected: ` + display: none; + @include hello; + `, + }, { fixture: ` a { @@ -602,7 +613,7 @@ test('Should sort by keywords (styled)', () => { order: ['declarations', 'rules', 'at-rules'], }, - __dirname + __dirname, )); test('Ignore nodes with template literals (styled)', () => @@ -611,5 +622,5 @@ test('Ignore nodes with template literals (styled)', () => { order: ['declarations', 'rules', 'at-rules'], }, - __dirname + __dirname, )); diff --git a/lib/calcAtRulePatternPriority.js b/lib/order/calcAtRulePatternPriority.js similarity index 100% rename from lib/calcAtRulePatternPriority.js rename to lib/order/calcAtRulePatternPriority.js diff --git a/lib/calcRulePatternPriority.js b/lib/order/calcRulePatternPriority.js similarity index 100% rename from lib/calcRulePatternPriority.js rename to lib/order/calcRulePatternPriority.js diff --git a/lib/createExpectedOrder.js b/lib/order/createExpectedOrder.js similarity index 97% rename from lib/createExpectedOrder.js rename to lib/order/createExpectedOrder.js index bbbe112..634ba87 100644 --- a/lib/createExpectedOrder.js +++ b/lib/order/createExpectedOrder.js @@ -1,4 +1,4 @@ -const isString = require('./isString'); +const isString = require('../isString'); module.exports = function createExpectedOrder(input) { const order = {}; diff --git a/lib/getOrderData.js b/lib/order/getOrderData.js similarity index 94% rename from lib/getOrderData.js rename to lib/order/getOrderData.js index 51b7f96..d528ac3 100644 --- a/lib/getOrderData.js +++ b/lib/order/getOrderData.js @@ -1,5 +1,5 @@ -const isStandardSyntaxProperty = require('./isStandardSyntaxProperty'); -const isCustomProperty = require('./isCustomProperty'); +const isStandardSyntaxProperty = require('../isStandardSyntaxProperty'); +const isCustomProperty = require('../isCustomProperty'); const isDollarVariable = require('./isDollarVariable'); const isAtVariable = require('./isAtVariable'); const calcAtRulePatternPriority = require('./calcAtRulePatternPriority'); diff --git a/lib/order/index.js b/lib/order/index.js deleted file mode 100644 index 2d29528..0000000 --- a/lib/order/index.js +++ /dev/null @@ -1,39 +0,0 @@ -const createExpectedOrder = require('../createExpectedOrder'); -const isRuleWithNodes = require('../isRuleWithNodes'); -const processLastComments = require('../processLastComments'); -const processMostNodes = require('../processMostNodes'); -const getContainingNode = require('../getContainingNode'); -const sorting = require('../sorting'); - -module.exports = function order(css, opts) { - const expectedOrder = createExpectedOrder(opts.order); - - css.walk((input) => { - const node = getContainingNode(input); - - if (isRuleWithNodes(node)) { - // Nodes for sorting - let processed = []; - - // Add indexes to nodes - node.each((childNode, index) => { - processed = processMostNodes(childNode, index, expectedOrder, processed); - }); - - // Add last comments in the rule. Need this because last comments are not belonging to anything - node.each((childNode, index) => { - processed = processLastComments(childNode, index, processed); - }); - - // Sort declarations saved for sorting - processed.sort(sorting.sortByIndexes); - - // Enforce semicolon for the last node - node.raws.semicolon = true; - - // Replace rule content with sorted one - node.removeAll(); - node.append(processed); - } - }); -}; diff --git a/lib/isAtVariable.js b/lib/order/isAtVariable.js similarity index 100% rename from lib/isAtVariable.js rename to lib/order/isAtVariable.js diff --git a/lib/isDollarVariable.js b/lib/order/isDollarVariable.js similarity index 100% rename from lib/isDollarVariable.js rename to lib/order/isDollarVariable.js diff --git a/lib/processLastComments.js b/lib/order/processLastComments.js similarity index 86% rename from lib/processLastComments.js rename to lib/order/processLastComments.js index 883eed3..376d488 100644 --- a/lib/processLastComments.js +++ b/lib/order/processLastComments.js @@ -3,7 +3,7 @@ module.exports = function processLastComments(node, index, processedNodes) { node.position = Infinity; node.initialIndex = index; - return processedNodes.concat(node); + return [...processedNodes, node]; } return processedNodes; diff --git a/lib/processMostNodes.js b/lib/order/processMostNodes.js similarity index 85% rename from lib/processMostNodes.js rename to lib/order/processMostNodes.js index d8d4975..d836894 100644 --- a/lib/processMostNodes.js +++ b/lib/order/processMostNodes.js @@ -1,5 +1,5 @@ const getOrderData = require('./getOrderData'); -const getComments = require('./getComments'); +const getComments = require('../getComments'); // eslint-disable-next-line max-params module.exports = function processMostNodes(node, index, order, processedNodes) { @@ -18,5 +18,5 @@ module.exports = function processMostNodes(node, index, order, processedNodes) { // If comment on same line with the node and node, use node's indexes for comment const commentsAfter = getComments.afterNode([], node.next(), node); - return processedNodes.concat(commentsBefore, node, commentsAfter); + return [...processedNodes, ...commentsBefore, node, ...commentsAfter]; }; diff --git a/lib/order/sortByIndexes.js b/lib/order/sortByIndexes.js new file mode 100644 index 0000000..28c730b --- /dev/null +++ b/lib/order/sortByIndexes.js @@ -0,0 +1,13 @@ +module.exports = function sortByIndexes(a, b) { + // If a and b have the same group index, and a's property index is + // higher than b's property index, in a sorted list a appears after + // b: + if (a.position !== b.position) { + return a.position - b.position; + } + + // If a and b have the same group index and the same property index, + // in a sorted list they appear in the same order they were in + // original array: + return a.initialIndex - b.initialIndex; +}; diff --git a/lib/order/sortNode.js b/lib/order/sortNode.js new file mode 100644 index 0000000..a3559c6 --- /dev/null +++ b/lib/order/sortNode.js @@ -0,0 +1,36 @@ +const createExpectedOrder = require('./createExpectedOrder'); +const isAllowedToProcess = require('../isAllowedToProcess'); +const processLastComments = require('./processLastComments'); +const processMostNodes = require('./processMostNodes'); +const sortByIndexes = require('./sortByIndexes'); + +module.exports = function sortNode(node, optsOrder) { + if (!isAllowedToProcess(node)) { + return; + } + + const expectedOrder = createExpectedOrder(optsOrder); + + // Nodes for sorting + let processed = []; + + // Add indexes to nodes + node.each((childNode, index) => { + processed = processMostNodes(childNode, index, expectedOrder, processed); + }); + + // Add last comments in the rule. Need this because last comments are not belonging to anything + node.each((childNode, index) => { + processed = processLastComments(childNode, index, processed); + }); + + // Sort declarations saved for sorting + processed.sort(sortByIndexes); + + // Enforce semicolon for the last node + node.raws.semicolon = true; + + // Replace rule content with sorted one + node.removeAll(); + node.append(processed); +}; diff --git a/lib/properties-order/__tests__/fixtures/inside-css-helper.expected.js b/lib/properties-order/__tests__/fixtures/inside-css-helper.expected.js new file mode 100644 index 0000000..de3e5b8 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/inside-css-helper.expected.js @@ -0,0 +1,8 @@ +const Component = styled.div` + ${() => css` + position: absolute; + top: 0; + display: block; + z-index: 2; + `}; +`; diff --git a/lib/properties-order/__tests__/fixtures/inside-css-helper.js b/lib/properties-order/__tests__/fixtures/inside-css-helper.js new file mode 100644 index 0000000..79278e4 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/inside-css-helper.js @@ -0,0 +1,8 @@ +const Component = styled.div` + ${() => css` + z-index: 2; + top: 0; + position: absolute; + display: block; + `}; +`; diff --git a/lib/properties-order/__tests__/fixtures/properties-alphabetical-case-insensitive.css b/lib/properties-order/__tests__/fixtures/properties-alphabetical-case-insensitive.css new file mode 100644 index 0000000..185c451 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/properties-alphabetical-case-insensitive.css @@ -0,0 +1,6 @@ +a { + Border: red; + align-content: center; + font-family: sans-serif; + Display: block; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-alphabetical-case-insensitive.expected.css b/lib/properties-order/__tests__/fixtures/properties-alphabetical-case-insensitive.expected.css new file mode 100644 index 0000000..47d722a --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/properties-alphabetical-case-insensitive.expected.css @@ -0,0 +1,6 @@ +a { + align-content: center; + Border: red; + Display: block; + font-family: sans-serif; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.css b/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.css index b78cab9..f67de92 100644 --- a/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.css +++ b/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.css @@ -21,3 +21,26 @@ a { border-color: transparent; border-bottom-color: pink; } + +a { + margin-inline: 0; + margin: 0; +} + +a { + margin-bottom: 0; + margin-inline: 0; + margin: 0; +} + +a { + margin-top: 0; + margin-inline: 0; + margin: 0; +} + +a { + margin-bottom: 0; + margin-inline-start: 0; + margin: 0; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.expected.css b/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.expected.css index 468932f..e4aeffa 100644 --- a/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.expected.css +++ b/lib/properties-order/__tests__/fixtures/properties-alphabetical-shorthand.expected.css @@ -21,3 +21,26 @@ a { border-color: transparent; border-bottom-color: pink; } + +a { + margin: 0; + margin-inline: 0; +} + +a { + margin: 0; + margin-inline: 0; + margin-bottom: 0; +} + +a { + margin: 0; + margin-inline: 0; + margin-top: 0; +} + +a { + margin: 0; + margin-inline-start: 0; + margin-bottom: 0; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-have-same-name-4.css b/lib/properties-order/__tests__/fixtures/properties-have-same-name-4.css new file mode 100644 index 0000000..02ab582 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/properties-have-same-name-4.css @@ -0,0 +1,11 @@ +div { + position: fixed; + top: 0; + Top: 0; +} + +div { + position: fixed; + Top: 0; + top: 0; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-have-same-name-4.expected.css b/lib/properties-order/__tests__/fixtures/properties-have-same-name-4.expected.css new file mode 100644 index 0000000..02ab582 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/properties-have-same-name-4.expected.css @@ -0,0 +1,11 @@ +div { + position: fixed; + top: 0; + Top: 0; +} + +div { + position: fixed; + Top: 0; + top: 0; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-have-same-name-5.css b/lib/properties-order/__tests__/fixtures/properties-have-same-name-5.css new file mode 100644 index 0000000..ee7d0e9 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/properties-have-same-name-5.css @@ -0,0 +1,9 @@ +.header { + Padding-bottom: 20px; + Justify-content: space-between; + display: -webkit-box; + Display: -ms-flexbox; + display: flex; + Align-items: center; + flex-wrap: wrap; +} diff --git a/lib/properties-order/__tests__/fixtures/properties-have-same-name-5.expected.css b/lib/properties-order/__tests__/fixtures/properties-have-same-name-5.expected.css new file mode 100644 index 0000000..cbdbdf1 --- /dev/null +++ b/lib/properties-order/__tests__/fixtures/properties-have-same-name-5.expected.css @@ -0,0 +1,9 @@ +.header { + Align-items: center; + display: -webkit-box; + Display: -ms-flexbox; + display: flex; + flex-wrap: wrap; + Justify-content: space-between; + Padding-bottom: 20px; +} diff --git a/lib/properties-order/__tests__/isShorthand.test.js b/lib/properties-order/__tests__/isShorthand.test.js new file mode 100644 index 0000000..1396843 --- /dev/null +++ b/lib/properties-order/__tests__/isShorthand.test.js @@ -0,0 +1,25 @@ +const { isShorthand } = require('../isShorthand'); + +test('margin is shorthand for margin-top', () => { + expect(isShorthand('margin', 'margin-top')).toBe(true); +}); + +test('margin-top is not shorthand for margin', () => { + expect(isShorthand('margin-top', 'margin')).toBe(false); +}); + +test('margin-block is shorthand for margin-top', () => { + expect(isShorthand('margin-block', 'margin-top')).toBe(true); +}); + +test('margin-top is not shorthand for margin-block', () => { + expect(isShorthand('margin-top', 'margin-block')).toBe(false); +}); + +test('border-inline is shorthand for border-top-color', () => { + expect(isShorthand('border-inline', 'border-top-color')).toBe(true); +}); + +test('border-top-color is not shorthand for border-inline', () => { + expect(isShorthand('border-top-color', 'border-inline')).toBe(false); +}); diff --git a/lib/properties-order/__tests__/properties-order.js b/lib/properties-order/__tests__/properties-order.js index 9f0456f..9a7de6f 100644 --- a/lib/properties-order/__tests__/properties-order.js +++ b/lib/properties-order/__tests__/properties-order.js @@ -4,7 +4,7 @@ test('Should sort properties (array config)', () => { 'properties-order': ['position', 'top', 'display', 'z-index'], }, - __dirname + __dirname, )); test('Should sort prefixed properties before unprefixed property', () => @@ -13,7 +13,7 @@ test('Should sort prefixed properties before unprefixed property', () => { 'properties-order': ['position', '-webkit-box-sizing', 'box-sizing', 'width'], }, - __dirname + __dirname, )); test('Should assign comments before and after declarations correctly (properties-order)', () => @@ -22,7 +22,7 @@ test('Should assign comments before and after declarations correctly (properties { 'properties-order': ['border-bottom', 'font-style'], }, - __dirname + __dirname, )); test('Should place the leftovers properties in the end (not specified)', () => @@ -31,7 +31,7 @@ test('Should place the leftovers properties in the end (not specified)', () => { 'properties-order': ['position', 'z-index'], }, - __dirname + __dirname, )); test('Should place the leftovers properties in the end (bottom)', () => @@ -41,7 +41,7 @@ test('Should place the leftovers properties in the end (bottom)', () => 'properties-order': ['position', 'z-index'], 'unspecified-properties-position': 'bottom', }, - __dirname + __dirname, )); test('Should place the leftovers properties in the beginning (top)', () => @@ -51,7 +51,7 @@ test('Should place the leftovers properties in the beginning (top)', () => 'properties-order': ['position', 'z-index'], 'unspecified-properties-position': 'top', }, - __dirname + __dirname, )); test('Should place the leftovers properties in the end (bottomAlphabetical)', () => @@ -61,7 +61,7 @@ test('Should place the leftovers properties in the end (bottomAlphabetical)', () 'properties-order': ['position', 'z-index'], 'unspecified-properties-position': 'bottomAlphabetical', }, - __dirname + __dirname, )); test('Should preserve order if properties have same name', () => @@ -70,7 +70,7 @@ test('Should preserve order if properties have same name', () => { 'properties-order': ['position', 'z-index'], }, - __dirname + __dirname, )); test('Should preserve order if properties have same name', () => @@ -79,7 +79,7 @@ test('Should preserve order if properties have same name', () => { 'properties-order': ['position', 'z-index'], }, - __dirname + __dirname, )); test('Should preserve order if properties have same name', () => @@ -88,7 +88,25 @@ test('Should preserve order if properties have same name', () => { 'properties-order': 'alphabetical', }, - __dirname + __dirname, + )); + +test('Should preserve order if properties have same name (case insensitive)', () => + runTest( + 'properties-have-same-name-4', + { + 'properties-order': ['position', 'z-index'], + }, + __dirname, + )); + +test('Should preserve order if properties have same name (case insensitive)', () => + runTest( + 'properties-have-same-name-5', + { + 'properties-order': 'alphabetical', + }, + __dirname, )); test(`Should not remove first comment in the rule if it's not on separate line (properties-order)`, () => @@ -97,7 +115,7 @@ test(`Should not remove first comment in the rule if it's not on separate line ( { 'properties-order': ['display'], }, - __dirname + __dirname, )); test(`Should sort declarations grouped together between not declarations (without comments)`, () => @@ -106,7 +124,7 @@ test(`Should sort declarations grouped together between not declarations (withou { 'properties-order': ['display', 'position'], }, - __dirname + __dirname, )); test(`Should sort declarations grouped together between not declarations (with comments)`, () => @@ -115,7 +133,7 @@ test(`Should sort declarations grouped together between not declarations (with c { 'properties-order': ['display', 'position'], }, - __dirname + __dirname, )); test(`Should sort declarations scattered everywhere (without comments)`, () => @@ -124,7 +142,7 @@ test(`Should sort declarations scattered everywhere (without comments)`, () => { 'properties-order': ['display', 'position'], }, - __dirname + __dirname, )); test(`Should sort declarations scattered everywhere (with comments)`, () => @@ -133,7 +151,7 @@ test(`Should sort declarations scattered everywhere (with comments)`, () => { 'properties-order': ['display', 'position'], }, - __dirname + __dirname, )); test('Should sort properties alphabetically', () => @@ -142,16 +160,26 @@ test('Should sort properties alphabetically', () => { 'properties-order': 'alphabetical', }, - __dirname + __dirname, )); +test('Should sort properties alphabetically regardless of case', () => { + runTest( + 'properties-alphabetical-case-insensitive', + { + 'properties-order': 'alphabetical', + }, + __dirname, + ); +}); + test('Should sort shorthand properties before their longhand versions', () => runTest( 'properties-alphabetical-shorthand', { 'properties-order': 'alphabetical', }, - __dirname + __dirname, )); test('Should sort prefixed properties before unprefixed property in alphabetical order', () => @@ -160,7 +188,7 @@ test('Should sort prefixed properties before unprefixed property in alphabetical { 'properties-order': 'alphabetical', }, - __dirname + __dirname, )); test('Should assign comments before and after declarations correctly (properties-order, alphabetical)', () => @@ -169,7 +197,7 @@ test('Should assign comments before and after declarations correctly (properties { 'properties-order': 'alphabetical', }, - __dirname + __dirname, )); test(`Preserve-empty-lines-between properties`, () => @@ -178,7 +206,7 @@ test(`Preserve-empty-lines-between properties`, () => { 'properties-order': ['position', 'top', 'display', 'z-index'], }, - __dirname + __dirname, )); test('Should sort properties (styled)', () => @@ -187,7 +215,7 @@ test('Should sort properties (styled)', () => { 'properties-order': ['position', 'top', 'display', 'z-index'], }, - __dirname + __dirname, )); test('Should sort properties in nested rules (styled)', () => @@ -196,7 +224,16 @@ test('Should sort properties in nested rules (styled)', () => { 'properties-order': ['position', 'top', 'display', 'z-index'], }, - __dirname + __dirname, + )); + +test('Should sort properties in css helper (styled)', () => + runTest( + 'inside-css-helper.js', + { + 'properties-order': ['position', 'top', 'display', 'z-index'], + }, + __dirname, )); test('Ignore template literals in flat components (styled)', () => @@ -205,7 +242,7 @@ test('Ignore template literals in flat components (styled)', () => { 'properties-order': ['position', 'top', 'display', 'z-index'], }, - __dirname + __dirname, )); test('Ignore template literals in nested components (styled)', () => @@ -214,7 +251,7 @@ test('Ignore template literals in nested components (styled)', () => { 'properties-order': ['position', 'top', 'display', 'z-index'], }, - __dirname + __dirname, )); test('Should sort properties (html,