Skip to content

Commit d37e704

Browse files
committedSep 24, 2019
feat: Respect explicit exports for a block interface.
Previously, the block's interface constituted of all of its imported blocks as well as the classes and states defined on the block. Now, the block's public interface will consist of only that which is explicitly exported using @export
1 parent e47c2db commit d37e704

File tree

15 files changed

+106
-42
lines changed

15 files changed

+106
-42
lines changed
 

Diff for: ‎packages/@css-blocks/core/src/Analyzer/Analysis.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { ResolvedConfiguration } from "../configuration";
2121
import { Analyzer } from "./Analyzer";
2222
import { ElementAnalysis, SerializedElementAnalysis } from "./ElementAnalysis";
2323
import { TemplateValidator, TemplateValidatorOptions } from "./validations";
24+
import { DEFAULT_EXPORT } from "../BlockSyntax";
2425

2526
/**
2627
* This interface defines a JSON friendly serialization
@@ -152,13 +153,13 @@ export class Analysis<K extends keyof TemplateTypes> {
152153
let names = Object.keys(this.blocks);
153154
for (let name of names) {
154155
if (this.blocks[name] === block) {
155-
return name;
156+
return name === DEFAULT_EXPORT ? "" : name;
156157
}
157158
}
158159
for (let name of names) {
159160
let superBlock = this.blocks[name].base;
160161
while (superBlock) {
161-
if (superBlock === block) return name;
162+
if (superBlock === block) return name === DEFAULT_EXPORT ? "" : name;
162163
superBlock = superBlock.base;
163164
}
164165
}

Diff for: ‎packages/@css-blocks/core/src/BlockCompiler/index.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { postcss } from "opticss";
44
import { Analyzer } from "../Analyzer";
55
import {
66
BLOCK_DEBUG,
7-
BLOCK_IMPORT,
87
BLOCK_PROP_NAMES_RE,
98
ROOT_CLASS,
109
parseBlockDebug,
10+
BLOCK_AT_RULES,
1111
} from "../BlockSyntax";
1212
import { Block } from "../BlockTree";
1313
import {
@@ -40,10 +40,11 @@ export class BlockCompiler {
4040
// Process all debug statements for this block.
4141
this.processDebugStatements(filename, root, block);
4242

43-
// Clean up CSS Block specific properties.
44-
root.walkAtRules(BLOCK_IMPORT, (atRule) => {
45-
atRule.remove();
46-
});
43+
// Clean up CSS Block specific at-rules.
44+
for (let atRuleName of BLOCK_AT_RULES) {
45+
root.walkAtRules(atRuleName, (atRule) => atRule.remove());
46+
}
47+
4748
root.walkRules(ROOT_CLASS, (rule) => {
4849
rule.walkDecls(BLOCK_PROP_NAMES_RE, (decl) => {
4950
decl.remove();
@@ -77,9 +78,6 @@ export class BlockCompiler {
7778
if (channel === "comment") {
7879
let text = `${ref.debug(this.config).join("\n * ")}\n`;
7980
atRule.replaceWith(this.postcss.comment({ text }));
80-
} else {
81-
// stderr/stdout are emitted during parse.
82-
atRule.remove();
8381
}
8482
});
8583
}

Diff for: ‎packages/@css-blocks/core/src/BlockSyntax/BlockPath.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BlockPathError, ErrorLocation, Position, errorHasRange } from "../error
33
import {
44
ATTR_PRESENT,
55
CLASS_NAME_IDENT,
6+
DEFAULT_EXPORT,
67
ROOT_CLASS,
78
STATE_NAMESPACE,
89
} from "./BlockSyntax";
@@ -67,7 +68,7 @@ export const ERRORS = {
6768
function stringify(tokens: Token[]): string {
6869
let out = "";
6970
for (let token of tokens) {
70-
if (isBlock(token)) { out += token.name; }
71+
if (isBlock(token)) { out += token.name === DEFAULT_EXPORT ? "" : token.name; }
7172
else if (isClass(token)) { out += token.name === ROOT_CLASS ? token.name : `.${token.name}`; }
7273
else if (isAttribute(token)) {
7374
let namespace = token.namespace ? `${token.namespace}|` : "";
@@ -165,12 +166,13 @@ export class BlockPath {
165166

166167
// Ensure we only have a single token of each type per block path.
167168
if (isBlock(token)) {
169+
token.name = token.name || DEFAULT_EXPORT;
168170
this._block = this._block ? this.throw(ERRORS.multipleOfType(token.type)) : token;
169171
}
170172
if (isClass(token)) {
171173
this._class = this._class ? this.throw(ERRORS.multipleOfType(token.type)) : token;
172174
// If no block has been added yet, automatically inject the `self` block name.
173-
if (!this._block) { this.addToken({ type: "block", name: "" }, false); }
175+
if (!this._block) { this.addToken({ type: "block", name: DEFAULT_EXPORT }, false); }
174176
}
175177
if (isAttribute(token)) {
176178
this._attribute = this._attribute ? this.throw(ERRORS.multipleOfType(token.type)) : token;
@@ -341,7 +343,7 @@ export class BlockPath {
341343
* Get the parsed block name of this Block Path
342344
*/
343345
get block(): string {
344-
return this._block ? this._block.name : "";
346+
return this._block ? this._block.name : DEFAULT_EXPORT;
345347
}
346348

