Skip to content

Commit

Permalink
fix(css-blocks): Updated with more of Chris' comments and use the ren…
Browse files Browse the repository at this point in the history
…amed MultiMap methods
  • Loading branch information
amiller-gh committed Feb 16, 2018
1 parent 29d2316 commit 2f04eb1
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 48 deletions.
4 changes: 2 additions & 2 deletions packages/css-blocks/src/Block/RulesetContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,15 @@ export class RulesetContainer {
}
return new Set(res);
}
return new Set([...this.concerns.subkeys(pseudo)]);
return new Set([...this.concerns.subKeys(pseudo)]);
}

/**
* Retrieve all pseudo elements which were found to have properties for this Style.
* @returns A set of pseudo element names.
*/
getPseudos(): Set<string> {
return new Set([...this.concerns.subkeys()]);
return new Set([...this.concerns.primaryKeys()]);
}

/**
Expand Down
84 changes: 46 additions & 38 deletions packages/css-blocks/src/BlockSyntax/BlockPath.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { BlockPathError, ErrorLocation } from '../errors';
import { CLASS_NAME_IDENT as CSS_IDENT } from "./blockSyntax";
import { StateInfo } from "../BlockParser";

export interface BlockToken {
interface BlockToken {
type: 'block';
name: string;
}

export interface ClassToken {
interface ClassToken {
type: 'class';
name: string;
}
Expand All @@ -18,20 +19,23 @@ export interface StateToken {
value?: string;
}

export type Token = BlockToken | ClassToken | StateToken;
type Token = BlockToken | ClassToken | StateToken;

export const isBlock = (token?: Token): token is BlockToken => !!token && token.type === 'block';
export const isClass = (token?: Token): token is ClassToken => !!token && token.type === 'class';
export const isState = (token?: Token): token is StateToken => !!token && token.type === 'state';
export const hasName = (token?: Token): boolean => !!token && !!token.name;
export const hasNamespace = (token?: Token): boolean => isState(token) && !!token.namespace;
export const hasValue = (token?: Token): boolean => isState(token) && !!token.value;
const isBlock = (token?: Token): token is BlockToken => !!token && token.type === 'block';
const isClass = (token?: Token): token is ClassToken => !!token && token.type === 'class';
const isState = (token?: Token): token is StateToken => !!token && token.type === 'state';
const hasName = (token?: Token): boolean => !!token && !!token.name;
const hasNamespace = (token?: Token): boolean => isState(token) && !!token.namespace;

const STATE_BEGIN = "[";
const STATE_END = "]";
const CLASS_BEGIN = ".";
const NAMESPACE_END = "|";
const VALUE_START = "=";
const SINGLE_QUOTE = `'`;
const DOUBLE_QUOTE = `"`;
const WHITESPACE_REGEXP = /\s/g;
const SEPARATORS = new Set([CLASS_BEGIN, STATE_BEGIN]);

export const ERRORS = {
whitespace: "Whitespace is only allowed in quoted state values",
Expand All @@ -56,8 +60,6 @@ function stringify(tokens: Token[]): string {
return out;
}

const SEPARATORS = new Set([".", "["]);

/**
* Simple utility to easily walk over string data one character at a time.
*/
Expand Down Expand Up @@ -99,7 +101,7 @@ export class BlockPath {
private _class: ClassToken;
private _state: StateToken;

protected tokens: Token[] = [];
private tokens: Token[] = [];

/**
* Throw a new BlockPathError with the given message.
Expand Down Expand Up @@ -147,10 +149,10 @@ export class BlockPath {

while (char = walker.next()) {

switch (char) {
switch (true) {

// If a period, we've finished the previous token and are now building a class name.
case CLASS_BEGIN:
case char === CLASS_BEGIN:
if (isState(token)) { this.throw(ERRORS.illegalCharInState(char)); }
token.name = working;
this.addToken(token);
Expand All @@ -159,7 +161,7 @@ export class BlockPath {
break;

// If the beginning of a state, we've finished the previous token and are now building a state.
case STATE_BEGIN:
case char === STATE_BEGIN:
if (isState(token)) { this.throw(ERRORS.illegalCharInState(char)); }
token.name = working;
this.addToken(token);
Expand All @@ -168,37 +170,37 @@ export class BlockPath {
break;

// If the end of a state, set the state part we've been working on and finish.
case STATE_END:
case char === STATE_END:
if (!isState(token)) { return this.throw(ERRORS.illegalCharNotInState(char)); }
token.name ? (token.value = working) : (token.name = working);
this.addToken(token);
working = "";

// The character immediately following a `STATE_END` *must* be another `SEPERATOR`
// The character immediately following a `STATE_END` *must* be another `SEPARATORS`
// Depending on the next value, seed our token input
let next = walker.next();
if (next && !SEPARATORS.has(next)) { this.throw(ERRORS.expectsSepInsteadRec(next)); }
token = (next === STATE_BEGIN) ? { type: 'state', namespace: '', name: '' } : { type: 'class', name: '' };
break;

// When we find a namespace terminator, set the namespace property of the state token we're working on.
case NAMESPACE_END:
case char === NAMESPACE_END:
if (!isState(token)) { return this.throw(ERRORS.illegalCharNotInState(char)); }
token.namespace = working;
working = "";
break;

// If the start of the value section of a state part, set the name we've been working on and move on.
case VALUE_START:
case char === VALUE_START:
if (!isState(token)) { this.throw(ERRORS.illegalCharNotInState(char)); }
if (!working) { this.throw(ERRORS.noname); }
token.name = working;
working = "";
break;

// If the opening quote of the value section of a state part, greedily consume everything between quotes.
case `"`:
case `'`:
case char === SINGLE_QUOTE:
case char === DOUBLE_QUOTE:
if (!isState(token)) { return this.throw(ERRORS.illegalCharNotInState(char)); }
working = walker.consume(char);
if (walker.peek() !== char) { this.throw(ERRORS.mismatchedQuote); }
Expand All @@ -208,7 +210,7 @@ export class BlockPath {
// We should never encounter whitespace in this switch statement.
// The only place whitespace is allowed is between quotes, which
// is handled above.
case ` `:
case WHITESPACE_REGEXP.test(char):
this.throw(ERRORS.whitespace);

// If none of the above special characters, add this character to our working string.
Expand Down Expand Up @@ -239,45 +241,51 @@ export class BlockPath {
* @param path The BlockPath input data.
* @param location An optional ErrorLocation object for more detailed error reporting.
*/
constructor(path: string | BlockPath | Token[], location?: ErrorLocation) {
constructor(path: string | BlockPath, location?: ErrorLocation) {
this._location = location;
if (path instanceof BlockPath) {
this.tokens = path.tokens;
}
else if (path instanceof Array) {
this.tokens = path;
}
else {
this.tokenize(path);
}
}

/**
* Get the parsed block name of this Block Path
*/
get block(): string {
return this._block.name;
private static from(tokens: Token[]) {
let path = new BlockPath('');
path.tokens = tokens;
return path;
}

/**
* Get the parsed Style path of this Block Path
*/
get path(): string {
return stringify(this.tokens.slice(1)) || '.root';
return stringify(this.tokens.slice(1));
}

/**
* Get the parsed block name of this Block Path
*/
get block(): string {
return this._block ? this._block.name : "";
}

/**
* Get the parsed class name of this Block Path
*/
get class(): string {
return this._class && this._class.name || 'root';
return this._class && this._class.name || "root";
}

/**
* Get the parsed state name of this Block Path
* Get the parsed state name of this Block Path and return the `StateInfo`
*/
get state(): StateToken | undefined {
return this._state;
get state(): StateInfo | undefined {
return {
group: this._state.value ? this._state.name : undefined,
name: this._state.value || this._state.name,
};
}

/**
Expand All @@ -291,14 +299,14 @@ export class BlockPath {
* Return a new BlockPath without the parent-most token.
*/
childPath() {
return new BlockPath(this.tokens.slice(1));
return BlockPath.from(this.tokens.slice(1));
}

/**
* Return a new BlockPath without the child-most token.
*/
parentPath() {
return new BlockPath(this.tokens.slice(0, -1));
return BlockPath.from(this.tokens.slice(0, -1));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ const propertyConflictValidator: Validator = (elAnalysis, _templateAnalysis, err
});
}

allConditions.merge(truthyConditions);
allConditions.merge(falsyConditions);
allConditions.setAll(truthyConditions);
allConditions.setAll(falsyConditions);

});

Expand All @@ -178,7 +178,7 @@ const propertyConflictValidator: Validator = (elAnalysis, _templateAnalysis, err
evaluate(state, allConditions, conflicts);
add(stateConditions, state);
});
allConditions.merge(stateConditions);
allConditions.setAll(stateConditions);
}

