Skip to content

Commit 3efdd20

Browse files
committed
Group syntax entities by combinator precedence
1 parent 8c7d16e commit 3efdd20

5 files changed

Lines changed: 211 additions & 148 deletions

File tree

index.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3554,7 +3554,7 @@ type BorderImageOutsetProperty<TLength> = Globals | TLength | string | number;
35543554

35553555
type BorderImageRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;
35563556

3557-
type BorderImageSliceProperty = Globals | "fill" | string | number;
3557+
type BorderImageSliceProperty = Globals | string | number;
35583558

35593559
type BorderImageSourceProperty = Globals | "none" | string;
35603560

@@ -3894,7 +3894,7 @@ type MaskBorderOutsetProperty<TLength> = Globals | TLength | string | number;
38943894

38953895
type MaskBorderRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;
38963896

3897-
type MaskBorderSliceProperty = Globals | "fill" | string | number;
3897+
type MaskBorderSliceProperty = Globals | string | number;
38983898

38993899
type MaskBorderSourceProperty = Globals | "none" | string;
39003900

@@ -4184,7 +4184,7 @@ type BorderBottomProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | Colo
41844184

41854185
type BorderColorProperty = Globals | Color | string;
41864186

4187-
type BorderImageProperty = Globals | "fill" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
4187+
type BorderImageProperty = Globals | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
41884188

41894189
type BorderInlineEndProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | NamedColor | DeprecatedSystemColor | "currentcolor" | string;
41904190

@@ -4232,7 +4232,7 @@ type MarginProperty<TLength> = Globals | TLength | "auto" | string;
42324232

42334233
type MaskProperty<TLength> = Globals | MaskLayer<TLength> | string;
42344234

4235-
type MaskBorderProperty = Globals | "alpha" | "fill" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
4235+
type MaskBorderProperty = Globals | "alpha" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
42364236

42374237
type OffsetProperty<TLength> = Globals | Position<TLength> | GeometryBox | "auto" | "none" | string;
42384238

index.js.flow

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3310,7 +3310,7 @@ type BorderImageOutsetProperty<TLength> = Globals | TLength | string | number;
33103310

33113311
type BorderImageRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;
33123312

3313-
type BorderImageSliceProperty = Globals | "fill" | string | number;
3313+
type BorderImageSliceProperty = Globals | string | number;
33143314

33153315
type BorderImageSourceProperty = Globals | "none" | string;
33163316

@@ -3650,7 +3650,7 @@ type MaskBorderOutsetProperty<TLength> = Globals | TLength | string | number;
36503650

36513651
type MaskBorderRepeatProperty = Globals | "repeat" | "round" | "space" | "stretch" | string;
36523652

3653-
type MaskBorderSliceProperty = Globals | "fill" | string | number;
3653+
type MaskBorderSliceProperty = Globals | string | number;
36543654

36553655
type MaskBorderSourceProperty = Globals | "none" | string;
36563656

@@ -3940,7 +3940,7 @@ type BorderBottomProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | Colo
39403940

39413941
type BorderColorProperty = Globals | Color | string;
39423942

3943-
type BorderImageProperty = Globals | "fill" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
3943+
type BorderImageProperty = Globals | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
39443944

39453945
type BorderInlineEndProperty<TLength> = Globals | BrWidth<TLength> | BrStyle | NamedColor | DeprecatedSystemColor | "currentcolor" | string;
39463946

@@ -3988,7 +3988,7 @@ type MarginProperty<TLength> = Globals | TLength | "auto" | string;
39883988

39893989
type MaskProperty<TLength> = Globals | MaskLayer<TLength> | string;
39903990

3991-
type MaskBorderProperty = Globals | "alpha" | "fill" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
3991+
type MaskBorderProperty = Globals | "alpha" | "luminance" | "none" | "repeat" | "round" | "space" | "stretch" | string | number;
39923992

39933993
type OffsetProperty<TLength> = Globals | Position<TLength> | GeometryBox | "auto" | "none" | string;
39943994

src/compat.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Combinator, combinatorData, Component, componentData, componentGroupData, Entity, EntityType } from './parser';
1+
import { Combinator, combinators, Component, componentData, componentGroupData, Entity, EntityType } from './parser';
22

33
const importsCache: { [cssPath: string]: MDN.PropertiesCompat | null } = {};
44

@@ -108,7 +108,7 @@ export function compatSyntax(data: MDN.CompatData, entities: EntityType[]): Enti
108108
const alternativeEntities: EntityType[] = [entity];
109109

110110
for (const keyword of alternatives) {
111-
alternativeEntities.push(combinatorData(Combinator.SingleBar), componentData(Component.Keyword, keyword));
111+
alternativeEntities.push(combinators[Combinator.SingleBar], componentData(Component.Keyword, keyword));
112112
}
113113

