diff --git a/packages/stylify/src/Compiler/CompilationResult.ts b/packages/stylify/src/Compiler/CompilationResult.ts index 74b7e3f4..f4724c8b 100755 --- a/packages/stylify/src/Compiler/CompilationResult.ts +++ b/packages/stylify/src/Compiler/CompilationResult.ts @@ -177,7 +177,7 @@ export class CompilationResult { const newCssRecord = new CssRecord({ screenId: this.screensList.get(screen), selector: selector, - pseudoClasses: macroMatch.pseudoClasses, + pseudoClasses: macroMatch.pseudoClasses ? [macroMatch.pseudoClasses] : [], utilityShouldBeGenerated }); diff --git a/packages/stylify/src/Compiler/Compiler.ts b/packages/stylify/src/Compiler/Compiler.ts index e6d54ee7..554f5c35 100755 --- a/packages/stylify/src/Compiler/Compiler.ts +++ b/packages/stylify/src/Compiler/Compiler.ts @@ -127,6 +127,14 @@ export class Compiler { private readonly contentOptionsRegExp = /stylify-([a-zA-Z-_0-9]+)\s([\s\S]+?)\s\/stylify-[a-zA-Z-_0-9]+/; + private readonly macrosRegExpGenerators = [ + // Match with media query and without pseudo class + (macroKey: string): RegExp => new RegExp(`(?:([a-zA-Z0-9\\-:&\\|]+):)${macroKey}${this.macroRegExpEndPart}`, 'g'), + // Match without media query and without pseudo class + // () - empty pseudo class and media query match + (macroKey: string): RegExp => new RegExp(`\\b()${macroKey}${this.macroRegExpEndPart}`, 'g') + ]; + private ignoredAreasRegExpString: string = null; public ignoredAreas = [ @@ -722,15 +730,7 @@ export class Compiler { return; } - const regExpGenerators = [ - // Match with media query and without pseudo class - (macroKey: string): RegExp => new RegExp(`\\b(?:([a-zA-Z0-9-:&|]+):)${macroKey}${this.macroRegExpEndPart}`, 'g'), - // Match without media query and without pseudo class - // () - empty pseudo class and media query match - (macroKey: string): RegExp => new RegExp(`\\b()${macroKey}${this.macroRegExpEndPart}`, 'g') - ]; - - for (const regExpGenerator of regExpGenerators) { + for (const regExpGenerator of this.macrosRegExpGenerators) { for (const macroKey in this.macros) { content = content.replace(regExpGenerator(macroKey), (...args) => { const macroMatches: string[] = args.slice(0, args.length - 2); diff --git a/packages/stylify/src/Compiler/MacroMatch.ts b/packages/stylify/src/Compiler/MacroMatch.ts index de0d600c..0b6581c4 100755 --- a/packages/stylify/src/Compiler/MacroMatch.ts +++ b/packages/stylify/src/Compiler/MacroMatch.ts @@ -8,77 +8,102 @@ export class MacroMatch { public static readonly selectorQuoteAlias = '^'; - public fullMatch: string = null; + private readonly logicalOperandsReplacementMap = { + '&&': ' and ', + '||': ', ' + }; + + private readonly logicalOperandsList = Object.keys(this.logicalOperandsReplacementMap); - public screenAndPseudoClassesMatch: string = null; + public fullMatch: string = null; public selector: string = null; public screen = '_'; - public pseudoClasses: string[] = []; + public pseudoClasses: string = null; public captures: string[] = []; constructor(match: string[], screens: ScreensType) { this.fullMatch = match[0].trim(); - this.screenAndPseudoClassesMatch = match[1]?.trim() ?? null; + const screenAndPseudoClassesMatch = match[1]?.trim() ?? null; this.selector = this.fullMatch; - this.pseudoClasses = []; match.splice(0, 2); this.captures = match.filter(matchToFilter => typeof matchToFilter !== 'undefined'); - if (this.screenAndPseudoClassesMatch) { - const screenAndPseudoClassesMatchArray = this.screenAndPseudoClassesMatch.split(':'); - const operators: string[] = []; - const possibleScreenMatchItems = screenAndPseudoClassesMatchArray[0] - .replace(/&&/ig, () => { - operators.push(' and '); - return '__OPERATOR__'; - }) - .replace(/\|\|/ig, () => { - operators.push(', '); - return '__OPERATOR__'; - }) - .split('__OPERATOR__'); - - let screenMatched = false; - let possibleScreenMatchString = ''; - - possibleScreenMatchItems.forEach((possibleScreenMatch) => { - for (const key in screens) { - const screenRegExp = new RegExp(`\\b${key}$`, 'g'); - const screenMatches = screenRegExp.exec(possibleScreenMatch); - - if (screenMatches === null) { - continue; - } - - let screenData = screens[key]; - - if (typeof screenData === 'function') { - screenData = screenData(screenMatches[0]); - } - - possibleScreenMatch = possibleScreenMatch.replace(screenRegExp, screenData); - screenMatched = true; + if (!screenAndPseudoClassesMatch) { + return; + } + + const screensAndPseudoClassesParts = []; + const screensAndPseudoClassesTokens = screenAndPseudoClassesMatch.split(''); + const tokensLength = screensAndPseudoClassesTokens.length; + + let tokenQueue = ''; + let screenMatched = false; + let pseudoClassesPart = screenAndPseudoClassesMatch; + + for (let i = 0; i <= tokensLength; i ++) { + const token = screensAndPseudoClassesTokens[i]; + const previousToken = screensAndPseudoClassesTokens[i - 1] ?? ''; + const nextToken = screensAndPseudoClassesTokens[i + 1] ?? ''; + + const nextSequence = token + nextToken; + const nextSequenceIsLogicalSeparator = this.logicalOperandsList.includes(nextSequence); + const nextSequenceIsColonSeparator = token === '\\' && nextToken !== ':'; + const isLastToken = i === tokensLength; + + if (!(nextSequenceIsColonSeparator + || nextSequenceIsLogicalSeparator + || token === ':' && previousToken !== '\\' + || isLastToken + )) { + tokenQueue += token; + continue; + } + + for (const key in screens) { + const screenRegExp = new RegExp(`^${key}$`, 'g'); + const screenMatches = screenRegExp.exec(tokenQueue); + + if (screenMatches === null) { + continue; } - const operator = operators[0] ?? ''; + let screenData = screens[key]; - if (operator) { - operators.shift(); + if (typeof screenData === 'function') { + screenData = screenData(screenMatches[0]); } - possibleScreenMatchString += `${possibleScreenMatch}${operator}`; - }); + if (screenData) { + pseudoClassesPart = pseudoClassesPart.substring(screenMatches[0].length); + screensAndPseudoClassesParts.push(screenData); + screenMatched = true; + break; + } + } + + tokenQueue += token; - if (screenMatched) { - this.screen = possibleScreenMatchString; - screenAndPseudoClassesMatchArray.shift(); + if (nextSequenceIsLogicalSeparator) { + pseudoClassesPart = pseudoClassesPart.substring(2); + screensAndPseudoClassesParts.push(this.logicalOperandsReplacementMap[nextSequence]); + i ++; + tokenQueue = ''; + continue; } + } + + if (screenMatched) { + this.screen = screensAndPseudoClassesParts.join(''); + } + + const pseudoClasses = pseudoClassesPart.replace(/^:/, ''); - this.pseudoClasses = screenAndPseudoClassesMatchArray; + if (pseudoClasses.trim().length) { + this.pseudoClasses = pseudoClasses; } }