Skip to content

Commit b953602

Browse files
committed
fix: Update global states to use simplified parser utils.
1 parent 1e48882 commit b953602

File tree

15 files changed

+198
-87
lines changed

15 files changed

+198
-87
lines changed

packages/@css-blocks/core/src/Analyzer/validations/property-conflict-validator.ts

-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ function evaluate(obj: Style, propToRules: PropMap, conflicts: ConflictMap) {
5555
// If these styles are from the same block, abort!
5656
if (other.style.block === self.style.block) { continue; }
5757

58-
5958
// Get the declarations for this specific property.
6059
let selfDecl = self.declarations.get(prop);
6160
let otherDecl = other.declarations.get(prop);

packages/@css-blocks/core/src/BlockCompiler/ConflictResolver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { assertNever } from "@opticss/util";
22
import { CompoundSelector, ParsedSelector, parseSelector, postcss, postcssSelectorParser as selectorParser } from "opticss";
33

4-
import { isRootNode, isClassNode, isAttributeNode, toAttrToken } from "../BlockParser";
4+
import { isAttributeNode, isClassNode, isRootNode, toAttrToken } from "../BlockParser";
55
import { RESOLVE_RE } from "../BlockSyntax";
66
import { Block, BlockClass, Style } from "../BlockTree";
77
import { ResolvedConfiguration } from "../configuration";

packages/@css-blocks/core/src/BlockParser/block-intermediates.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { CompoundSelector, postcssSelectorParser as selectorParser } from "optic
44
import { ATTR_PRESENT, AttrToken, ROOT_CLASS, STATE_NAMESPACE } from "../BlockSyntax";
55

66
export enum BlockType {
7-
root = 1,
7+
block = 1,
8+
root,
89
attribute,
910
class,
1011
classAttribute,
@@ -16,6 +17,9 @@ export type NodeAndType = {
1617
} | {
1718
blockType: BlockType.root | BlockType.class;
1819
node: selectorParser.ClassName | selectorParser.Pseudo;
20+
} | {
21+
blockType: BlockType.block;
22+
node: selectorParser.Tag;
1923
};
2024

2125
export type BlockNodeAndType = NodeAndType & {
@@ -34,7 +38,6 @@ function attrValue(attr: selectorParser.Attribute): string {
3438
/** Extract an Attribute's name from an attribute selector */
3539
export function toAttrToken(attr: selectorParser.Attribute): AttrToken {
3640
return {
37-
type: "attribute",
3841
namespace: attr.namespaceString,
3942
name: attr.attribute,
4043
value: attrValue(attr),
@@ -51,6 +54,7 @@ export function toAttrToken(attr: selectorParser.Attribute): AttrToken {
5154
export function blockTypeName(t: BlockType, options?: { plural: boolean }): string {
5255
let isPlural = options && options.plural;
5356
switch (t) {
57+
case BlockType.block: return isPlural ? "external blocks" : "external block";
5458
case BlockType.root: return isPlural ? "block roots" : "block root";
5559
case BlockType.attribute: return isPlural ? "root-level states" : "root-level state";
5660
case BlockType.class: return isPlural ? "classes" : "class";
@@ -59,10 +63,18 @@ export function blockTypeName(t: BlockType, options?: { plural: boolean }): stri
5963
}
6064
}
6165

66+
/**
67+
* Test if the provided node representation is an external block.
68+
* @param object The NodeAndType's descriptor object.
69+
*/
70+
export function isExternalBlock(object: NodeAndType): boolean {
71+
return object.blockType === BlockType.block;
72+
}
73+
6274
/**
6375
* Test if the provided node representation is a root level object, aka: operating
6476
* on the root element.
65-
* @param object The CompoundSelector's descriptor object.
77+
* @param object The NodeAndType's descriptor object.
6678
*/
6779
export function isRootLevelObject(object: NodeAndType): boolean {
6880
return object.blockType === BlockType.root || object.blockType === BlockType.attribute;

packages/@css-blocks/core/src/BlockParser/features/assert-foreign-global-attribute.ts

+36-35
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import { postcss } from "opticss";
1+
import { postcss, postcssSelectorParser as selectorParser } from "opticss";
22

33
import { Block } from "../../BlockTree";
44
import * as errors from "../../errors";
55
import { selectorSourceLocation as loc } from "../../SourceLocation";
6-
import {
7-
BlockType,
8-
getBlockNode,
9-
toAttrToken,
10-
} from "../block-intermediates";
6+
import { isAttributeNode, toAttrToken } from "../block-intermediates";
117

128
/**
139
* Verify that the external block referenced by a `rule` selects an Attribute that
@@ -24,42 +20,47 @@ export async function assertForeignGlobalAttribute(root: postcss.Root, block: Bl
2420

2521
parsedSelectors.forEach((iSel) => {
2622

27-
iSel.eachCompoundSelector((compoundSel) => {
28-
29-
let obj = getBlockNode(compoundSel);
23+
iSel.eachCompoundSelector((sel) => {
3024

3125
// Only test rules that are block references (this is validated in parse-styles and shouldn't happen).
3226
// If node isn't selecting a block, move on
33-
if (!obj || !obj.blockName) { return; }
27+
let blockName = sel.nodes.find(n => n.type === selectorParser.TAG);
28+
if (!blockName || !blockName.value) { return; }
3429

35-
// If selecting something other than an attribute on external block, throw.
36-
if (obj.blockType !== BlockType.attribute) {
37-
throw new errors.InvalidBlockSyntax(
38-
`Only global states from other blocks can be used in selectors: ${rule.selector}`,
39-
loc(file, rule, obj.node));
40-
}
30+
for (let node of sel.nodes) {
4131

42-
// If referenced block does not exist, throw.
43-
let otherBlock = block.getReferencedBlock(obj.blockName!);
44-
if (!otherBlock) {
45-
throw new errors.InvalidBlockSyntax(
46-
`No block named ${obj.blockName} found: ${rule.selector}`,
47-
loc(file, rule, obj.node));
48-
}
32+
if (node.type === selectorParser.TAG) { continue; }
4933

50-
// If state referenced does not exist on external block, throw
51-
let otherAttr = otherBlock.rootClass.getAttributeValue(toAttrToken(obj.node));
52-
if (!otherAttr) {
53-
throw new errors.InvalidBlockSyntax(
54-
`No state ${obj.node.toString()} found in : ${rule.selector}`,
55-
loc(file, rule, obj.node));
56-
}
34+
// If selecting something other than an attribute on external block, throw.
35+
if (!isAttributeNode(node)) {
36+
throw new errors.InvalidBlockSyntax(
37+
`Only global states from other blocks can be used in selectors: ${rule.selector}`,
38+
loc(file, rule, node));
39+
}
40+
41+
// If referenced block does not exist, throw.
42+
let otherBlock = block.getReferencedBlock(blockName.value);
43+
if (!otherBlock) {
44+
throw new errors.InvalidBlockSyntax(
45+
`No block named ${blockName.value} found: ${rule.selector}`,
46+
loc(file, rule, node));
47+
}
48+
49+
// If state referenced does not exist on external block, throw
50+
let otherAttr = otherBlock.rootClass.getAttributeValue(toAttrToken(node));
51+
if (!otherAttr) {
52+
throw new errors.InvalidBlockSyntax(
53+
`No state ${node.toString()} found in : ${rule.selector}`,
54+
loc(file, rule, node));
55+
}
56+
57+
// If external state is not set as global, throw.
58+
if (!otherAttr.isGlobal) {
59+
throw new errors.InvalidBlockSyntax(
60+
`${node.toString()} is not global: ${rule.selector}`,
61+
loc(file, rule, node));
62+
}
5763

58-
// If external state is not set as global, throw.
59-
if (!otherAttr.isGlobal) {
60-
throw new errors.InvalidBlockSyntax(
61-
`${obj.node.toString()} is not global: ${rule.selector}`,
62-
loc(file, rule, obj.node));
6364
}
6465
});
6566
});

packages/@css-blocks/core/src/BlockParser/features/construct-block.ts

+59-23
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
isAttributeNode,
1212
isClassLevelObject,
1313
isClassNode,
14+
isExternalBlock,
1415
isRootLevelObject,
1516
isRootNode,
1617
toAttrToken,
@@ -61,41 +62,50 @@ export async function constructBlock(root: postcss.Root, block: Block, file: str
6162
parsedSelectors.forEach((iSel) => {
6263

6364
let keySel = iSel.key;
64-
let currentCompoundSel: CompoundSelector | undefined = iSel.selector;
65+
let sel: CompoundSelector | undefined = iSel.selector;
6566

6667
// Assert this selector is well formed according to CSS Blocks' selector rules.
6768
assertValidSelector(block, rule, iSel, file);
6869

6970
// For each `CompoundSelector` in this rule, configure the `Block` object
7071
// depending on the BlockType.
71-
while (currentCompoundSel) {
72-
73-
let isKey = (keySel === currentCompoundSel);
72+
while (sel) {
73+
74+
let isKey = (keySel === sel);
7475
let blockClass: BlockClass | undefined = undefined;
7576
let foundStyles: Style[] = [];
7677

77-
for (let node of currentCompoundSel.nodes) {
78-
if (isRootNode(node)){
78+
// If this is an external Style, move on. These are validated
79+
// in `assert-foreign-global-attribute`.
80+
let blockName = sel.nodes.find(n => n.type === selectorParser.TAG);
81+
if (blockName) {
82+
sel = sel.next && sel.next.selector;
83+
continue;
84+
}
85+
86+
for (let node of sel.nodes) {
87+
if (isRootNode(node)) {
7988
blockClass = block.rootClass;
8089
}
81-
else if (isClassNode(node)){
90+
else if (isClassNode(node)) {
8291
blockClass = block.ensureClass(node.value);
8392
}
84-
else if (isAttributeNode(node)){
85-
// The fact that a base class exists for all state selectors is validated elsewhere
93+
else if (isAttributeNode(node)) {
94+
// The fact that a base class exists for all state selectors is
95+
// validated in `assertBlockObject`.
8696
foundStyles.push(blockClass!.ensureAttributeValue(toAttrToken(node)));
8797
}
88-
8998
}
90-
// If we haven't found any terminating states, we're targeting the discovered Block class.
91-
if (blockClass && !foundStyles.length) { foundStyles.push(blockClass); }
9299

93-
// If this is the key selector, save this ruleset on the created style.
94-
if (isKey) {
95-
foundStyles.map(s => styleRuleTuples.add([s, rule]));
96-
}
100+
// If we haven't found any terminating states, we're targeting the discovered Block class.
101+
if (blockClass && !foundStyles.length) { foundStyles.push(blockClass); }
102+
103+
// If this is the key selector, save this ruleset on the created style.
104+
if (isKey) {
105+
foundStyles.map(s => styleRuleTuples.add([s, rule]));
106+
}
97107

98-
currentCompoundSel = currentCompoundSel.next && currentCompoundSel.next.selector;
108+
sel = sel.next && sel.next.selector;
99109
}
100110
});
101111
});
@@ -268,6 +278,23 @@ function assertBlockObject(block: Block, sel: CompoundSelector, rule: postcss.Ru
268278
// Test each node in selector
269279
let result = sel.nodes.reduce<NodeAndType | null>(
270280
(found, n) => {
281+
282+
// If this is an external Block reference, indicate we have encountered it.
283+
// If this is not the first BlockType encountered, throw the appropriate error.
284+
if (n.type === selectorParser.TAG) {
285+
if (found === null) {
286+
found = {
287+
blockType: BlockType.block,
288+
node: n,
289+
};
290+
} else {
291+
throw new errors.InvalidBlockSyntax(
292+
`External Block ${n} must be the first selector in "${rule.selector}"`,
293+
loc(file, rule, sel.nodes[0]),
294+
);
295+
}
296+
}
297+
271298
// If selecting the root element, indicate we have encountered it. If this
272299
// is not the first BlockType encountered, throw the appropriate error
273300
if (isRootNode(n)) {
@@ -282,11 +309,6 @@ function assertBlockObject(block: Block, sel: CompoundSelector, rule: postcss.Ru
282309
`${n} cannot be on the same element as ${found.node}: ${rule.selector}`,
283310
loc(file, rule, sel.nodes[0]),
284311
);
285-
} else if (found.blockType === BlockType.attribute) {
286-
throw new errors.InvalidBlockSyntax(
287-
`It's redundant to specify a state with the an explicit .root: ${rule.selector}`,
288-
loc(file, rule, n),
289-
);
290312
}
291313
}
292314
}
@@ -308,7 +330,7 @@ function assertBlockObject(block: Block, sel: CompoundSelector, rule: postcss.Ru
308330
);
309331
} else if (found.blockType === BlockType.class || found.blockType === BlockType.classAttribute) {
310332
found = { node: n, blockType: BlockType.classAttribute };
311-
} else if (found.blockType === BlockType.root || found.blockType === BlockType.attribute) {
333+
} else if (found.blockType === BlockType.block || found.blockType === BlockType.root || found.blockType === BlockType.attribute) {
312334
found = { node: n, blockType: BlockType.attribute };
313335
}
314336
}
@@ -351,6 +373,20 @@ function assertBlockObject(block: Block, sel: CompoundSelector, rule: postcss.Ru
351373
loc(file, rule, sel.nodes[0]));
352374
}
353375

376+
if (isExternalBlock(result)) {
377+
let external = block.getReferencedBlock(result.node.value!);
378+
if (!external) { throw new errors.InvalidBlockSyntax(``, loc(file, rule, sel.nodes[0])); }
379+
let globalStates = external.rootClass.allAttributeValues().filter((a) => a.isGlobal);
380+
if (!globalStates.length) {
381+
throw new errors.InvalidBlockSyntax(
382+
`External Block '${result.node.value}' has no global states.`,
383+
loc(file, rule, sel.nodes[0]));
384+
}
385+
throw new errors.InvalidBlockSyntax(
386+
`Missing global state selector on external Block '${result.node.value}'. Did you mean one of: ${globalStates.map((s) => s.asSource()).join(" ")}`,
387+
loc(file, rule, sel.nodes[0]));
388+
}
389+
354390
// Otherwise, return the block, type and associated node.
355391
else {
356392
return {

packages/@css-blocks/core/src/BlockParser/index.ts

-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
// These constructs are **only** used in "../Block/Block.ts"!
22
// Can we make these internal to the package?
33
export {
4-
BlockType,
5-
NodeAndType,
64
isAttributeNode,
75
isClassNode,
86
isRootNode,
9-
getBlockNode,
107
toAttrToken,
118
} from "./block-intermediates";
129

packages/@css-blocks/core/src/BlockSyntax/BlockPath.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BlockPathError, ErrorLocation } from "../errors";
22

33
import {
44
ATTR_PRESENT,
5-
CLASS_NAME_IDENT as CSS_IDENT,
5+
CLASS_NAME_IDENT,
66
ROOT_CLASS,
77
STATE_NAMESPACE,
88
} from "./BlockSyntax";
@@ -17,24 +17,26 @@ interface ClassToken {
1717
name: string;
1818
}
1919

20-
export interface IAttrToken {
20+
interface IAttrToken {
2121
namespace?: string;
22+
value?: string;
2223
name: string;
23-
value: string;
24+
quoted?: boolean;
2425
}
2526

26-
export interface AttrToken extends IAttrToken {
27+
interface AttrToken extends IAttrToken {
2728
type: "attribute";
28-
quoted: boolean;
2929
}
3030

3131
type Token = BlockToken | ClassToken | AttrToken;
3232

33+
export { IAttrToken as AttrToken };
34+
3335
const isBlock = (token?: Partial<Token>): token is BlockToken => !!token && token.type === "block";
3436
const isClass = (token?: Partial<Token>): token is ClassToken => !!token && token.type === "class";
3537
const isAttribute = (token?: Partial<Token>): token is AttrToken => !!token && token.type === "attribute";
3638
const isQuoted = (token?: Partial<AttrToken>): boolean => !!token && !!token.quoted;
37-
const isIdent = (ident?: string): boolean => !ident || CSS_IDENT.test(ident);
39+
const isIdent = (ident?: string): boolean => !ident || CLASS_NAME_IDENT.test(ident);
3840
const hasName = (token?: Partial<Token>): boolean => !!token && !!token.name;
3941
const isValidNamespace = (token?: Partial<AttrToken>): boolean => !!token && (token.namespace === undefined || token.namespace === STATE_NAMESPACE);
4042

@@ -343,7 +345,7 @@ export class BlockPath {
343345
/**
344346
* Get the parsed attribute name of this Block Path and return the `AttrInfo`
345347
*/
346-
get attribute(): AttrToken | undefined {
348+
get attribute(): IAttrToken | undefined {
347349
if (!this._attribute) return;
348350
return this._attribute;
349351
}

packages/@css-blocks/core/src/BlockTree/Attribute.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "@opticss/element-analysis";
99
import { ObjectDictionary, assertNever } from "@opticss/util";
1010

11-
import { ATTR_PRESENT, IAttrToken as AttrToken } from "../BlockSyntax";
11+
import { ATTR_PRESENT, AttrToken } from "../BlockSyntax";
1212
import { OutputMode, ResolvedConfiguration } from "../configuration";
1313

1414
import { AttrValue } from "./AttrValue";

packages/@css-blocks/core/src/BlockTree/BlockClass.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Attribute as Attr, AttributeValue, attrValues } from "@opticss/element-
22
import { assertNever } from "@opticss/util";
33
import { isString } from "util";
44

5-
import { ATTR_PRESENT, IAttrToken as AttrToken, ROOT_CLASS } from "../BlockSyntax";
5+
import { ATTR_PRESENT, AttrToken, ROOT_CLASS } from "../BlockSyntax";
66
import { BlockPath } from "../BlockSyntax";
77
import { OutputMode, ResolvedConfiguration } from "../configuration";
88

0 commit comments

Comments
 (0)