114114
compatEntities.push(componentGroupData(alternativeEntities));

src/parser.ts

Lines changed: 135 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ export enum Component {
1111
Group,
1212
}
1313

14+
// Higher number is higher precedence
1415
export enum Combinator {
1516
/** Components are mandatory and should appear in that order */
16-
Juxtaposition,
17+
Juxtaposition = 0,
1718
/** Components are mandatory but may appear in any order */
18-
DoubleAmpersand,
19+
DoubleAmpersand = 1,
1920
/** At least one of the components must be present, and they may appear in any order */
20-
DoubleBar,
21+
DoubleBar = 2,
2122
/** Exactly one of the components must be present */
22-
SingleBar,
23+
SingleBar = 3,
2324
}
2425

2526
export enum Multiplier {
@@ -72,7 +73,6 @@ export type ComponentType = INonGroupData | IGroupData;
7273

7374
export interface ICombinator {
7475
entity: Entity.Combinator;
75-
multiplier: MultiplierType | null;
7676
combinator: Combinator;
7777
}
7878

@@ -81,7 +81,7 @@ export interface IFunction {
8181
multiplier: MultiplierType | null;
8282
}
8383

84-
interface IUnknown {
84+
export interface IUnknown {
8585
entity: Entity.Unknown;
8686
multiplier: MultiplierType | null;
8787
}
@@ -92,10 +92,29 @@ const REGEX_ENTITY = /(?:^|\s)((?:[\w]+\([^\)]*\))|[^\s*+?#!{]+)([*+?#!]|{(\d+),
9292
const REGEX_DATA_TYPE = /^(<[^>]+>)/g;
9393
const REGEX_KEYWORD = /^([\w-]+)/g;
9494

95+
export const combinators: { [key: number]: ICombinator } = {
96+
[Combinator.Juxtaposition]: {
97+
entity: Entity.Combinator,
98+
combinator: Combinator.Juxtaposition,
99+
},
100+
[Combinator.DoubleAmpersand]: {
101+
entity: Entity.Combinator,
102+
combinator: Combinator.DoubleAmpersand,
103+
},
104+
[Combinator.DoubleBar]: {
105+
entity: Entity.Combinator,
106+
combinator: Combinator.DoubleBar,
107+
},
108+
[Combinator.SingleBar]: {
109+
entity: Entity.Combinator,
110+
combinator: Combinator.SingleBar,
111+
},
112+
};
113+
95114
export default function parse(syntax: string): EntityType[] {
96115
const levels: EntityType[][] = [[]];
97-
const deepestLevel = () => levels[levels.length - 1];
98116
let previousMatchWasComponent = false;
117+
99118
let entityMatch: RegExpExecArray | null;
100119
while ((entityMatch = REGEX_ENTITY.exec(syntax))) {
101120
const [, value, ...rawMultiplier] = entityMatch;
@@ -104,27 +123,27 @@ export default function parse(syntax: string): EntityType[] {
104123
previousMatchWasComponent = false;
105124
continue;
106125
} else if (value.indexOf('&&') === 0) {
107-
deepestLevel().push(combinatorData(Combinator.DoubleAmpersand, multiplierData(rawMultiplier)));
126+
deepestLevel().push(combinators[Combinator.DoubleAmpersand]);
108127
previousMatchWasComponent = false;
109128
continue;
110129
} else if (value.indexOf('||') === 0) {
111-
deepestLevel().push(combinatorData(Combinator.DoubleBar, multiplierData(rawMultiplier)));
130+
deepestLevel().push(combinators[Combinator.DoubleBar]);
112131
previousMatchWasComponent = false;
113132
continue;
114133
} else if (value.indexOf('|') === 0) {
115-
deepestLevel().push(combinatorData(Combinator.SingleBar, multiplierData(rawMultiplier)));
134+
deepestLevel().push(combinators[Combinator.SingleBar]);
116135
previousMatchWasComponent = false;
117136
continue;
118137
} else if (value.indexOf(']') === 0) {
119138
const definitions = levels.pop();
120139
if (definitions) {
121-
deepestLevel().push(componentGroupData(definitions, multiplierData(rawMultiplier)));
140+
deepestLevel().push(componentGroupData(groupByPrecedence(definitions), multiplierData(rawMultiplier)));
122141
}
123142
previousMatchWasComponent = true;
124143
continue;
125144
} else {
126-
if (previousMatchWasComponent === true) {
127-
deepestLevel().push(combinatorData(Combinator.Juxtaposition));
145+
if (previousMatchWasComponent) {
146+
deepestLevel().push(combinators[Combinator.Juxtaposition]);
128147
}
129148

130149
if (value.indexOf('[') === 0) {
@@ -149,15 +168,55 @@ export default function parse(syntax: string): EntityType[] {
149168
deepestLevel().push({ entity: Entity.Unknown, multiplier: multiplierData(rawMultiplier) });
150169
}
151170

152-
return levels[0];
171+
function deepestLevel() {
172+
return levels[levels.length - 1];
173+
}
174+
175+
return groupByPrecedence(levels[0]);
153176
}
154177

155-
export function combinatorData(combinator: Combinator, multiplier: MultiplierType | null = null): ICombinator {
156-
return {
157-
entity: Entity.Combinator,
158-
combinator,
159-
multiplier,
160-
};
178+
export function isComponent(entity: EntityType): entity is ComponentType {
179+
return entity.entity === Entity.Component;
180+
}
181+
182+
export function isCombinator(entity: EntityType): entity is ICombinator {
183+
return entity.entity === Entity.Combinator;
184+
}
185+
186+
export function isCurlyBracetMultiplier(multiplier: MultiplierType): multiplier is IMultiplierCurlyBracet {
187+
return multiplier.sign === Multiplier.CurlyBracet;
188+
}
189+
190+
export function isMandatoryMultiplied(multiplier: MultiplierType | null) {
191+
return multiplier !== null && (isCurlyBracetMultiplier(multiplier) && multiplier.min > 1);
192+
}
193+
194+
export function isOptionallyMultiplied(multiplier: MultiplierType | null) {
195+
return (
196+
multiplier !== null &&
197+
((isCurlyBracetMultiplier(multiplier) && multiplier.min < multiplier.max && multiplier.max > 1) ||
198+
multiplier.sign === Multiplier.Asterisk ||
199+
multiplier.sign === Multiplier.PlusSign ||
200+
multiplier.sign === Multiplier.HashMark ||
201+
multiplier.sign === Multiplier.ExclamationPoint)
202+
);
203+
}
204+
205+
export function isMandatoryEntity(entity: EntityType) {
206+
if (isCombinator(entity)) {
207+
return entity === combinators[Combinator.DoubleAmpersand] || entity === combinators[Combinator.Juxtaposition];
208+
}
209+
210+
if (entity.multiplier) {
211+
return (
212+
(isCurlyBracetMultiplier(entity.multiplier) && entity.multiplier.min > 0) ||
213+
entity.multiplier.sign === Multiplier.PlusSign ||
214+
entity.multiplier.sign === Multiplier.HashMark ||
215+
entity.multiplier.sign === Multiplier.ExclamationPoint
216+
);
217+
}
218+
219+
return true;
161220
}
162221

163222
export function componentData(
@@ -198,8 +257,63 @@ function multiplierData(raw: string[]): MultiplierType | null {
198257
case '!':
199258
return { sign: Multiplier.ExclamationPoint };
200259
case '{':
201-
return { sign: Multiplier.CurlyBracet, min: +raw[1], max: +raw[2] };
260+
return { sign: Multiplier.CurlyBracet, min: Number(raw[1]), max: Number(raw[2]) };
202261
default:
203262
return null;
204263
}
205264
}
265+
266+
function groupByPrecedence(entities: EntityType[], precedence: number = Combinator.SingleBar): EntityType[] {
267+
if (precedence < 0) {
268+
// We've reached the lowest precedence possible
269+
return entities;
270+
}
271+
272+
const combinator = combinators[precedence];
273+
const combinatorIndexes: number[] = [];
274+
275+
// Search for indexes where the combinator is used
276+
for (let i = entities.indexOf(combinator); i > -1; i = entities.indexOf(combinator, i + 1)) {
277+
combinatorIndexes.push(i);
278+
}
279+
280+
const nextPrecedence = precedence - 1;
281+
282+
if (combinatorIndexes.length === 0) {
283+
return groupByPrecedence(entities, nextPrecedence);
284+
}
285+
286+
const groupedEntities: EntityType[] = [];
287+
288+
// Yes, what you see is correct: it's index of indexes
289+
for (
290+
let i = 0;
291+
// Add one loop to finnish up the last entities
292+
i < combinatorIndexes.length + 1;
293+
i++
294+
) {
295+
const sectionEntities = entities.slice(
296+
i > 0
297+
? combinatorIndexes[i - 1] + 1
298+
: // Slice from beginning
299+
0,
300+
i < combinatorIndexes.length
301+
? combinatorIndexes[i]
302+
: // Slice to end
303+
entities.length,
304+
);
305+
306+
// Only group if there's more than one entity in between
307+
if (sectionEntities.length > 1) {
308+
groupedEntities.push(componentGroupData(groupByPrecedence(sectionEntities, nextPrecedence)));
309+
} else {
310+
groupedEntities.push(...sectionEntities);
311+
}
312+
313+
if (i < combinatorIndexes.length) {
314+
groupedEntities.push(entities[combinatorIndexes[i]]);
315+
}
316+
}
317+
318+
return groupedEntities;
319+
}

0 commit comments

Comments
 (0)