From 6dc8f222f22ea8a6fccd59630143d3b1ea2fb7c7 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Wed, 11 Aug 2021 10:02:04 +0530 Subject: [PATCH] fix: multiline class sorting is not supported #161 (PR #221) * fix: respect separators while sorting fixes issue #161 * test(sort.test.ts): add tests for sorting * test(sort.test.ts): add tests for malformed HTML * perf(commands.ts, index.ts): improve sorting Use new strategy to sort classes and preserve formatting. * test(sort.test.ts): use new functions for testing --- src/lib/commands.ts | 5 +-- src/utils/index.ts | 36 +++++++++++++++++++-- test/sort.test.ts | 79 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 test/sort.test.ts diff --git a/src/lib/commands.ts b/src/lib/commands.ts index 18e50e2..3f87092 100644 --- a/src/lib/commands.ts +++ b/src/lib/commands.ts @@ -5,7 +5,7 @@ import { StyleSheet } from 'windicss/utils/style'; import { Log } from '../utils/console'; import { HTMLParser } from '../utils/parser'; -import { sortClassNames } from '../utils'; +import { rearrangeClasses, sortClassNames } from '../utils'; import { toggleConfig } from '../utils/helpers'; import type { Processor } from 'windicss/lib'; @@ -80,7 +80,8 @@ export default class Commands { for (const p of classes) { const sortedP = sortClassNames(p.result, variantsMap); - textEdit.replace(new Range(textEditor.document.positionAt(p.start), textEditor.document.positionAt(p.end)), sortedP); + const toReplace = rearrangeClasses(p.result, sortedP); + textEdit.replace(new Range(textEditor.document.positionAt(p.start), textEditor.document.positionAt(p.end)), toReplace); } }); } diff --git a/src/utils/index.ts b/src/utils/index.ts index 983b43b..ee9135c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -36,7 +36,7 @@ export function connectList(list: T[][]) { export function sortClassNames(classNames: string, variantsMap: { [key: string]: number }) { const variantsArray = Object.keys(variantsMap); - const ast = new ClassParser(classNames, ":", variantsArray).parse(); + const ast = new ClassParser(classNames, ':', variantsArray).parse(); return ast.map(({ raw, variants, important }) => { const head = variants.join(':') + ':'; const utility = raw.replace(head, ''); @@ -45,7 +45,39 @@ export function sortClassNames(classNames: string, variantsMap: { [key: string]: const offset = variants.map(i => variantsMap[i] * 100).reduce((p, c) => p + c, 0) + (important ? 500 : 0) + (hasDynamicValue ? 25 : 0); if (key === null) return { raw, weight: offset }; return { raw, weight: (keyOrder[key[0]] ?? 300) + offset }; - }).sort((a, b) => a.weight - b.weight).map(i => i.raw).join(' '); + }).sort((a, b) => a.weight - b.weight).map(i => i.raw); +} + +function replaceBetweenIndices( + origin: string, + startIndex: number, + endIndex: number, + find: string, + replace: string +) { + return ( + origin.substring(0, startIndex) + + origin.substring(startIndex, endIndex).replace(find, replace) + ); +} + +export function rearrangeClasses(classesText: string, sortedP: string[]) { + const unsortedClasses = classesText + .split(/\s+/) + .map((s) => s.trim()) + .filter(Boolean); + let occurrenceIndex = 0; + for (let i = 0; i < sortedP.length; i++) { + classesText = replaceBetweenIndices( + classesText, + occurrenceIndex, + classesText.length, + unsortedClasses[i], + sortedP[i] + ); + occurrenceIndex = occurrenceIndex + sortedP[0].length + 1; + } + return classesText; } export function rem2px(str?: string) { diff --git a/test/sort.test.ts b/test/sort.test.ts new file mode 100644 index 0000000..eafb42b --- /dev/null +++ b/test/sort.test.ts @@ -0,0 +1,79 @@ +import { HTMLParser } from 'windicss/utils/parser'; +import { + sortClassNames, + rearrangeClasses, +} from '../src/utils'; + +it('sorts classes single line', () => { + const parser = new HTMLParser(); + parser.html = ` +
+ `; + const p = parser.parseClasses()[0]; + const expected = 'bg-transparent text-transparent p-4 backdrop-blur'; + const sortedP = sortClassNames(p.result, {}); + const toReplace = rearrangeClasses(p.result, sortedP); + expect(toReplace).toBe(expected); +}); + +it('sorts classes single line but malformed', () => { + const parser = new HTMLParser(); + parser.html = ` +
+ `; + const p = parser.parseClasses()[0]; + const expected = ' bg-transparent text-transparent p-4 backdrop-blur'; + const sortedP = sortClassNames(p.result, {}); + const toReplace = rearrangeClasses(p.result, sortedP); + expect(toReplace).toBe(expected); +}); + +it('sorts classes multi line', () => { + const parser = new HTMLParser(); + parser.html = ` +
+`; + const p = parser.parseClasses()[0]; + const expected = ` + bg-transparent + text-transparent + p-4 + backdrop-blur + `; + + const sortedP = sortClassNames(p.result, {}); + const toReplace = rearrangeClasses(p.result, sortedP); + expect(toReplace).toBe(expected); +}); + +it('sorts classes multi line but malformed', () => { + const parser = new HTMLParser(); + parser.html = ` +
+`; + const p = parser.parseClasses()[0]; + const expected = ` + bg-transparent + text-transparent + p-4 +backdrop-blur + `; + + const sortedP = sortClassNames(p.result, {}); + const toReplace = rearrangeClasses(p.result, sortedP); + expect(toReplace).toBe(expected); +});