Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pseudo classes tokenization #187

Merged
merged 1 commit into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/stylify/src/Compiler/CompilationResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});

Expand Down
18 changes: 9 additions & 9 deletions packages/stylify/src/Compiler/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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);
Expand Down
121 changes: 73 additions & 48 deletions packages/stylify/src/Compiler/MacroMatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down