else if (isBooleanState(condition)) {
Expand Down
18 changes: 13 additions & 5 deletions packages/css-blocks/test/BlockSyntax/block-path-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export class BlockPathTests {
@test "parentPath returns the parent's path"() {
let path = new BlockPath("block.class[state|my-state]");
assert.equal(path.parentPath().toString(), "block.class");
path = new BlockPath(".class[state|my-state]");
assert.equal(path.parentPath().toString(), ".class");
path = new BlockPath("block.class");
assert.equal(path.parentPath().toString(), "block");
path = new BlockPath("block[state|my-state]");
Expand All @@ -101,6 +103,8 @@ export class BlockPathTests {
@test "childPath returns the child's path"() {
let path = new BlockPath("block.class[state|my-state]");
assert.equal(path.childPath().toString(), ".class[state|my-state]");
path = new BlockPath(".class[state|my-state]");
assert.equal(path.childPath().toString(), "[state|my-state]");
path = new BlockPath("block.class");
assert.equal(path.childPath().toString(), ".class");
path = new BlockPath("block[state|my-state]");
Expand All @@ -112,16 +116,16 @@ export class BlockPathTests {
assert.equal(path.block, "block");
assert.equal(path.path, ".class[state|my-state]");
assert.equal(path.class, "class");
assert.equal(path.state && path.state.namespace, "state");
// assert.equal(path.state && path.state.namespace, "state");
assert.equal(path.state && path.state.name, "my-state");

path = new BlockPath("block[state|my-state=foobar]");
assert.equal(path.block, "block");
assert.equal(path.path, `[state|my-state="foobar"]`);
assert.equal(path.class, "root");
assert.equal(path.state && path.state.namespace, "state");
assert.equal(path.state && path.state.name, "my-state");
assert.equal(path.state && path.state.value, "foobar");
// assert.equal(path.state && path.state.namespace, "state");
assert.equal(path.state && path.state.group, "my-state");
assert.equal(path.state && path.state.name, "foobar");
}

@test "mismatched State value quotes throw"() {
Expand Down Expand Up @@ -155,9 +159,12 @@ export class BlockPathTests {
assert.throws(() => {
let path = new BlockPath(`[state|my-state=my value]`);
}, ERRORS.whitespace);
assert.throws(() => {
let path = new BlockPath(`[state|my-state=my\nvalue]`);
}, ERRORS.whitespace);
}

@test "state are required to have namespaces"() {
@test "states are required to have namespaces"() {
let path = new BlockPath(`[namespace|name=value]`);

assert.throws(() => {
Expand Down Expand Up @@ -241,6 +248,7 @@ export class BlockPathTests {

// Quoted values may have illegal strings
let path = new BlockPath(`block[name|foo="1bar"]`);
assert.equal(path.state && path.state.name, "1bar");
}

@test @skip "escaped illegal characters in identifiers are processed"() {
Expand Down

0 comments on commit 2f04eb1

Please sign in to comment.