Skip to content

Commit c2a3271

Browse files
ramithachriseppstein
authored andcommitted
feat: Convert methods to start recording multiple errors.
1 parent 14c1d31 commit c2a3271

File tree

8 files changed

+192
-78
lines changed

8 files changed

+192
-78
lines changed

packages/@css-blocks/core/src/BlockParser/BlockFactory.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ export class BlockFactory {
3939
blockNames: ObjectDictionary<number>;
4040
parser: BlockParser;
4141
preprocessors: Preprocessors;
42+
faultTolerant: boolean;
4243

4344
private promises: ObjectDictionary<Promise<Block>>;
4445
private blocks: ObjectDictionary<Block>;
4546
private paths: ObjectDictionary<string>;
4647
private preprocessQueue: PromiseQueue<PreprocessJob, ProcessedFile>;
4748

48-
constructor(options: Options, postcssImpl = postcss) {
49+
constructor(options: Options, postcssImpl = postcss, faultTolerant = false) {
4950
this.postcssImpl = postcssImpl;
5051
this.configuration = resolveConfiguration(options);
5152
this.importer = this.configuration.importer;
@@ -58,6 +59,7 @@ export class BlockFactory {
5859
this.preprocessQueue = new PromiseQueue(this.configuration.maxConcurrentCompiles, (item: PreprocessJob) => {
5960
return item.preprocessor(item.filename, item.contents, this.configuration);
6061
});
62+
this.faultTolerant = faultTolerant;
6163
}
6264

6365
reset() {
@@ -75,9 +77,26 @@ export class BlockFactory {
7577
* @returns The Block object promise.
7678
*/
7779
parse(root: postcss.Root, identifier: string, name: string): Promise<Block> {
80+
// TODO: this function is referenced only in tests. Use parseSync if we need
81+
// to catch errors
7882
return this.promises[identifier] = this.parser.parse(root, identifier, name);
7983
}
8084

85+
/**
86+
* Parse a `postcss.Root` into a Block object. Save the Block promise and return it.
87+
* Also assert that the block is valid so that we can catch any errors that
88+
* the block contains
89+
* This function is only used in tests
90+
* @param root The postcss.Root to parse.
91+
* @param identifier A unique identifier for this Block file.
92+
* @param name Default name for the block.
93+
* @returns The Block object promise.
94+
*/
95+
async parseSync(root: postcss.Root, identifier: string, name: string): Promise<Block> {
96+
const block = await this.parser.parse(root, identifier, name);
97+
return this._surfaceBlockErrors(block);
98+
}
99+
81100
/**
82101
* In some cases (like when using preprocessors with native bindings), it may
83102
* be necessary to wait until the block factory has completed current
@@ -130,6 +149,10 @@ export class BlockFactory {
130149

131150
// Ensure this block name is unique.
132151
block.setName(this.getUniqueBlockName(block.name));
152+
153+
// if the block has any errors, surface them here
154+
this._surfaceBlockErrors(block);
155+
133156
return this.blocks[block.identifier] = block;
134157
})
135158
.catch((error) => {
@@ -146,6 +169,19 @@ export class BlockFactory {
146169
});
147170
}
148171

172+
/**
173+
* Depending on whether the blockFactory is fault tolerant or not, it either
174+
* surfaces the errors or swallows them and reexports the block interface
175+
* @param block the block to check for errors
176+
*/
177+
_surfaceBlockErrors(block: Block): Block {
178+
if (this.faultTolerant) {
179+
return block;
180+
} else {
181+
return block.assertValid();
182+
}
183+
}
184+
149185
/**
150186
* Parse the file into a `Block`.
151187
**/
@@ -198,6 +234,7 @@ export class BlockFactory {
198234
originalSyntax: file.syntax,
199235
dependencies: preprocessResult.dependencies || [],
200236
};
237+
201238
return this.parser.parseSource(source);
202239
}
203240

packages/@css-blocks/core/src/BlockParser/features/construct-block.ts

+28-25
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ export async function constructBlock(configuration: Configuration, root: postcss
6666
let keySel = iSel.key;
6767
let sel: CompoundSelector | undefined = iSel.selector;
6868

69-
// Assert this selector is well formed according to CSS Blocks' selector rules.
69+
// Assert this selector is well formed according to CSS Blocks' selector
70+
// rules.
7071
assertValidSelector(configuration, block, rule, iSel, file);
72+
// this should list all the errors
73+
// block.assertValid();
7174

7275
// For each `CompoundSelector` in this rule, configure the `Block` object
7376
// depending on the BlockType.
@@ -137,9 +140,9 @@ function assertValidSelector(configuration: Configuration, block: Block, rule: p
137140
// Verify our key selector targets a block object, but not one from an another block.
138141
let keyObject = assertBlockObject(configuration, block, selector.key, rule, file);
139142
if (keyObject.blockName) {
140-
throw new errors.InvalidBlockSyntax(
143+
block.addError(new errors.InvalidBlockSyntax(
141144
`Cannot style values from other blocks: ${rule.selector}`,
142-
range(configuration, block.stylesheet, file, rule, keyObject.node));
145+
range(configuration, block.stylesheet, file, rule, keyObject.node)));
143146
}
144147

145148
// Fetch and validate our first `CompoundSelector`
@@ -167,45 +170,45 @@ function assertValidSelector(configuration: Configuration, block: Block, rule: p
167170
// Don't allow weird combinators like the column combinator (`||`)
168171
// or the attribute target selector (e.g. `/for/`)
169172
if (!LEGAL_COMBINATORS.has(combinator)) {
170-
throw new errors.InvalidBlockSyntax(
173+
block.addError(new errors.InvalidBlockSyntax(
171174
`Illegal Combinator '${combinator}': ${rule.selector}`,
172175
range(configuration, block.stylesheet, file, rule, currentCompoundSel.next.combinator),
173-
);
176+
));
174177
}
175178

176179
// Class level objects cannot be ancestors of root level objects
177180
if (isClassLevelObject(currentObject) && nextLevelIsRoot && SIBLING_COMBINATORS.has(combinator)) {
178-
throw new errors.InvalidBlockSyntax(
181+
block.addError(new errors.InvalidBlockSyntax(
179182
`A class is never a sibling of a ${blockTypeName(nextObject.blockType)}: ${rule.selector}`,
180183
range(configuration, block.stylesheet, file, rule, selector.selector.nodes[0]),
181-
);
184+
));
182185
}
183186

184187
// Once you go to the class level there's no combinator that gets you back to the root level
185188
if (foundClassLevel && nextLevelIsRoot) {
186-
throw new errors.InvalidBlockSyntax(
189+
block.addError(new errors.InvalidBlockSyntax(
187190
`Illegal scoping of a ${blockTypeName(currentObject.blockType)}: ${rule.selector}`,
188191
range(configuration, block.stylesheet, file, rule, currentCompoundSel.next.combinator),
189-
);
192+
));
190193
}
191194

192195
// You can't reference a new root level object once you introduce descend the hierarchy
193196
if (foundRootLevel && nextLevelIsRoot && foundCombinators.some(c => HIERARCHICAL_COMBINATORS.has(c))) {
194197
// unless it's only referencing the same object.
195198
if (!foundObjects.every(f => f.node.toString() === nextObject.node.toString())) {
196-
throw new errors.InvalidBlockSyntax(
199+
block.addError(new errors.InvalidBlockSyntax(
197200
`Illegal scoping of a ${blockTypeName(currentObject.blockType)}: ${rule.selector}`,
198201
range(configuration, block.stylesheet, file, rule, currentCompoundSel.next.combinator),
199-
);
202+
));
200203
}
201204
}
202205

203206
// class-level and root-level objects cannot be siblings.
204207
if (isRootLevelObject(currentObject) && nextLevelIsClass && SIBLING_COMBINATORS.has(combinator)) {
205-
throw new errors.InvalidBlockSyntax(
208+
block.addError(new errors.InvalidBlockSyntax(
206209
`A ${blockTypeName(nextObject.blockType)} cannot be a sibling with a ${blockTypeName(currentObject.blockType)}: ${rule.selector}`,
207210
range(configuration, block.stylesheet, file, rule, currentCompoundSel.next.combinator),
208-
);
211+
));
209212
}
210213

211214
// Class-level objects cannot be combined with each other. only with themselves.
@@ -214,15 +217,15 @@ function assertValidSelector(configuration: Configuration, block: Block, rule: p
214217
if (conflictObj) {
215218
// slightly better error verbiage for objects of the same type.
216219
if (conflictObj.blockType === nextObject.blockType) {
217-
throw new errors.InvalidBlockSyntax(
220+
block.addError(new errors.InvalidBlockSyntax(
218221
`Distinct ${blockTypeName(conflictObj.blockType, { plural: true })} cannot be combined: ${rule.selector}`,
219222
range(configuration, block.stylesheet, file, rule, nextObject.node),
220-
);
223+
));
221224
} else {
222-
throw new errors.InvalidBlockSyntax(
225+
block.addError(new errors.InvalidBlockSyntax(
223226
`Cannot combine a ${blockTypeName(conflictObj.blockType)} with a ${blockTypeName(nextObject.blockType)}}: ${rule.selector}`,
224227
range(configuration, block.stylesheet, file, rule, nextObject.node),
225-
);
228+
));
226229
}
227230
}
228231
}
@@ -302,10 +305,10 @@ function assertBlockObject(configuration: Configuration, block: Block, sel: Comp
302305
else if (isAttributeNode(n)) {
303306
// Assert this state node uses a valid operator if specifying a value.
304307
if (n.value && n.operator !== "=") {
305-
throw new errors.InvalidBlockSyntax(
308+
block.addError(new errors.InvalidBlockSyntax(
306309
`A state with a value must use the = operator (found ${n.operator} instead).`,
307310
range(configuration, block.stylesheet, file, rule, n),
308-
);
311+
));
309312
}
310313
if (n.attribute === "scope") {
311314
throw new errors.InvalidBlockSyntax(
@@ -344,19 +347,19 @@ function assertBlockObject(configuration: Configuration, block: Block, sel: Comp
344347
};
345348
} else {
346349
if (found.blockType === BlockType.root) {
347-
throw new errors.InvalidBlockSyntax(
350+
block.addError(new errors.InvalidBlockSyntax(
348351
`${n} cannot be on the same element as ${found.node}: ${rule.selector}`,
349-
range(configuration, block.stylesheet, file, rule, sel.nodes[0]));
352+
range(configuration, block.stylesheet, file, rule, sel.nodes[0])));
350353
} else if (found.blockType === BlockType.class) {
351354
if (n.toString() !== found.node.toString()) {
352-
throw new errors.InvalidBlockSyntax(
355+
block.addError(new errors.InvalidBlockSyntax(
353356
`Two distinct classes cannot be selected on the same element: ${rule.selector}`,
354-
range(configuration, block.stylesheet, file, rule, n));
357+
range(configuration, block.stylesheet, file, rule, n)));
355358
}
356359
} else if (found.blockType === BlockType.classAttribute || found.blockType === BlockType.attribute) {
357-
throw new errors.InvalidBlockSyntax(
360+
block.addError(new errors.InvalidBlockSyntax(
358361
`The class must precede the state: ${rule.selector}`,
359-
range(configuration, block.stylesheet, file, rule, sel.nodes[0]));
362+
range(configuration, block.stylesheet, file, rule, sel.nodes[0])));
360363
}
361364
}
362365
}

packages/@css-blocks/core/src/BlockTree/Block.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,11 @@ export class Block
198198
* Checks for errors on the block
199199
* @returns true if the block is valid else throws the errors on the block
200200
*/
201-
assertValid(): Boolean {
201+
assertValid(): Block {
202202
if (this._blockErrors.length) {
203203
throw new MultipleCssBlockErrors(this._blockErrors);
204204
}
205-
return true;
205+
return this;
206206
}
207207

208208
/**

packages/@css-blocks/core/src/errors.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,22 @@ export class BlockPathError extends CssBlockError {
132132
* Acts as a collection of CssBlockErrors along with utility methods to add and
133133
* clear errors
134134
*/
135-
export class MultipleCssBlockErrors extends Error {
135+
export class MultipleCssBlockErrors extends CssBlockError {
136+
static prefix = "MultipleCssBlocksError";
136137
private _errors: CssBlockError[] = [];
137-
constructor(errors: CssBlockError[]) {
138-
super();
138+
139+
constructor(errors: CssBlockError[], location?: ErrorLocation, details?: string) {
140+
super(MultipleCssBlockErrors.prefix, location);
139141
this._errors = errors;
142+
if (details) { this.message += `\n${details}`; }
140143
}
141-
add(error:CssBlockError) {
144+
145+
add(error: CssBlockError) {
142146
this._errors.push(error);
143147
}
148+
get errors(): CssBlockError[] {
149+
return this._errors;
150+
}
144151
clear() {
145152
this._errors = [];
146153
}

0 commit comments

Comments
 (0)