Skip to content

Commit e34636b

Browse files
committed
chore(css-blocks): Code quality improvements.
- Disentangle Block from opticss - Remove SelectorFactory concept in favor of better caching in parseSelector (see opticss PR) - Comment all BlockTree modules - Add BlockTree tests - Start Style object / RulesetContainer tests - Begin crafting final Node.lookup interface for BlockTrees - Add optional non-silent mode to Node.lookup. If an error occurs during lookup, request an error to be thrown.
1 parent 115283f commit e34636b

File tree

17 files changed

+328
-170
lines changed

17 files changed

+328
-170
lines changed

packages/css-blocks/src/Block/Block.ts

+17-35
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
CompoundSelector,
55
ParsedSelector,
66
parseSelector,
7-
SelectorFactory,
87
} from "opticss";
98
import * as postcss from "postcss";
109
import selectorParser = require("postcss-selector-parser");
@@ -16,15 +15,15 @@ import {
1615
NodeAndType,
1716
stateParser,
1817
} from "../BlockParser";
19-
import { CLASS_NAME_IDENT, ROOT_CLASS } from "../BlockSyntax";
20-
import { BlockPath } from "../BlockSyntax";
18+
import { BlockPath, CLASS_NAME_IDENT, ROOT_CLASS } from "../BlockSyntax";
2119
import { OptionsReader } from "../OptionsReader";
22-
import { CssBlockError } from "../errors";
20+
import { SourceLocation } from "../SourceLocation";
21+
import { CssBlockError, InvalidBlockSyntax } from "../errors";
2322
import { FileIdentifier } from "../importing";
2423
import { LocalScopedContext } from "../util/LocalScope";
2524

2625
import { BlockClass } from "./BlockClass";
27-
import { SourceNode } from "./BlockTree";
26+
import { SourceContainer } from "./BlockTree";
2827
import { State } from "./State";
2928

3029
export type Style = BlockClass | State;
@@ -38,9 +37,7 @@ export const OBJ_REF_SPLITTER = (s: string): [string, string] | undefined => {
3837
return;
3938
};
4039

