Skip to content

Commit 717a205

Browse files
author
Timothy Lindvall
committed
fix: Updates per CR feedback.
- Better logic for merging CSS decls onto CSS Block nodes. - discoverGuid doesn't need to be async. - Don't check if each rule in the blockdef file has a block-class decl. Instead, validate that each style node has a preset class name after the block has been processed. - Add new prop names to BlockSyntax. - Rename some methods and properties. - Allow spaces preceeding @block-syntax-version. - Add fixtures and test coverage for blockdef processing. Ensure that the resulting block reflects the decls on a given style node for property conflict resolution.
1 parent cdb09a3 commit 717a205

File tree

22 files changed

+481
-107
lines changed

22 files changed

+481
-107
lines changed

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

+29-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ObjectDictionary, isString } from "@opticss/util";
22
import * as debugGenerator from "debug";
3-
import { LegacyRawSourceMap, adaptFromLegacySourceMap, postcss } from "opticss";
3+
import { LegacyRawSourceMap, adaptFromLegacySourceMap, parseSelector, postcss } from "opticss";
44
import * as path from "path";
55
import { RawSourceMap } from "source-map";
66

@@ -9,7 +9,6 @@ import { Options, ResolvedConfiguration, resolveConfiguration } from "../configu
99
import { CssBlockError } from "../errors";
1010
import { FileIdentifier, ImportedCompiledCssFile, ImportedFile, Importer } from "../importing";
1111
import { upgradeDefinitionFileSyntax } from "../PrecompiledDefinitions/block-syntax-version";
12-
import { sourceRange } from "../SourceLocation";
1312
import { PromiseQueue } from "../util/PromiseQueue";
1413