347349
/**
@@ -370,7 +372,7 @@ export class BlockPath {
370372
* Return a new BlockPath without the parent-most token.
371373
*/
372374
childPath() {
373-
return BlockPath.from(this.parts.slice(this._block && this._block.name ? 1 : 2));
375+
return BlockPath.from(this.parts.slice(this._block && this._block.name !== DEFAULT_EXPORT ? 1 : 2));
374376
}
375377

376378
/**

Diff for: ‎packages/@css-blocks/core/src/BlockTree/Block.ts

+56-1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,51 @@ export class Block
138138
return attr || klass || undefined;
139139
}
140140

141+
/**
142+
* Lookup a sub-block either locally, or on a exported foreign block.
143+
* @param reference
144+
* A reference to a block object adhering to the following grammar:
145+
* reference -> <ident> '.' <sub-reference> // reference through sub-block <ident>
146+
* | <ident> // reference to sub-block <ident>
147+
* | '.' <class-selector> // reference to class in this block
148+
* | <attr-selector> // reference to attribute in this block
149+
* | '.' // reference to this block
150+
* sub-reference -> <ident> '.' <sub-reference> // reference through sub-sub-block
151+
* | <object-selector> // reference to object in sub-block
152+
* object-selector -> <block-selector> // reference to this sub-block
153+
* | <class-selector> // reference to class in sub-block
154+
* | <attribute-selector> // reference to attribute in this sub-block
155+
* block-selector -> 'root'
156+
* class-selector -> <ident>
157+
* attribute-selector -> '[state|' <ident> ']'
158+
* ident -> regex:[a-zA-Z_-][a-zA-Z0-9]*
159+
* A single dot by itself returns the current block.
160+
* @returns The Style referenced at the supplied path.
161+
*/
162+
public externalLookup(path: string | BlockPath, errLoc?: SourceLocation): Styles | undefined {
163+
path = new BlockPath(path);
164+
let block = this.getExportedBlock(path.block);
165+
if (!block) {
166+
if (errLoc) { throw new InvalidBlockSyntax(`No Block named "${path.block}" found in scope.`, errLoc); }
167+
return undefined;
168+
}
169+
let klass = block.resolveClass(path.class);
170+
let attrInfo = path.attribute;
171+
let attr;
172+
if (klass && attrInfo) {
173+
attr = klass.resolveAttributeValue(attrInfo);
174+
if (!attr) return undefined;
175+
}
176+
177+
if (!attr && !klass && errLoc) {
178+
throw new InvalidBlockSyntax(`No Style "${path.path}" found on Block "${block.name}".`, errLoc);
179+
}
180+
181+
return attr || klass || undefined;
182+
}
183+
184+
185+
141186
/**
142187
* Add an absolute, normalized path as a compilation dependency. This is used
143188
* to invalidate caches and trigger watchers when those files change.
@@ -247,7 +292,7 @@ export class Block
247292
* @returns Block | null.
248293
*/
249294
getReferencedBlock(localName: string): Block | null {
250-
if (localName === "" || localName === DEFAULT_EXPORT) { return this; }
295+
if (localName === DEFAULT_EXPORT) { return this; }
251296
return this._blockReferences[localName] || null;
252297
}
253298

@@ -270,6 +315,16 @@ export class Block
270315
this._blockExportReverseLookup.set(block, remoteName);
271316
}
272317