41-
export class Block
42-
extends SourceNode<Block, BlockClass>
43-
implements SelectorFactory {
40+
export class Block extends SourceContainer<Block, BlockClass> {
4441
private _rootClass: BlockClass;
4542
private _blockReferences: ObjectDictionary<Block> = {};
4643
private _blockReferencesReverseLookup: Map<Block, string> = new Map();
@@ -51,7 +48,7 @@ export class Block
5148
/**
5249
* array of paths that this block depends on and, if changed, would
5350
* invalidate the compiled css of this block. This is usually only useful in
54-
* preprocessed blocks.
51+
* pre-processed blocks.
5552
*/
5653
private _dependencies: Set<string>;
5754

@@ -90,6 +87,7 @@ export class Block
9087
return;
9188
}
9289
}
90+
9391
lookupLocal(name: string): Style | undefined {
9492
let blockRef = this._blockReferences[name];
9593
if (blockRef) {
@@ -120,26 +118,27 @@ export class Block
120118
* A single dot by itself returns the current block.
121119
* @returns The Style referenced at the supplied path.
122120
*/
123-
public lookup(path: string | BlockPath): Style | undefined {
121+
public lookup(path: string | BlockPath, errLoc?: SourceLocation): Style | undefined {
124122
path = new BlockPath(path);
125123
let block = this.getReferencedBlock(path.block);
126-
if (!block) return undefined;
124+
if (!block) {
125+
if (errLoc) { throw new InvalidBlockSyntax(`No Block named "${path.block}" found in scope.`, errLoc); }
126+
return undefined;
127+
}
127128
let klass = block.resolveClass(path.class);
128-
if (!klass) return undefined;
129129
let state;
130-
if (path.state) {
130+
if (klass && path.state) {
131131
let groupName = path.state.group ? path.state.group : path.state.name;
132132
let stateName = path.state.group ? path.state.name : undefined;
133133
state = klass.resolveState(groupName, stateName);
134134
if (!state) return undefined;
135135
}
136-
return state || klass;
137136

138-
// for (let part of path.tokens()) {
137+
if (!state && !klass && errLoc) {
138+
throw new InvalidBlockSyntax(`No Style "${path.path}" found on Block "${block.name}".`, errLoc);
139+
}
139140

140-
// res = res.resolveChild(part);
141-
// if (!res) { break; }
142-
// }
141+
return state || klass || undefined;
143142
}
144143

145144
/// End of methods to implement LocalScope<Block, Style>
@@ -473,23 +472,6 @@ export class Block
473472
return false;
474473
}
475474

476-
/**
477-
* TODO: Move selector cache into `parseSelector` and have consumers of this
478-
* method interface with the parseSelector utility directly.
479-
* Given a PostCSS Rule, ensure it is present in this Block's parsed rule
480-
* selectors cache, and return the ParsedSelector array.
481-
* @param rule PostCSS Rule
482-
* @return ParsedSelector array
483-
*/
484-
getParsedSelectors(rule: postcss.Rule): ParsedSelector[] {
485-
let sels = this.parsedRuleSelectors.get(rule);
486-
if (!sels) {
487-
sels = parseSelector(rule);
488-
this.parsedRuleSelectors.set(rule, sels);
489-
}
490-
return sels;
491-
}
492-
493475
/**
494476
* Objects that contain Blocks are often passed into assorted libraries' options
495477
* hashes. Some libraries like to `JSON.stringify()` their options to create

packages/css-blocks/src/Block/BlockClass.ts

+3
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export class BlockClass extends StyleNode<BlockClass, Block, Block, StateGroup>
108108
}
109109
}
110110

111+
// TODO: Implement lookup relative to BlockClass.
112+
public lookup(): undefined { return undefined; }
113+
111114
/**
112115
* Return array self and all children.
113116
* @param shallow Pass false to not include children.

packages/css-blocks/src/Block/BlockTree/Inheritable.ts

+65-32
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
1+
/**
2+
* The `Inheritable` module delivers the core data structure for making type safe,
3+
* single-typed-children, tree nodes.
4+
*
5+
* This abstract class, in order to support tree nodes of *any* kind, makes creative
6+
* use of the `any` type in a few places – but don't be fooled! These typing holes
7+
* are *required* to be plugged by any by any implementing class by virtue of the type
8+
* generics they are forced to provide when extending. By the time this abstract class
9+
* is exposed to the outside world, all typing holes have been plugged and we are left
10+
* with a fully typed tree structure.
11+
*
12+
* @module Block/BlockTree/Inheritable
13+
*/
114
import { ObjectDictionary } from "@opticss/util";
215
import { whatever } from "@opticss/util";
316

17+
import { SourceLocation } from "../../SourceLocation";
18+
419
/* tslint:disable:prefer-whatever-to-any */
520
export type AnyNode = Inheritable<any, any, any, any>;
621

722
export abstract class Inheritable<
823
Self extends Inheritable<Self, Root, Parent, Child>,
9-
Root extends Inheritable<any, Root, null, any> | Self,
10-
Parent extends Inheritable<any, Root, any, Self> | null,
11-
Child extends Inheritable<any, Root, Self, any> | null
24+
Root extends Inheritable<any, Root, null, AnyNode> | Self,
25+
Parent extends Inheritable<any, Root, AnyNode | null, Self> | null,
26+
Child extends Inheritable<any, Root, Self, AnyNode | null> | null
1227
> {
1328
/* tslint:enable:prefer-whatever-to-any */
29+
1430
protected _name: string;
1531
protected _base: Self | undefined;
1632
protected _root: Root | Self;
@@ -23,17 +39,6 @@ export abstract class Inheritable<
2339
*/
2440
protected abstract newChild(name: string): Child;
2541

26-
// // TODO: Currently only ever returns itself if is a style. Need to get it to look other things up.
27-
// public lookup(path: string | BlockPath): Self | Child | Descendants | undefined {
28-
// path = new BlockPath(path);
29-
// let res: Self | Child | Descendants | null = this.asSelf();
30-
// for (let part of path.tokens()) {
31-
// res = res.resolveChild(part);
32-
// if (!res) { break; }
33-
// }
34-
// return res ? res : undefined;
35-
// }
36-
3742
/**
3843
* Inheritable constructor
3944
* @param name Name for this Inheritable instance.
@@ -74,14 +79,28 @@ export abstract class Inheritable<
7479
}
7580

7681
/**
77-
* traverse parents and return the base block object.
78-
* @returns The base block in this container tree.
82+
* Traverse parents and return the base block object.
83+
* @returns The base node in this tree.
7984
*/
8085
public get root(): Root {
8186
// This is a safe cast because we know root will only be set to `Self` for `Source` nodes.
8287
return this._root as Root;
8388
}
8489

90+
/**
91+
* The `block` property is an alias for `root`. This isn't the dryest place to put
92+
* this line, but every extension re-declared this interface itself and I wanted it
93+
* in one place.
94+
* @returns The base node in this tree.
95+
*/
96+
public get block(): Root { return this.root; }
97+
98+
public abstract lookup(path: string, errLoc?: SourceLocation): AnyNode | undefined;
99+
100+
/**
101+
* Sets the base node that this node inherits from. Must be of same type.
102+
* @prop base Self The new base node.
103+
*/
85104
public setBase(base: Self) {
86105
this._base = base;
87106
}
@@ -92,6 +111,7 @@ export abstract class Inheritable<
92111
* other relationships to this object.
93112
*
94113
* If nothing is inherited, this returns an empty array.
114+
* @returns The array of nodes this node inherits from.
95115
*/
96116
public resolveInheritance(): Self[] {
97117
let inherited: Self[] = [];
@@ -107,6 +127,7 @@ export abstract class Inheritable<
107127
* Resolves the child with the given name from this node's inheritance
108128
* chain. Returns undefined if the child is not found.
109129
* @param name The name of the child to resolve.
130+
* @returns The child node, or `null`
110131
*/
111132
public resolveChild(name: string): Child | null {
112133
let state: Child | null = this.getChild(name);
@@ -118,32 +139,32 @@ export abstract class Inheritable<
118139
return state || null;
119140
}
120141

121-
// /**
122-
// * Find the closest common ancestor Block between two Styles
123-
// * TODO: I think there is a more efficient way to do this.
124-
// * @param relative Style to compare ancestry with.
125-
// * @returns The Style's common Block ancestor, or null.
126-
// */
127-
// commonAncestor(relative: Style<Child>): Style<Child> | null {
128-
// let blockChain = new Set(...this.block.rootClass.resolveInheritance()); // lol
129-
// blockChain.add(this.block.rootClass);
130-
// let common = [relative.block.rootClass, ...relative.block.rootClass.resolveInheritance()].filter(b => blockChain.has(b));
131-
// return common.length ? common[0] as Style<Child> : null;
132-
// }
133-
134142
/**
135-
* Given a parent that is a base class of this style, retrieve this style's
136-
* base style from it, if it exists. This method does not traverse into base styles.
143+
* Retrieve a child node from this object at `key`.
144+
* @param key string The key to fetch the child object from.
145+
* @returns The child node.
137146
*/
138147
public getChild(key: string): Child | null {
139148
return this._children.get(key) || null;
140149
}
141150

151+
/**
152+
* Set a child node on this object at `key`.
153+
* @param key string The key to set the child object to.
154+
* @returns The child node.
155+
*/
142156
public setChild(key: string, value: Child): Child {
143157
this._children.set(key, value);
144158
return value;
145159
}
146160

161+
/**
162+
* Ensure a child node exists on this object at `key`. If it does not, create it.
163+
* If `key` is not provided, use the child name as the key.
164+
* @param name string The name of this object to ensure.
165+
* @param key string The key at which this child object should be (optional)
166+
* @returns The child node.
167+
*/
147168
public ensureChild(name: string, key?: string): Child {
148169
key = key !== undefined ? key : name;
149170
if (!this._children.has(name)) {
@@ -152,15 +173,27 @@ export abstract class Inheritable<
152173
return this._children.get(key) as Child;
153174
}
154175

176+
/**
177+
* Returns an array of all children nodes in the order they were added.
178+
* @returns The children array.
179+
*/
155180
public children(): Child[] {
156181
return [...this._children.values()];
157182
}
158183

184+
/**
185+
* Returns a map of all children nodes at the keys they are stored..
186+
* @returns The children map.
187+
*/
159188
public childrenMap(): Map<string, Child> {
160189
return new Map(this._children);
161190
}
162191

163-
// TODO: Cache this maybe? Convert entire model to only use hash?...
192+
/**
193+
* Returns a hash of all children nodes at the keys they are stored..
194+
* TODO: Cache this maybe? Convert entire model to only use hash?...
195+
* @returns The children hash.
196+
*/
164197
public childrenHash(): ObjectDictionary<Child> {
165198
let out = {};
166199
for (let [key, value] of this._children.entries()) {

0 commit comments

Comments
 (0)