Skip to content

Commit 63e4a45

Browse files
committed
feat(css-blocks): Full type safety for all BlockTree objects.
1 parent 6824fa8 commit 63e4a45

18 files changed

+243
-198
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -499,4 +499,4 @@ export class Block
499499

500500
export function isBlock(o?: object): o is Block {
501501
return o instanceof Block;
502-
}
502+
}

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

+29-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Block } from "./Block";
2-
import { NodeStyle } from "./Style";
2+
import { NodeStyle } from "./BlockTree";
33
import { StateGroup } from "./StateGroup";
44
import { State } from "./State";
55
import { Attribute } from "@opticss/element-analysis";
@@ -18,15 +18,16 @@ export interface StateInfo {
1818
/**
1919
* Represents a Class present in the Block.
2020
*/
21-
export class BlockClass extends NodeStyle<BlockClass, Block, StateGroup> {
21+
export class BlockClass extends NodeStyle<BlockClass, Block, Block, StateGroup> {
2222
private _sourceAttribute: Attribute;
2323

24-
protected newChild(name: string): StateGroup { return new StateGroup(name, this); }
24+
protected newChild(name: string): StateGroup { return new StateGroup(name, this, this.block); }
2525

2626
get isRoot(): boolean { return this.name === "root"; }
2727

2828
public getGroups(): StateGroup[] { return this.children(); }
29-
public getGroup(name: string, filter?: string): State[] {
29+
public getGroup(name: string): StateGroup | null { return this.getChild(name); }
30+
public getStates(name: string, filter?: string): State[] {
3031
let group = this.getChild(name);
3132
if (!group) { return []; }
3233
let states = group.states();
@@ -35,14 +36,32 @@ export class BlockClass extends NodeStyle<BlockClass, Block, StateGroup> {
3536
public ensureGroup(name: string): StateGroup { return this.ensureChild(name); }
3637
public resolveGroup(name: string): StateGroup | null { return this.resolveChild(name); }
3738
public stateGroups(): StateGroup[] { return this.children(); }
38-
public resolveState(group: string, state: string): State | null {
39-
let parent = this.resolveChild(group);
40-
if (parent) {
41-
parent.resolveChild(state);
42-
}
39+
public resolveState(groupName: string, stateName = UNIVERSAL_STATE): State | null {
40+
let parent = this.resolveChild(groupName);
41+
if (parent) { return parent.resolveChild(stateName); }
4342
return null;
4443
}
4544

45+
/**
46+
* Resolves all sub-states from this state's inheritance
47+
* chain. Returns an empty object if no
48+
* @param stateName The name of the sub-state to resolve.
49+
*/
50+
resolveStates(groupName?: string): Map<string, State> {
51+
let resolved: Map<string, State> = new Map;
52+
let chain = this.resolveInheritance();
53+
chain.push(this);
54+
for (let base of chain) {
55+
let groups = !groupName ? base.getGroups() : [base.getGroup(groupName)];
56+
for (let group of groups ) {
57+
if (group && group.states()) {
58+
resolved = new Map([...resolved, ...group.statesMap()]);
59+
}
60+
}
61+
}
62+
return resolved;
63+
}
64+
4665
public booleanStates(): State[]{
4766
let res: State[] = [];
4867
for (let group of this.getGroups()) {
@@ -130,7 +149,7 @@ export class BlockClass extends NodeStyle<BlockClass, Block, StateGroup> {
130149
* @returns An array of all States that were requested.
131150
*/
132151
getState(groupName: string, stateName = UNIVERSAL_STATE): State | null {
133-
let group = this.getChild(groupName);
152+
let group = this.getGroup(groupName);
134153
return group ? group.getState(stateName) || null : null;
135154
}
136155

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

Whitespace-only changes.

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

+41-44
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import { ObjectDictionary } from "@opticss/util";
2-
import { Style, Block, isBlock, isBlockClass, isState } from './index';
3-
4-
import { BlockPath } from "../BlockSyntax";
5-
import { CssBlockError } from "../errors";
62

73
export abstract class Inheritable<
8-
Self extends Inheritable<Self, Parent, Child>,
9-
Parent extends Inheritable<any, any, Self> | null = null,
10-
Child extends Inheritable<any, Self, any> | null = null
4+
Self extends Inheritable<Self, Root, Parent, Child>,
5+
Root extends Inheritable<any, Root, null, any> | null,
6+
Parent extends Inheritable<any, Root, any, Self> | null,
7+
Child extends Inheritable<any, Root, Self, any> | null
118
> {
129

1310
protected _name: string;
1411
protected _base: Self | undefined;
15-
public _block: Block;
12+
protected _root: Root | Self;
1613
protected _baseName: string;
1714
protected _parent: Parent | undefined;
1815
protected _children: Map<string, Child> = new Map;
@@ -23,21 +20,26 @@ export abstract class Inheritable<
2320
*/
2421
protected abstract newChild(name: string): Child;
2522

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): Style | undefined {
28-
path = new BlockPath(path);
29-
if (isBlockClass(this) || isState(this)) return this;
30-
return undefined;
31-
}
23+
// // TODO: Currently only ever returns itself if is a style. Need to get it to look other things up.
24+
// public lookup(path: string | BlockPath): Self | Child | Descendants | undefined {
25+
// path = new BlockPath(path);
26+
// let res: Self | Child | Descendants | null = this.asSelf();
27+
// for (let part of path.tokens()) {
28+
// res = res.resolveChild(part);
29+
// if (!res) { break; }
30+
// }
31+
// return res ? res : undefined;
32+
// }
3233

3334
/**
3435
* Inheritable constructor
3536
* @param name Name for this Inheritable instance.
3637
* @param parent The parent Inheritable of this node.
3738
*/
38-
constructor(name: string, parent?: Parent,) {
39+
constructor(name: string, parent?: Parent, root?: Root) {
3940
this._name = name;
4041
this._parent = parent;
42+
this._root = root || this.asSelf();
4143
}
4244

4345
public get name(): string { return this._name; }
@@ -73,11 +75,8 @@ export abstract class Inheritable<
7375
* traverse parents and return the base block object.
7476
* @returns The base block in this container tree.
7577
*/
76-
public get block(): Block {
77-
if (isBlock(this)) { return this._block = this; }
78-
if (this._block !== undefined) { return this._block; }
79-
if (this.parent) { return this._block = this.parent.block; }
80-
throw new CssBlockError("Tried to access `block` on an orphaned `Style`");
78+
public get block(): Root | Self {
79+
return this._root;
8180
}
8281

8382
setBase(baseName: string, base: Self) {
@@ -117,6 +116,19 @@ export abstract class Inheritable<
117116
return state || null;
118117
}
119118

119+
// /**
120+
// * Find the closest common ancestor Block between two Styles
121+
// * TODO: I think there is a more efficient way to do this.
122+
// * @param relative Style to compare ancestry with.
123+
// * @returns The Style's common Block ancestor, or null.
124+
// */
125+
// commonAncestor(relative: Style<Child>): Style<Child> | null {
126+
// let blockChain = new Set(...this.block.rootClass.resolveInheritance()); // lol
127+
// blockChain.add(this.block.rootClass);
128+
// let common = [relative.block.rootClass, ...relative.block.rootClass.resolveInheritance()].filter(b => blockChain.has(b));
129+
// return common.length ? common[0] as Style<Child> : null;
130+
// }
131+
120132
/**
121133
* Given a parent that is a base class of this style, retrieve this style's
122134
* base style from it, if it exists. This method does not traverse into base styles.
@@ -141,6 +153,10 @@ export abstract class Inheritable<
141153
return [...this._children.values()];
142154
}
143155

156+
protected childrenMap(): Map<string, Child> {
157+
return new Map(this._children);
158+
}
159+
144160
// TODO: Cache this maybe? Convert entire model to only use hash?...
145161
protected childrenHash(): ObjectDictionary<Child> {
146162
let out = {};
@@ -150,29 +166,10 @@ export abstract class Inheritable<
150166
return out;
151167
}
152168

153-
}
154-
155-
export abstract class Source<
156-
Self extends Inheritable<Self, null, Child>,
157-
Child extends Inheritable<Child, Self, any>
158-
> extends Inheritable<Self, null, Child> {
159-
public parent: null;
160-
protected _children: Map<string, Child>;
161-
}
162-
163-
export abstract class Node<
164-
Self extends Inheritable<Self, Parent, Child>,
165-
Parent extends Inheritable<Parent, any, Self>,
166-
Child extends Inheritable<Child, Self, any>
167-
> extends Inheritable<Self, Parent, Child> {
168-
public parent: Parent;
169-
protected _children: Map<string, Child>;
170-
}
169+
// TypeScript can't figure out that `this` is the `Self` so this private
170+
// method casts it in a few places where it's needed.
171+
private asSelf(): Self {
172+
return <Self><any>this;
173+
}
171174

172-
export abstract class Sink<
173-
Self extends Inheritable<Self, Parent, null>,
174-
Parent extends Inheritable<any, any, Self>
175-
> extends Inheritable<Self, Parent, null> {
176-
public parent: Parent;
177-
protected _children: Map<string, null>;
178175
}

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { MultiMap, TwoKeyMultiMap } from "@opticss/util";
22
import * as propParser from "css-property-parser";
33
import * as postcss from "postcss";
44

5-
import { BLOCK_PROP_NAMES, RESOLVE_RE, SELF_SELECTOR, BlockPath } from "../BlockSyntax";
6-
import { sourceLocation } from "../SourceLocation";
7-
import * as errors from "../errors";
5+
import { BLOCK_PROP_NAMES, RESOLVE_RE, SELF_SELECTOR, BlockPath } from "../../BlockSyntax";
6+
import { isBlockClass, isState, Style } from '../index';
7+
import { sourceLocation } from "../../SourceLocation";
8+
import * as errors from "../../errors";
89

910
import { Style } from "./Block";
1011

@@ -113,6 +114,7 @@ export class RulesetContainer {
113114
if (referenceStr) {
114115

115116
let blockPath = new BlockPath(referenceStr);
117+
console.log(referenceStr);
116118
let other = style.block.lookup(referenceStr);
117119
let otherBlock = style.block.getReferencedBlock(blockPath.block);
118120

@@ -144,7 +146,9 @@ export class RulesetContainer {
144146
);
145147
}
146148

147-
ruleSet.addResolution(decl.prop, other);
149+
if (isBlockClass(other) || isState(other)) {
150+
ruleSet.addResolution(decl.prop, other);
151+
}
148152
}
149153

150154
// If not a resolution, add this as a tracked property on our Ruleset.

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

+12-37
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { Attr } from "@opticss/element-analysis";
22

33
import { RulesetContainer } from './RulesetContainer';
4-
import { OptionsReader } from "../OptionsReader";
5-
import { unionInto } from '../util/unionInto';
4+
import { OptionsReader } from "../../OptionsReader";
5+
import { unionInto } from '../../util/unionInto';
66
import { Inheritable } from "./Inheritable";
77

88
/**
99
* Abstract class that serves as the base for all Styles. Contains basic
1010
* properties and abstract methods that extenders must implement.
1111
*/
1212
export abstract class Style<
13-
Self extends Style<any, any, any>,
14-
Parent extends Inheritable<Parent, any, Self>,
15-
Child extends Inheritable<any, Self, any> | null = null
16-
> extends Inheritable<Self, Parent, Child> {
13+
Self extends Style<Self, Root, Parent, Child>,
14+
Root extends Inheritable<Root, Root, null, any>,
15+
Parent extends Inheritable<Parent, Root, any, Self>,
16+
Child extends Inheritable<any, Root, Self, any> | null
17+
> extends Inheritable<Self, Root, Parent, Child> {
1718

1819
public readonly rulesets: RulesetContainer;
1920

@@ -23,8 +24,8 @@ export abstract class Style<
2324
/**
2425
* Save name, parent container, and create the PropertyContainer for this data object.
2526
*/
26-
constructor(name: string, parent: Parent) {
27-
super(name, parent);
27+
constructor(name: string, parent: Parent, root: Root) {
28+
super(name, parent, root);
2829
this.rulesets = new RulesetContainer();
2930
}
3031

@@ -121,19 +122,6 @@ export abstract class Style<
121122
return inherited;
122123
}
123124

124-
// /**
125-
// * Find the closest common ancestor Block between two Styles
126-
// * TODO: I think there is a more efficient way to do this.
127-
// * @param relative Style to compare ancestry with.
128-
// * @returns The Style's common Block ancestor, or null.
129-
// */
130-
// commonAncestor(relative: Style<Child>): Style<Child> | null {
131-
// let blockChain = new Set(...this.block.rootClass.resolveInheritance()); // lol
132-
// blockChain.add(this.block.rootClass);
133-
// let common = [relative.block.rootClass, ...relative.block.rootClass.resolveInheritance()].filter(b => blockChain.has(b));
134-
// return common.length ? common[0] as Style<Child> : null;
135-
// }
136-
137125
/**
138126
* Debug utility to help log Styles
139127
* @param opts Options for rendering cssClass.
@@ -150,19 +138,6 @@ export abstract class Style<
150138
}
151139
}
152140

153-
export abstract class NodeStyle<
154-
Self extends Style<Self, Parent, Child>,
155-
Parent extends Inheritable<Parent, any, Self>,
156-
Child extends Inheritable<Child, Self, any>
157-
> extends Style<Self, Parent, Child> {
158-
public parent: Parent;
159-
protected _children: Map<string, Child>;
160-
}
161-
162-
export abstract class SinkStyle <
163-
Self extends Style<Self, Parent, null>,
164-
Parent extends Inheritable<Parent, any, Self>
165-
> extends Style<Self, Parent, null> {
166-
public parent: Parent;
167-
protected _children: Map<string, null>;
168-
}
141+
export function isStyle(o: any): o is Style<any, any, any, any> {
142+
return o && o instanceof Style;
143+
}

0 commit comments

Comments
 (0)