diff --git a/__tests__/helpers/arrayToRegex.ts b/__tests__/helpers/arrayToRegex.ts new file mode 100644 index 0000000..7fcd3b9 --- /dev/null +++ b/__tests__/helpers/arrayToRegex.ts @@ -0,0 +1,15 @@ +import arrayToRegex from '../../lib/helpers/arrayToRegex'; + +describe('arrayToRegex', () => { + it('should return null on empty array', () => { + expect(arrayToRegex([])).toBeNull(); + }); + + it('should sort by length', () => { + expect(arrayToRegex(['bb', 'ccc', 'a'])).toStrictEqual(/(ccc)|(bb)|(a)/g); + }); + + it('should add a specific modifier', () => { + expect(arrayToRegex(['bb', 'a', 'ccc'], (a) => `--${a}`)).toStrictEqual(/(--ccc)|(--bb)|(--a)/g); + }); +}); diff --git a/__tests__/replace.css.ts b/__tests__/replace.css.ts index 6a80789..e86b767 100644 --- a/__tests__/replace.css.ts +++ b/__tests__/replace.css.ts @@ -588,38 +588,6 @@ it('replace css variables in calc and multiple variables | rename-css-selectors# ); }); -it('replace css variables with deep nested and multiline | rename-css-selectors#43', () => { - replaceCssMacro( - ` - :root { - --top: 2px; - --right: 2px; - --left: 2px; - } - - .my-selector { - margin: var(--top, var(--right)) - var(--right, var(--top, 3px)) - 5px - var(--left, var(--top, var(--not-renamed, 5px))) - } - `, - ` - :root { - --a: 2px; - --b: 2px; - --c: 2px; - } - - .a { - margin: var(--a, var(--b)) - var(--b, var(--a, 3px)) - 5px - var(--c, var(--a, var(--not-renamed, 5px))) - } - `, - ); -}); it('should replace excluded special characters | rename-css-selectors#77', () => { rcs.selectorsLibrary.setExclude('somediv:test-me'); @@ -645,3 +613,34 @@ it('should classes with and ignore them | rcs-core#133', () => { '.a.bottom-\\[99999px\\][class="test"] {bottom: 99999px;}', ); }); + +it('replace multiple variables after each other | #137', () => { + replaceCssMacro( + ` + *, ::before, ::after { + --tw-shadow: 0 0 transparent; + --tw-ring-offset-shadow: 0 0 transparent; + --tw-ring-shadow: 0 0 transparent; + } + + .shadow-small { + --tw-shadow: 0 2px 4px 0 rgb(151, 145, 151, 0.1); + box-shadow: 0 0 transparent, 0 0 transparent, var(--tw-shadow); + box-shadow: var(--tw-ring-offset-shadow, 0 0 transparent), var(--tw-ring-shadow, 0 0 transparent), var(--tw-shadow); + } + `, + ` + *, ::before, ::after { + --a: 0 0 transparent; + --b: 0 0 transparent; + --c: 0 0 transparent; + } + + .a { + --a: 0 2px 4px 0 rgb(151, 145, 151, 0.1); + box-shadow: 0 0 transparent, 0 0 transparent, var(--a); + box-shadow: var(--b, 0 0 transparent), var(--c, 0 0 transparent), var(--a); + } + `, + ); +}); diff --git a/__tests__/selectorsLibrary.ts b/__tests__/selectorsLibrary.ts index 731a060..731adf3 100644 --- a/__tests__/selectorsLibrary.ts +++ b/__tests__/selectorsLibrary.ts @@ -233,7 +233,7 @@ it('getall | should return a regex of compressed with classes', () => { addSelectorType: true, }); - expect(regex.source).toBe('(\\#a)|(\\.a|\\.b)'); + expect(regex.source).toBe('(\\.a|\\.b)|(\\#a)'); }); it('getall | should return an array with selectors', () => { @@ -293,7 +293,7 @@ it('getall | should return a regex of non compressed with classes', () => { addSelectorType: true, }); - expect(regex.source).toBe('(\\#id)|(\\.bottom-\\[9px]|\\.test)'); + expect(regex.source).toBe('(\\.bottom-\\[9px]|\\.test)|(\\#id)'); }); it('getall | should return a regex of non compressed selecotrs', () => { @@ -303,7 +303,7 @@ it('getall | should return a regex of non compressed selecotrs', () => { getRenamedValues: true, }); - expect(regex.source).toBe('((\\s|\\#)(a)[\\s)])|((\\s|\\.)(a|b)[\\s)])'); + expect(regex.source).toBe('((\\s|\\.)(a|b)[\\s)])|((\\s|\\#)(a)[\\s)])'); }); it('getall | should return a regex of compressed selectors', () => { @@ -311,7 +311,7 @@ it('getall | should return a regex of compressed selectors', () => { const regex = rcs.selectorsLibrary.getAllRegex(); - expect(regex.source).toBe('((\\s|\\#)(id)[\\s)])|((\\s|\\.)(bottom-\\[9px]|test)[\\s)])'); + expect(regex.source).toBe('((\\s|\\.)(bottom-\\[9px]|test)[\\s)])|((\\s|\\#)(id)[\\s)])'); }); it('getall | should get all setted classes', () => { diff --git a/lib/helpers/arrayToRegex.ts b/lib/helpers/arrayToRegex.ts new file mode 100644 index 0000000..262be54 --- /dev/null +++ b/lib/helpers/arrayToRegex.ts @@ -0,0 +1,20 @@ +function arrayToRegex( + values: string[], + modifier: (value: string) => string = (value) => value, +): RegExp | null { + if (!values.length) { + return null; + } + + return ( + new RegExp(`(${values + // sort by size + .sort((a, b) => b.length - a.length) + // add -- to ensure to only capture attributes + .map(modifier) + .join(')|(') + })`, 'g') + ); +} + +export default arrayToRegex; diff --git a/lib/replace/css.ts b/lib/replace/css.ts index 380af37..66074c8 100644 --- a/lib/replace/css.ts +++ b/lib/replace/css.ts @@ -1,36 +1,11 @@ import { parse } from 'postcss'; import cssVariablesLibrary from '../cssVariablesLibrary'; +import arrayToRegex from '../helpers/arrayToRegex'; import keyframesLibrary from '../keyframesLibrary'; import selectorsLibrary from '../selectorsLibrary'; import replaceRegex from './regex'; -const extractCssVariables = (value: string): string[] => { - const regexMatches = value.match(new RegExp(replaceRegex.cssVariables)); - - let matches: string[] = []; - - if (regexMatches) { - regexMatches.forEach((matchWithVariables) => { - const cssVariableMatch = new RegExp(replaceRegex.cssVariables).exec(matchWithVariables); - - if (!cssVariableMatch) { - return; - } - - matches = [...matches, cssVariableMatch[1]]; - - if (cssVariableMatch[2]) { - const innerMatches = extractCssVariables(cssVariableMatch[2]); - - matches = [...matches, ...innerMatches]; - } - }); - } - - return [...(new Set(matches))]; -}; - // calls the selectorLibrary.getAttributeSelector internally // String.replace will call this function and // get call selectorLibrary.getAttributeSelector directly @@ -180,12 +155,14 @@ const replaceCss = (css: string | Buffer, opts: ReplaceCssOptions = {}): string * replace css variables var() * * *************************** */ if (node.value.match(replaceRegex.cssVariables)) { - const matches = extractCssVariables(node.value); + const regex = arrayToRegex(Object.keys(cssVariablesLibrary.values), (v) => `--${v}`); - // eslint-disable-next-line no-param-reassign - node.value = node.value.replace(new RegExp(matches.join('|'), 'g'), (match: string) => ( - cssVariablesLibrary.get(match, { source }) - )); + if (regex) { + // eslint-disable-next-line no-param-reassign + node.value = node.value.replace(regex, (match: string) => ( + `--${cssVariablesLibrary.get(match.replace(/^--/, ''), { source })}` + )); + } } /* ******************************************** * diff --git a/lib/selectorsLibrary.ts b/lib/selectorsLibrary.ts index d94da0d..774bad4 100644 --- a/lib/selectorsLibrary.ts +++ b/lib/selectorsLibrary.ts @@ -2,6 +2,7 @@ import { AttributeLibrary } from './attributeLibrary'; import idSelectorLibrary, { IdSelectorLibrary } from './idSelectorLibrary'; import classSelectorLibrary, { ClassSelectorLibrary } from './classSelectorLibrary'; import { BaseLibrary, BaseLibraryOptions } from './baseLibrary'; +import arrayToRegex from './helpers/arrayToRegex'; // Simple aggregate class to avoid duplicating code dealing with any CSS selector. export class SelectorsLibrary extends BaseLibrary { @@ -127,7 +128,9 @@ export class SelectorsLibrary extends BaseLibrary { const ret = this.callOnBoth('getAll', options); - return new RegExp(`(${ret.filter(Boolean).map((x) => x.source).join(')|(')})`, 'g'); + // null assertion to keep the same functionality + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return arrayToRegex(ret.filter(Boolean).map((x) => x.source))!; } get(value: string, opts: BaseLibraryOptions = {}): string {