1514
import { BlockParser, ParsedSource } from "./BlockParser";
@@ -365,29 +364,39 @@ export class BlockFactory {
365364
const block = await this.parser.parseDefinitionSource(definitionAst, file.definitionIdentifier, file.blockId, file.defaultName);
366365

367366
// Merge the rules from the CSS contents into the Block.
368-
const styleNodesMap = block.compiledClassesMap(true);
367+
const styleNodesMap = block.presetClassesMap(true);
369368
cssContentsAst.walkRules(rule => {
370-
rule.selectors.forEach(sel => {
371-
if (sel.split(".").length !== 1 || !sel.startsWith(".")) {
372-
// Skip it, we only care about selectors with only one class.
373-
return;
374-
}
375-
const styleNode = styleNodesMap[sel];
376-
if (!styleNode) {
377-
block.addError(
378-
new CssBlockError(
379-
`Selector ${sel} exists in Compiled CSS file but doesn't match any rules in definition file.`,
380-
sourceRange(this.configuration, cssContentsAst.root(), file.identifier, rule),
381-
),
382-
);
383-
return;
369+
const parsedSelectors = parseSelector(rule);
370+
371+
parsedSelectors.forEach(sel => {
372+
let doProcess = true;
373+
374+
// Check selector: do not process selectors with class names that
375+
// aren't from this block. (aka: resolution selectors)
376+
sel.eachSelectorNode(selNode => {
377+
if (selNode.type !== "class") {
378+
return;
379+
}
380+
if (!styleNodesMap[selNode.value]) {
381+
doProcess = false;
382+
return;
383+
}
384+
});
385+
// If this node should be processed, add its declarations to each class
386+
// in the key selector (the last part of the selector).
387+
// We add the declarations so that later we can determine any conflicts
388+
// between the imported CSS and any app CSS that relies on it.
389+
if (doProcess) {
390+
const keys = sel.key.nodes;
391+
keys.forEach(key => {
392+
if (key.value && key.type === "class") {
393+
styleNodesMap[key.value].rulesets.addRuleset(this.configuration, file.identifier, rule);
394+
}
395+
});
384396
}
385-
styleNode.rulesets.addRuleset(this.configuration, file.identifier, rule);
386397
});
387398
});
388399

389-
// TODO: Set the block's name from the block-name rule. (We skip this later for definition files.)
390-
391400
// And we're done!
392401
return block;
393402
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export class BlockParser {
9393
let guid: string;
9494
let guidDiscoveryError: CssBlockError | undefined;
9595
try {
96-
guid = await discoverGuid(configuration, root, sourceFile, isDfnFile, expectedGuid) || gen_guid(identifier, configuration.guidAutogenCharacters);
96+
guid = discoverGuid(configuration, root, sourceFile, isDfnFile, expectedGuid) || gen_guid(identifier, configuration.guidAutogenCharacters);
9797
} catch (e) {
9898
guidDiscoveryError = e;
9999
guid = gen_guid(identifier, configuration.guidAutogenCharacters);

packages/@css-blocks/core/src/BlockParser/features/add-preset-selectors.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Block } from "../../BlockTree";
55
import { Configuration } from "../../configuration";
66
import { CssBlockError } from "../../errors";
77
import { sourceRange } from "../../SourceLocation";
8+
import { getStyleTargets } from "../block-intermediates";
89
import { stripQuotes } from "../utils";
910

1011
/**
@@ -26,11 +27,9 @@ import { stripQuotes } from "../utils";
2627
export function addPresetSelectors(configuration: Configuration, root: postcss.Root, block: Block, file: string) {
2728
// For each rule declared in the file...
2829
root.walkRules(rule => {
29-
let foundDecl = false;
3030

3131
// Find the block-class declaration...
3232
rule.walkDecls("block-class", decl => {
33-
foundDecl = true;
3433
const val = stripQuotes(decl.value);
3534

3635
// Test that this actually could be a class name.
@@ -44,21 +43,29 @@ export function addPresetSelectors(configuration: Configuration, root: postcss.R
4443
}
4544

4645
// And add its value to the corresponding BlockClass node.
47-
rule.selectors.forEach(sel => {
48-
const node = block.find(sel);
49-
if (!node) {
46+
const parsedSelectors = block.getParsedSelectors(rule);
47+
parsedSelectors.forEach(sel => {
48+
const styleTarget = getStyleTargets(block, sel.key);
49+
if (styleTarget.blockAttrs.length > 0) {
50+
styleTarget.blockAttrs[0].setPresetClassName(val);
51+
} else if (styleTarget.blockClasses.length > 0) {
52+
styleTarget.blockClasses[0].setPresetClassName(val);
53+
} else {
5054
throw new Error(`Couldn\'t find block class corresponding to selector ${sel}. This shouldn't happen.`);
5155
}
52-
node.setPresetClassName(val);
5356
});
5457
});
58+
});
5559

56-
// If we didn't find block-class declared, we should error.
57-
if (!foundDecl) {
60+
// At this point, every style node should have a fixed block-class.
61+
block.all(true).forEach(styleNode => {
62+
if (!styleNode.presetCssClass) {
5863
block.addError(
5964
new CssBlockError(
60-
`Definition file rule ${rule.selectors} is missing a 'block-class' declaration`,
61-
sourceRange(configuration, root, file, rule),
65+
`Style node ${styleNode.asSource()} doesn't have a preset block class after parsing definition file. You may need to declare this style node in the definition file.`,
66+
{
67+
filename: file,
68+
},
6269
),
6370
);
6471
}

packages/@css-blocks/core/src/BlockParser/features/discover-guid.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as errors from "../../errors";
66
import { sourceRange } from "../../SourceLocation";
77
import { stripQuotes } from "../utils";
88

9-
export async function discoverGuid(configuration: Configuration, root: postcss.Root, file: string, isDfnFile = false, expectedId?: string): Promise<string | undefined> {
9+
export function discoverGuid(configuration: Configuration, root: postcss.Root, file: string, isDfnFile = false, expectedId?: string): string | undefined {
1010
let blockId: string | undefined;
1111
let scopeNode: postcss.Rule | undefined;
1212

@@ -22,7 +22,7 @@ export async function discoverGuid(configuration: Configuration, root: postcss.R
2222
blockId = stripQuotes(decl.value.trim());
2323
// We don't have to expect an ID was declared in the header comment of the Compiled CSS
2424
// file, but we need to hard error if it's a mismatch.
25-
if (expectedId && decl.value !== expectedId) {
25+
if (expectedId && blockId !== expectedId) {
2626
throw new errors.InvalidBlockSyntax(
2727
`Expected block-id property in definition data to match header in Compiled CSS.`,
2828
sourceRange(configuration, root, file, decl),

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@ export const EXTENDS = "extends";
1111
export const IMPLEMENTS = "implements";
1212
export const BLOCK_NAME = "block-name";
1313
export const BLOCK_ID = "block-id";
14+
export const BLOCK_INTERFACE_INDEX = "block-interface-index";
15+
export const BLOCK_CLASS = "block-class";
1416
export const BLOCK_ALIAS = "block-alias";
1517
export const COMPOSES = "composes";
16-
export const BLOCK_PROP_NAMES = new Set([BLOCK_NAME, BLOCK_ALIAS, EXTENDS, IMPLEMENTS, COMPOSES]);
17-
export const BLOCK_PROP_NAMES_RE = /^(extends|implements|block-name|composes|block-alias)$/;
18+
export const BLOCK_PROP_NAMES = new Set([BLOCK_ID, BLOCK_NAME, BLOCK_CLASS, BLOCK_ALIAS, EXTENDS, IMPLEMENTS, COMPOSES, BLOCK_INTERFACE_INDEX]);
19+
export const BLOCK_PROP_NAMES_RE = /^(extends|implements|block-name|block-id|block-interface-index|block-class|composes|block-alias)$/;
1820

1921
// At Rules
2022
export const BLOCK_DEBUG = "block-debug";
2123
export const BLOCK_GLOBAL = "block-global";
2224
export const BLOCK_IMPORT = "block";
2325
export const BLOCK_EXPORT = "export";
24-
export const BLOCK_AT_RULES = new Set([BLOCK_DEBUG, BLOCK_GLOBAL, BLOCK_IMPORT, BLOCK_EXPORT]);
26+
export const BLOCK_SYNTAX_VERSION = "block-syntax-version";
27+
export const BLOCK_AT_RULES = new Set([BLOCK_DEBUG, BLOCK_GLOBAL, BLOCK_IMPORT, BLOCK_EXPORT, BLOCK_SYNTAX_VERSION]);
2528

2629
// Prop Values
2730
// TODO: Flesh out prop value parser APIs and actually use them.

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ export class AttrValue extends Style<AttrValue, Block, Attribute, never> {
9696
}
9797

9898
public cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>): string {
99-
if (this.presetSelector) {
100-
return this.presetSelector;
99+
if (this.presetCssClass) {
100+
return this.presetCssClass;
101101
}
102102
switch (config.outputMode) {
103103
case OutputMode.BEM:

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ export class Block
442442
* @param shallow - Pass true to exclude inherited objects.
443443
* @returns Collection of Styles objects, organized by preset selector value.
444444
*/
445-
compiledClassesMap(shallow?: boolean): ObjectDictionary<Styles> {
445+
presetClassesMap(shallow?: boolean): ObjectDictionary<Styles> {
446446
const result = {};
447447
const all = this.all(shallow);
448448

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ export class BlockClass extends Style<BlockClass, Block, Block, Attribute> {
205205
* @returns String representing output class.
206206
*/
207207
public cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>): string {
208-
if (this.presetSelector) {
209-
return this.presetSelector;
208+
if (this.presetCssClass) {
209+
return this.presetCssClass;
210210
}
211211
switch (config.outputMode) {
212212
case OutputMode.BEM:

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

+2-11
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export abstract class Style<
3636
* The preset selector for this particular class node. Set this if you're
3737
* loading in a definition file and `block-class` is set.
3838
*/
39-
protected presetSelector: string | undefined;
39+
public presetCssClass: string | undefined;
4040

4141
/**
4242
* Save name, parent container, and create the PropertyContainer for this data object.
@@ -52,15 +52,6 @@ export abstract class Style<
5252
*/
5353
public abstract cssClass(config: ResolvedConfiguration, reservedClassNames: Set<string>): string;
5454

55-
/**
56-
* The preset selector string for this style node. This is set if a specific
57-
* class name was specified using a block-class declaration. This is only
58-
* relevant to definition files.
59-
*/
60-
get presetCssClass(): string | undefined {
61-
return this.presetSelector;
62-
}
63-
6455
/**
6556
* Return the source selector this `Style` was read from.
6657
* @param scope Optional scope to resolve this name relative to. If `true`, return the Block name instead of `:scope`. If a Block object, return with the local name instead of `:scope`.
@@ -118,7 +109,7 @@ export abstract class Style<
118109
* @param selector - The class name to set.
119110
*/
120111
public setPresetClassName(selector: string): void {
121-
this.presetSelector = selector;
112+
this.presetCssClass = selector;
122113
}
123114

124115
/**

packages/@css-blocks/core/src/PrecompiledDefinitions/block-syntax-version.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ImportedCompiledCssFile } from "../importing";
44
/**
55
* A regex to find the block-syntax-version annotation in a definition file.
66
*/
7-
const REGEXP_BLOCK_SYNTAX_VERSION = /^@block-syntax-version (\d+);/;
7+
const REGEXP_BLOCK_SYNTAX_VERSION = /^\s*@block-syntax-version ([^;]+);/m;
88

99
/**
1010
* The earliest block-syntax-version supported by CSS Blocks. If this has been
@@ -33,19 +33,18 @@ export function determineBlockSyntaxVersion(file: ImportedCompiledCssFile): numb
3333
const versionLookupResult = dfnContents.match(REGEXP_BLOCK_SYNTAX_VERSION);
3434

3535
if (!versionLookupResult) {
36-
throw new CssBlockError("Unable to process definition file because the file is missing a block-syntax-version declaration.", {
36+
throw new CssBlockError("Unable to process definition file because the file is missing a block-syntax-version declaration or it is malformed.", {
3737
filename: dfnId,
3838
});
3939
}
4040

41-
try {
42-
const version = parseInt(versionLookupResult[1], 10);
43-
return version;
44-
} catch {
45-
throw new CssBlockError("Unable to process definition file because the declared block-syntax version isn't a number.", {
41+
const version = parseInt(versionLookupResult[1], 10);
42+
if (isNaN(version)) {
43+
throw new CssBlockError("Unable to process definition file because the declared block-syntax-version isn't a number.", {
4644
filename: dfnId,
4745
});
4846
}
47+
return version;
4948
}
5049

5150
/**

packages/@css-blocks/core/src/importing/BaseImporter.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ export abstract class BaseImporter implements Importer {
7575
// Determine start/end indexes based on the regexp results above.
7676
const [headerFullMatch, blockId] = headerRegexpResult;
7777
const headerStartIndex = headerRegexpResult.index;
78-
if (!headerStartIndex) {
78+
if (typeof headerStartIndex === "undefined") {
7979
throw new Error("Unable to determine start location of regexp result.");
8080
}
8181
const headerEndIndex = headerStartIndex + headerFullMatch.length;
8282
const [footerFullMatch] = footerRegexpResult;
8383
const footerStartIndex = footerRegexpResult.index;
84-
if (!footerStartIndex) {
84+
if (typeof footerStartIndex === "undefined") {
8585
throw new Error("Unable to determine start location of regexp result.");
8686
}
8787
const footerEndIndex = footerStartIndex + footerFullMatch.length;
@@ -98,7 +98,7 @@ export abstract class BaseImporter implements Importer {
9898
// Parse out the URL, or embedded data, for the block definition.
9999
// The definition comment should be removed from the block's CSS contents.
100100
const definitionRegexpResult = fullBlockContents.match(REGEXP_COMMENT_DEFINITION_REF);
101-
if (!definitionRegexpResult) {
101+
if (definitionRegexpResult === null) {
102102
throw new Error("Unable to find definition URL in compiled CSS. This comment must be declared between the header and footer CSS Blocks comments.");
103103
}
104104
const [definitionFullMatch, definitionUrl] = definitionRegexpResult;

0 commit comments

Comments
 (0)