Skip to content

Commit 5a94c3a

Browse files
committed
chore: Code cleanup.
- Added comments and jsdoc annotations. - Template Analyzers use `startElement` and `endElement` APIs. - Rewriters' element analyzers now use special syntax, easily refactored later when we address #84. - Old `addElement` method now gone from base Analysys object. - Annotate more broccoli-css-blocks future features in comments.
1 parent 2815a25 commit 5a94c3a

File tree

8 files changed

+127
-105
lines changed

8 files changed

+127
-105
lines changed

packages/css-blocks/src/Analyzer/Analysis.ts

+63-54
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import {
1212
TemplateIntegrationOptions,
1313
TemplateTypes,
1414
} from "@opticss/template-api";
15-
import {
16-
ObjectDictionary,
17-
objectValues,
18-
} from "@opticss/util";
15+
import { ObjectDictionary, objectValues } from "@opticss/util";
1916
import { IdentGenerator } from "opticss";
2017

2118
import { BlockFactory } from "../BlockParser";
@@ -28,7 +25,7 @@ import { TemplateValidator, TemplateValidatorOptions } from "./validations";
2825

2926
/**
3027
* This interface defines a JSON friendly serialization
31-
* of a {TemplateAnalysis}.
28+
* of an {Analysis}.
3229
*/
3330
export interface SerializedAnalysis {
3431
template: SerializedTemplateInfo<keyof TemplateTypes>;
@@ -39,8 +36,8 @@ export interface SerializedAnalysis {
3936
}
4037

4138
/**
42-
* A TemplateAnalysis performs book keeping and ensures internal consistency of the block objects referenced
43-
* within a template. It is designed to be used as part of an AST walk over a template.
39+
* An Analysis performs book keeping and ensures internal consistency of the block objects referenced
40+
* within a single template. It is designed to be used as part of an AST walk over a template.
4441
*
4542
* 1. Call [[startElement startElement()]] at the beginning of an new html element.
4643
* 2. Call [[addStyle addStyle(style, isDynamic)]] for all the styles used on the current html element.
@@ -93,22 +90,39 @@ export class Analysis<K extends keyof TemplateTypes> {
9390

9491
/**
9592
* Return the number of blocks discovered in this Template.
96-
*/
93+
*/
9794
blockCount(): number { return Object.keys(this.blocks).length; }
9895

9996
/**
10097
* Convenience setter for adding a block to the template scope.
10198
*/
102-
addBlock(name: string, block: Block) { this.blocks[name] = block; }
99+
addBlock(name: string, block: Block): void { this.blocks[name] = block; }
103100

104101
/**
105102
* Convenience getter for fetching a block from the template scope.
106103
*/
107-
getBlock(name: string): Block { return this.blocks[name]; }
104+
getBlock(name: string): Block | undefined { return this.blocks[name]; }
105+
106+
/**
107+
* Return the number of elements discovered in this Analysis.
108+
*/
109+
elementCount(): number { return this.elements.size; }
110+
111+
/**
112+
* Get the nth element discovered in this Analysis.
113+
*/
114+
getElement<BooleanExpression, StringExpression, TernaryExpression>(idx: number): ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression> {
115+
let mapIter = this.elements.entries();
116+
let el = mapIter.next().value;
117+
for (let i = 0; i < idx; i++) {
118+
el = mapIter.next().value;
119+
}
120+
return el[1];
121+
}
108122

109123
/**
110124
* Return the number of styles discovered in this Analysis' Template.
111-
* This is slow.
125+
* TODO: This is slow. We can pre-compute this as elements are added.
112126
*/
113127
styleCount(): number {
114128
let c = 0;
@@ -124,25 +138,11 @@ export class Analysis<K extends keyof TemplateTypes> {
124138
}
125139

126140
/**
127-
* Return the number of elements discovered in this Analysis.
128-
*/
129-
elementCount(): number { return this.elements.size; }
130-
131-
/**
132-
* Get the nth element discovered in this Analysis.
141+
* Get either the owner Analyzer's optimization options, or a default set of options.
133142
*/
134-
getElement<BooleanExpression, StringExpression, TernaryExpression>(idx: number): ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression> {
135-
let mapIter = this.elements.entries();
136-
let el = mapIter.next().value;
137-
for (let i = 0; i < idx; i++) {
138-
el = mapIter.next().value;
139-
}
140-
return el[1];
141-
}
142-
143143
optimizationOptions(): TemplateIntegrationOptions {
144-
// TODO: take this as an argument from the template integration.
145-
return {
144+
// TODO: Optimization options must be handled / propagated better.
145+
return this.parent ? this.parent.optimizationOptions : {
146146
rewriteIdents: {
147147
id: false,
148148
class: true,
@@ -164,6 +164,7 @@ export class Analysis<K extends keyof TemplateTypes> {
164164
}
165165

166166
/**
167+
* Returns the local name of the provided in this Analysis' template.
167168
* @param block The block for which the local name should be returned.
168169
* @return The local name of the given block.
169170
*/
@@ -184,46 +185,42 @@ export class Analysis<K extends keyof TemplateTypes> {
184185
return null;
185186
}
186187

188+
/**
189+
* Get a new {ElementAnalysis} object to track an individual element's {Style}
190+
* consumption in this {Analysis}' template. Every {ElementAnalysis} returned from
191+
* `Analysis.startElement` must be passed to `Analysis.endElement` before startElement
192+
* is called again.
193+
* @param location The {SourceLocation} of this element in the template.
194+
* @param tagName Optional. The tag name of the element being represented by this {ElementAnalysis}.
195+
* @returns A new {ElementAnalysis}.
196+
*/
187197
startElement<BooleanExpression, StringExpression, TernaryExpression>(location: SourceLocation | SourcePosition, tagName?: string): ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression> {
188-
if (isSourcePosition(location)) {
189-
location = {start: location};
190-
}
198+
if (isSourcePosition(location)) { location = {start: location}; }
191199
if (this.currentElement) {
192200
throw new Error("Internal Error: failure to call endElement before previous call to startElement.");
193201
}
194202
this.currentElement = new ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>(location, tagName, this.idGenerator.nextIdent());
195203
return this.currentElement;
196204
}
197205

198-
endElement<BooleanExpression, StringExpression, TernaryExpression>(element: ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>, endPosition?: SourcePosition) {
206+
/**
207+
* Finish an {ElementAnalysis} object returned from `Analysis.startElement` to
208+
* the end location in source and save {Style} usage on the parent {Analysis}.
209+
* @param element The {ElementAnalysis} we are finishing.
210+
* @param endPosition Optional. The location in code where this element tag is closed..
211+
*/
212+
endElement<BooleanExpression, StringExpression, TernaryExpression>(element: ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>, endPosition?: SourcePosition): void {
199213
if (this.currentElement !== element) {
200214
throw new Error("Element is not the current element.");
201215
}
202-
if (endPosition) {
203-
element.sourceLocation.end = endPosition;
204-
}
205-
this.addElement(element);
206-
this.currentElement = undefined;
207-
}
208-
209-
private addFilename(pos: SourcePosition | undefined) {
210-
if (pos && !pos.filename) {
211-
pos.filename = this.template.identifier;
212-
}
213-
}
214-
215-
addElement<BooleanExpression, StringExpression, TernaryExpression>(element: ElementAnalysis<BooleanExpression, StringExpression, TernaryExpression>) {
216-
if (!element.id) {
217-
element.id = this.idGenerator.nextIdent();
218-
}
216+
if (endPosition) { element.sourceLocation.end = endPosition; }
217+
if (!element.id) { element.id = this.idGenerator.nextIdent(); }
219218
if (this.elements.get(element.id)) {
220219
throw new Error(`Internal Error: an element with id = ${element.id} already exists in this analysis`);
221220
}
222-
this.addFilename(element.sourceLocation.start);
223-
this.addFilename(element.sourceLocation.end);
224-
if (!element.sealed) {
225-
element.seal();
226-
}
221+
this.ensureFilename(element.sourceLocation.start);
222+
this.ensureFilename(element.sourceLocation.end);
223+
if (!element.sealed) { element.seal(); }
227224
this.validator.validate(this, element);
228225
this.elements.set(element.id, element);
229226
if (this.parent) {
@@ -234,6 +231,18 @@ export class Analysis<K extends keyof TemplateTypes> {
234231
this.parent.saveDynamicStyle(s, this);
235232
}
236233
}
234+
this.currentElement = undefined;
235+
}
236+
237+
/**
238+
* Given a {SourcePosition}, ensure that the file name is present. If not,
239+
* add the template identifier.
240+
* @param post The {SourcePosition} we are validating.
241+
*/
242+
private ensureFilename(pos: SourcePosition | undefined) {
243+
if (pos && !pos.filename) {
244+
pos.filename = this.template.identifier;
245+
}
237246
}
238247

239248
/**

packages/glimmer-templates/src/Analyzer.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,12 @@ export class GlimmerAnalyzer extends Analyzer<TEMPLATE_TYPE> {
102102
let localBlockNames = Object.keys(analysis.blocks).map(n => n === "" ? "<default>" : n);
103103
self.debug(`Analyzing ${componentName}. ${localBlockNames.length} blocks in scope: ${localBlockNames.join(", ")}.`);
104104

105-
let elementAnalyzer = new ElementAnalyzer(block, template, this.options);
105+
let elementAnalyzer = new ElementAnalyzer(analysis, this.options);
106106
traverse(ast, {
107107
ElementNode(node) {
108108
elementCount++;
109109
let atRootElement = (elementCount === 1);
110110
let element = elementAnalyzer.analyze(node, atRootElement);
111-
analysis.addElement(element);
112111
if (self.debug.enabled) self.debug("Element analyzed:", element.forOptimizer(self.options).toString());
113112
},
114113
});

packages/glimmer-templates/src/ElementAnalyzer.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { assertNever } from "@opticss/util";
44
import { AttrValue, Block, BlockClass, DynamicClasses, ElementAnalysis, ResolvedConfiguration as CSSBlocksConfiguration } from "css-blocks";
55
import * as debugGenerator from "debug";
66

7+
import { GlimmerAnalysis } from "./Analyzer";
78
import { ResolvedFile } from "./GlimmerProject";
89
import { cssBlockError } from "./utils";
910

@@ -29,17 +30,21 @@ const STYLE_UNLESS: "style-unless" = "style-unless";
2930
const debug = debugGenerator("css-blocks:glimmer:analyzer");
3031

3132
export class ElementAnalyzer {
33+
analysis: GlimmerAnalysis;
34+
block: Block;
3235
template: ResolvedFile;
3336
cssBlocksOpts: CSSBlocksConfiguration;
34-
block: Block;
35-
constructor(block: Block, template: ResolvedFile, cssBlocksOpts: CSSBlocksConfiguration) {
36-
this.block = block;
37-
this.template = template;
37+
38+
constructor(analysis: GlimmerAnalysis, cssBlocksOpts: CSSBlocksConfiguration) {
39+
this.analysis = analysis;
40+
this.block = analysis.blocks[""];
41+
this.template = analysis.template;
3842
this.cssBlocksOpts = cssBlocksOpts;
3943
}
4044
analyze(node: AST.ElementNode, atRootElement: boolean): AnalysisElement {
41-
let element = new ElementAnalysis<null, null, null>(nodeLocation(node), node.tag);
45+
let element = this.analysis.startElement<null, null, null>(nodeLocation(node), node.tag);
4246
this._analyze(node, atRootElement, {element, storeConditionals: false});
47+
this.analysis.endElement(element);
4348
return element;
4449
}
4550
analyzeForRewrite(node: AST.ElementNode, atRootElement: boolean): TemplateElement {

packages/glimmer-templates/src/Rewriter.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class GlimmerRewriter implements ASTPlugin {
4848
this.styleMapping = styleMapping;
4949
this.cssBlocksOpts = resolveConfiguration(cssBlocksOpts);
5050
this.elementCount = 0;
51-
this.elementAnalyzer = new ElementAnalyzer(this.block, this.template, this.cssBlocksOpts);
51+
this.elementAnalyzer = new ElementAnalyzer(this.analysis, this.cssBlocksOpts);
5252
}
5353

5454
debug(message: string, ...args: whatever[]): void {
@@ -65,6 +65,10 @@ export class GlimmerRewriter implements ASTPlugin {
6565
ElementNode(node: AST.ElementNode) {
6666
this.elementCount++;
6767
let atRootElement = (this.elementCount === 1);
68+
// TODO: We use this to re-analyze elements in the rewriter.
69+
// We've already done this work and should be able to
70+
// re-use the data! Unfortunately, there are problems...
71+
// See: https://github.com/css-blocks/css-blocks/issues/84
6872
let element = this.elementAnalyzer.analyzeForRewrite(node, atRootElement);
6973
this.debug(element.forOptimizer(this.cssBlocksOpts)[0].toString());
7074
let rewrite = this.styleMapping.simpleRewriteMapping(element);

0 commit comments

Comments
 (0)