318+
/**
319+
* Iterates over each exported block, applying the callback to it
320+
* @param callback the function to iterate over each exported block
321+
*/
322+
eachBlockExport(callback: (name: string, block: Block) => unknown) {
323+
for (let name of Object.keys(this._blockExports)) {
324+
callback(name, this._blockExports[name]);
325+
}
326+
}
327+
273328
/**
274329
* Get an exported Block at name `remoteName`.
275330
* @param remoteName The public name of the requested exported block.

Diff for: ‎packages/@css-blocks/core/test/BlockSyntax/block-path-test.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { assert } from "chai";
22
import { skip, suite, test } from "mocha-typescript";
33

4-
import { BlockPath, ERRORS } from "../../src/BlockSyntax";
4+
import { BlockPath, ERRORS, DEFAULT_EXPORT } from "../../src/BlockSyntax";
55
import { ErrorLocation } from "../../src/errors";
66

77
function parseBlockPath(blockPath: string, loc?: ErrorLocation): BlockPath {
@@ -18,7 +18,7 @@ export class BlockPathTests {
1818

1919
@test "finds the class"() {
2020
let path = new BlockPath(".test");
21-
assert.equal(path.block, "");
21+
assert.equal(path.block, DEFAULT_EXPORT);
2222
assert.equal(path.path, ".test");
2323
}
2424

@@ -42,37 +42,37 @@ export class BlockPathTests {
4242

4343
@test "finds a namespaced attribute with value"() {
4444
let path = new BlockPath("[state|my-attr=value]");
45-
assert.equal(path.block, "");
45+
assert.equal(path.block, DEFAULT_EXPORT);
4646
assert.equal(path.path, `:scope[state|my-attr="value"]`);
4747
}
4848

4949
@test "finds a namespace with value in single quotes"() {
5050
let path = new BlockPath("[state|my-attr='my value']");
51-
assert.equal(path.block, "");
51+
assert.equal(path.block, DEFAULT_EXPORT);
5252
assert.equal(path.path, `:scope[state|my-attr="my value"]`);
5353
}
5454

5555
@test "finds a namespace with value in double quotes"() {
5656
let path = new BlockPath(`[state|my-attr="my value"]`);
57-
assert.equal(path.block, "");
57+
assert.equal(path.block, DEFAULT_EXPORT);
5858
assert.equal(path.path, `:scope[state|my-attr="my value"]`);
5959
}
6060

6161
@test "finds a class with a namespace and value"() {
6262
let path = new BlockPath(".class[state|my-attr=value]");
63-
assert.equal(path.block, "");
63+
assert.equal(path.block, DEFAULT_EXPORT);
6464
assert.equal(path.path, `.class[state|my-attr="value"]`);
6565
}
6666

6767
@test "finds a class with a namespace and value in single quotes"() {
6868
let path = new BlockPath(".class[state|my-attr='my value']");
69-
assert.equal(path.block, "");
69+
assert.equal(path.block, DEFAULT_EXPORT);
7070
assert.equal(path.path, `.class[state|my-attr="my value"]`);
7171
}
7272

7373
@test "finds a class with a namespace and value in double quotes"() {
7474
let path = new BlockPath(`.class[state|my-attr="my value"]`);
75-
assert.equal(path.block, "");
75+
assert.equal(path.block, DEFAULT_EXPORT);
7676
assert.equal(path.path, `.class[state|my-attr="my value"]`);
7777
}
7878

@@ -96,7 +96,7 @@ export class BlockPathTests {
9696

9797
@test "finds :scope when passed empty string"() {
9898
let path = new BlockPath("");
99-
assert.equal(path.block, "");
99+
assert.equal(path.block, DEFAULT_EXPORT);
100100
assert.equal(path.path, ":scope");
101101
assert.equal(path.attribute, undefined);
102102
}

Diff for: ‎packages/@css-blocks/core/test/validations/property-conflict-validator-test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,7 @@ function constructElement(block: Block, ...styles: string[]) {
13131313
let analysis = analyzer.newAnalysis(info);
13141314

13151315
analysis.addBlock(":scope", block);
1316-
block.eachBlockReference((name, ref) => {
1316+
block.eachBlockExport((name, ref) => {
13171317
analysis.addBlock(name, ref);
13181318
});
13191319

Diff for: ‎packages/@css-blocks/glimmer/src/Analyzer.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,11 @@ export class GlimmerAnalyzer extends Analyzer<TEMPLATE_TYPE> {
110110
let block: Block | undefined = await this.resolveBlock(dir, componentName);
111111
if (!block) { return analysis; }
112112

113-
analysis.addBlock("", block);
114113
self.debug(`Analyzing ${componentName}. Got block for component.`);
115114

116115
// Add all transitive block dependencies
117116
let localBlockNames: string[] = [];
118-
analysis.addBlock("", block);
119-
localBlockNames.push("<default>");
120-
block.eachBlockReference((name, refBlock) => {
117+
block.eachBlockExport((name, refBlock) => {
121118
analysis.addBlock(name, refBlock);
122119
localBlockNames.push(name);
123120
});

Diff for: ‎packages/@css-blocks/glimmer/src/ElementAnalyzer.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type AttrRewriteMap = { [key: string]: TemplateElement };
2727
const STATE = /^state:(?:([^.]+)\.)?([^.]+)$/;
2828
const STYLE_IF = "style-if";
2929
const STYLE_UNLESS = "style-unless";
30+
const DEFAULT_BLOCK_NAME = "default";
3031

3132
const debug = debugGenerator("css-blocks:glimmer:element-analyzer");
3233

@@ -40,7 +41,7 @@ export class ElementAnalyzer {
4041

4142
constructor(analysis: GlimmerAnalysis, cssBlocksOpts: CSSBlocksConfiguration) {
4243
this.analysis = analysis;
43-
this.block = analysis.getBlock("")!; // Local block check done elsewhere
44+
this.block = analysis.getBlock(DEFAULT_BLOCK_NAME)!; // Local block check done elsewhere
4445
this.template = analysis.template;
4546
this.cssBlocksOpts = cssBlocksOpts;
4647
}
@@ -160,9 +161,9 @@ export class ElementAnalyzer {
160161
}
161162

162163
private lookupClass(name: string, node: AST.Node): BlockClass {
163-
let found = this.block.lookup(name);
164+
let found = this.block.externalLookup(name);
164165
if (!found && !/\./.test(name)) {
165-
found = this.block.lookup("." + name);
166+
found = this.block.externalLookup("." + name);
166167
}
167168
if (found) {
168169
return <BlockClass>found;
@@ -257,7 +258,7 @@ export class ElementAnalyzer {
257258
element: TemplateElement,
258259
forRewrite: boolean,
259260
): void {
260-
let stateBlock = blockName ? this.block.getReferencedBlock(blockName) : this.block;
261+
let stateBlock = blockName ? this.block.getExportedBlock(blockName) : this.block;
261262
if (stateBlock === null) {
262263
throw cssBlockError(`No block named ${blockName} referenced from ${this.debugBlockPath()}`, node, this.template);
263264
}

Diff for: ‎packages/@css-blocks/glimmer/src/Rewriter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ElementAnalyzer } from "./ElementAnalyzer";
1919
import { getEmberBuiltInStates, isEmberBuiltIn } from "./EmberBuiltins";
2020
import { CONCAT_HELPER_NAME } from "./helpers";
2121
import { ResolvedFile, TEMPLATE_TYPE } from "./Template";
22+
import { DEFAULT_EXPORT } from "@css-blocks/core/dist/src/BlockSyntax";
2223

2324
// TODO: The state namespace should come from a config option.
2425
const STYLE_ATTR = /^(class$|state:)/;
@@ -66,7 +67,7 @@ export class GlimmerRewriter implements ASTPluginWithDeps {
6667
this.syntax = syntax;
6768
this.analysis = analysis;
6869
this.template = analysis.template;
69-
this.block = analysis.getBlock("")!; // Local block check done elsewhere
70+
this.block = analysis.getBlock(DEFAULT_EXPORT)!; // Local block check done elsewhere
7071
this.styleMapping = styleMapping;
7172
this.cssBlocksOpts = resolveConfiguration(cssBlocksOpts);
7273
this.elementCount = 0;

Diff for: ‎packages/@css-blocks/glimmer/test/fixtures/readme-app/src/ui/components/page-layout/stylesheet.css

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
.sidebar[state|collapsed] { display: none; }
66
.main { border-right: 2px groove gray; }
77
.recommended { background-color: orange }
8+
9+
@export grid;

Diff for: ‎packages/@css-blocks/glimmer/test/fixtures/styled-app/src/ui/components/with-dynamic-classes/stylesheet.css

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@
1212
.world[state|thick] {
1313
border-width: 3px;
1414
}
15+
16+
@export (h, t);

Diff for: ‎packages/@css-blocks/glimmer/test/fixtures/styled-app/src/ui/components/with-dynamic-states/stylesheet.css

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@block h from "./header.css";
1+
@export h from "./header.css";
22

33
:scope {
44
color: red;
@@ -11,3 +11,4 @@
1111
.world[state|thick] {
1212
border-width: 3px;
1313
}
14+

Diff for: ‎packages/@css-blocks/glimmer/test/fixtures/styled-app/src/ui/components/with-link-to/stylesheet.css

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@block external from "./external.css";
2-
@block util from "./util.css";
2+
@export util from "./util.css";
33

44
:scope {
55
extends: external;
@@ -32,4 +32,6 @@
3232

3333
.link-4[state|disabled] {
3434
color: red;
35-
}
35+
}
36+
37+
@export external;

Diff for: ‎packages/@css-blocks/glimmer/test/fixtures/styled-app/src/ui/components/with-multiple-blocks/stylesheet.css

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@
1010

1111
.world[state|thick] {
1212
border-width: 3px;
13-
}
13+
}
14+
15+
@export h;

Diff for: ‎packages/@css-blocks/glimmer/test/stylesheet-analysis-test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("Stylesheet analysis", function() {
1616
let serializedAnalysis = analysis.serialize();
1717
assert.equal(analysis.template.identifier, "template:/styled-app/components/my-app");
1818
assert.deepEqual(serializedAnalysis.blocks, {
19-
"": fixture("styled-app/src/ui/components/my-app/stylesheet.css"),
19+
"default": fixture("styled-app/src/ui/components/my-app/stylesheet.css"),
2020
});
2121
assert.deepEqual(serializedAnalysis.stylesFound, [".editor", ".editor[state|disabled]" , ":scope", ":scope[state|is-loading]"]);
2222
let expected: ElementsAnalysis = {
@@ -45,7 +45,7 @@ describe("Stylesheet analysis", function() {
4545
let analysis = analyzer.getAnalysis(0).serialize();
4646
assert.equal(analysis.template.identifier, "template:/styled-app/components/with-multiple-blocks");
4747
assert.deepEqual(analysis.blocks, {
48-
"": fixture("styled-app/src/ui/components/with-multiple-blocks/stylesheet.css"),
48+
"default": fixture("styled-app/src/ui/components/with-multiple-blocks/stylesheet.css"),
4949
"h": fixture("styled-app/src/ui/components/with-multiple-blocks/header.css"),
5050
});
5151
assert.deepEqual(analysis.stylesFound, [".world", ".world[state|thick]", ":scope", "h.emphasis", "h.emphasis[state|extra]", "h:scope"]);
@@ -67,7 +67,7 @@ describe("Stylesheet analysis", function() {
6767
let analysis = analyzer.getAnalysis(0).serialize();
6868
assert.equal(analysis.template.identifier, "template:/styled-app/components/with-dynamic-states");
6969
assert.deepEqual(analysis.blocks, {
70-
"": fixture("styled-app/src/ui/components/with-dynamic-states/stylesheet.css"),
70+
"default": fixture("styled-app/src/ui/components/with-dynamic-states/stylesheet.css"),
7171
"h": fixture("styled-app/src/ui/components/with-dynamic-states/header.css"),
7272
});
7373
assert.deepEqual(analysis.stylesFound, [
@@ -119,7 +119,7 @@ describe("Stylesheet analysis", function() {
119119
let analysis = analyzer.getAnalysis(0).serialize();
120120
assert.equal(analysis.template.identifier, "template:/styled-app/components/with-dynamic-classes");
121121
assert.deepEqual(analysis.blocks, {
122-
"": fixture("styled-app/src/ui/components/with-dynamic-classes/stylesheet.css"),
122+
"default": fixture("styled-app/src/ui/components/with-dynamic-classes/stylesheet.css"),
123123
"h": fixture("styled-app/src/ui/components/with-dynamic-classes/header.css"),
124124
"t": fixture("styled-app/src/ui/components/with-dynamic-classes/typography.css"),
125125
});

0 commit comments

Comments
 (0)
Please sign in to comment.