Skip to content

Commit 6b3e3f7

Browse files
ramithachriseppstein
authored andcommitted
feat: Converting export and import blocks to use multple errors.
1 parent c9c790e commit 6b3e3f7

File tree

8 files changed

+306
-235
lines changed

8 files changed

+306
-235
lines changed

packages/@css-blocks/core/src/BlockParser/features/assert-foreign-global-attribute.ts

+23-23
Original file line numberDiff line numberDiff line change
@@ -46,32 +46,32 @@ export async function assertForeignGlobalAttribute(configuration: Configuration,
4646

4747
// If selecting something other than an attribute on external attribute, throw.
4848
if (!isAttributeNode(node)) {
49-
throw new errors.InvalidBlockSyntax(
50-
`Illegal global state selector: ${rule.selector}`,
51-
range(configuration, block.stylesheet, file, rule, node));
52-
}
53-
54-
// If referenced block does not exist, throw.
55-
let otherBlock = block.getReferencedBlock(blockName.namespace);
56-
if (!otherBlock) {
57-
throw new errors.InvalidBlockSyntax(
58-
`No Block named "${blockName.value}" found in scope: ${rule.selector}`,
59-
range(configuration, block.stylesheet, file, rule, node));
60-
}
61-
62-
// If state referenced does not exist on external block, throw
63-
let otherAttr = otherBlock.rootClass.getAttributeValue(toAttrToken(node));
64-
if (!otherAttr) {
6549
block.addError(new errors.InvalidBlockSyntax(
66-
`No state ${node.toString()} found in : ${rule.selector}`,
50+
`Illegal global state selector: ${rule.selector}`,
6751
range(configuration, block.stylesheet, file, rule, node)));
68-
}
52+
} else {
53+
// If referenced block does not exist, throw.
54+
let otherBlock = block.getReferencedBlock(blockName.namespace);
55+
if (!otherBlock) {
56+
block.addError(new errors.InvalidBlockSyntax(
57+
`No Block named "${blockName.value}" found in scope: ${rule.selector}`,
58+
range(configuration, block.stylesheet, file, rule, node)));
59+
} else {
60+
// If state referenced does not exist on external block, throw
61+
let otherAttr = otherBlock.rootClass.getAttributeValue(toAttrToken(node));
62+
if (!otherAttr) {
63+
block.addError(new errors.InvalidBlockSyntax(
64+
`No state ${node.toString()} found in : ${rule.selector}`,
65+
range(configuration, block.stylesheet, file, rule, node)));
66+
}
6967

70-
// If external state is not set as global, throw.
71-
else if (!otherAttr.isGlobal) {
72-
throw new errors.InvalidBlockSyntax(
73-
`${node.toString()} is not global: ${rule.selector}`,
74-
range(configuration, block.stylesheet, file, rule, node));
68+
// If external state is not set as global, throw.
69+
else if (!otherAttr.isGlobal) {
70+
block.addError(new errors.InvalidBlockSyntax(
71+
`${node.toString()} is not global: ${rule.selector}`,
72+
range(configuration, block.stylesheet, file, rule, node)));
73+
}
74+
}
7575
}
7676
}
7777
});

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

+74-73
Original file line numberDiff line numberDiff line change
@@ -21,87 +21,88 @@ export async function exportBlocks(block: Block, factory: BlockFactory, file: st
2121
const remoteNames: Set<string> = new Set();
2222

2323
if (!root) {
24-
throw new errors.InvalidBlockSyntax(`Error finding PostCSS root for block ${block.name}`);
25-
}
26-
27-
// Blocks will always export themselves as the default export.
28-
block.addBlockExport(DEFAULT_EXPORT, block);
29-
30-
// For each `@block` expression, read in the block file, parse and
31-
// push to block references Promise array.
32-
root.walkAtRules(BLOCK_EXPORT, async (atRule: postcss.AtRule) => {
33-
let exports = atRule.params;
34-
35-
let [exportList = "", blockPath = ""] = exports.split(FROM_EXPR);
36-
blockPath = stripQuotes(blockPath);
37-
38-
if (!exportList) {
39-
throw new errors.InvalidBlockSyntax(
40-
`Malformed block export: \`@export ${atRule.params}\``,
41-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
42-
);
43-
}
44-
45-
// Import file, then parse file, then save block reference.
46-
let srcBlockPromise: Promise<Block> = Promise.resolve(block);
47-
if (blockPath) {
48-
srcBlockPromise = factory.getBlockRelative(block.identifier, blockPath);
49-
}
50-
51-
// Validate our imported block name is a valid CSS identifier.
52-
const blockNames = parseBlockNames(exportList, !!blockPath);
53-
const exportPromise = srcBlockPromise.then((srcBlock) => {
54-
for (let remoteName of Object.keys(blockNames)) {
55-
if (remoteNames.has(remoteName)) {
56-
throw new errors.InvalidBlockSyntax(
57-
`Cannot have duplicate Block export of same name: "${remoteName}".`,
24+
// TODO: add a test case that catches this error
25+
block.addError(new errors.InvalidBlockSyntax(`Error finding PostCSS root for block ${block.name}`));
26+
} else {
27+
// Blocks will always export themselves as the default export.
28+
block.addBlockExport(DEFAULT_EXPORT, block);
29+
30+
// For each `@block` expression, read in the block file, parse and
31+
// push to block references Promise array.
32+
root.walkAtRules(BLOCK_EXPORT, async (atRule: postcss.AtRule) => {
33+
let exports = atRule.params;
34+
35+
let [exportList = "", blockPath = ""] = exports.split(FROM_EXPR);
36+
blockPath = stripQuotes(blockPath);
37+
38+
if (!exportList) {
39+
block.addError(new errors.InvalidBlockSyntax(
40+
`Malformed block export: \`@export ${atRule.params}\``,
5841
sourceRange(factory.configuration, block.stylesheet, file, atRule),
59-
);
60-
}
61-
let localName = blockNames[remoteName];
62-
if (!CLASS_NAME_IDENT.test(localName)) {
63-
throw new errors.InvalidBlockSyntax(
64-
`Illegal block name in export. "${localName}" is not a legal CSS identifier.`,
65-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
66-
);
67-
}
68-
if (!CLASS_NAME_IDENT.test(remoteName)) {
69-
throw new errors.InvalidBlockSyntax(
70-
`Illegal block name in import. "${remoteName}" is not a legal CSS identifier.`,
71-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
72-
);
73-
}
74-
if (localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT) {
75-
throw new errors.InvalidBlockSyntax(
76-
`Unnecessary re-export of default Block.`,
77-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
78-
);
79-
}
42+
));
43+
}
8044

81-
if (RESERVED_BLOCK_NAMES.has(remoteName)) {
82-
throw new errors.InvalidBlockSyntax(
83-
`Cannot export "${localName}" as reserved word "${remoteName}"`,
84-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
85-
);
86-
}
45+
// Import file, then parse file, then save block reference.
46+
let srcBlockPromise: Promise<Block> = Promise.resolve(block);
47+
if (blockPath) {
48+
srcBlockPromise = factory.getBlockRelative(block.identifier, blockPath);
49+
}
8750

88-
let referencedBlock = srcBlock.getReferencedBlock(localName);
89-
if (!referencedBlock) {
90-
throw new errors.InvalidBlockSyntax(
91-
`Cannot export Block "${localName}". No Block named "${localName}" in "${file}".`,
51+
// Validate our imported block name is a valid CSS identifier.
52+
const blockNames = parseBlockNames(exportList, !!blockPath);
53+
const exportPromise = srcBlockPromise.then((srcBlock) => {
54+
for (let remoteName of Object.keys(blockNames)) {
55+
if (remoteNames.has(remoteName)) {
56+
block.addError(new errors.InvalidBlockSyntax(
57+
`Cannot have duplicate Block export of same name: "${remoteName}".`,
9258
sourceRange(factory.configuration, block.stylesheet, file, atRule),
93-
);
59+
));
60+
} else {
61+
let localName = blockNames[remoteName];
62+
if (!CLASS_NAME_IDENT.test(localName)) {
63+
block.addError(new errors.InvalidBlockSyntax(
64+
`Illegal block name in export. "${localName}" is not a legal CSS identifier.`,
65+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
66+
));
67+
}
68+
else if (!CLASS_NAME_IDENT.test(remoteName)) {
69+
block.addError(new errors.InvalidBlockSyntax(
70+
`Illegal block name in import. "${remoteName}" is not a legal CSS identifier.`,
71+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
72+
));
73+
}
74+
else if (localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT) {
75+
block.addError(new errors.InvalidBlockSyntax(
76+
`Unnecessary re-export of default Block.`,
77+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
78+
));
79+
}
80+
81+
else if (RESERVED_BLOCK_NAMES.has(remoteName)) {
82+
block.addError (new errors.InvalidBlockSyntax(
83+
`Cannot export "${localName}" as reserved word "${remoteName}"`,
84+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
85+
));
86+
}
87+
88+
let referencedBlock = srcBlock.getReferencedBlock(localName);
89+
if (!referencedBlock) {
90+
block.addError (new errors.InvalidBlockSyntax(
91+
`Cannot export Block "${localName}". No Block named "${localName}" in "${file}".`,
92+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
93+
));
94+
} else {
95+
// Save exported blocks
96+
block.addBlockExport(remoteName, referencedBlock);
97+
}
98+
}
9499
}
100+
});
95101

96-
// Save exported blocks
97-
block.addBlockExport(remoteName, referencedBlock);
102+
exportPromises.push(exportPromise);
98103

99-
}
100104
});
101-
102-
exportPromises.push(exportPromise);
103-
104-
});
105+
}
105106

106107
// After all export promises have resolved, resolve the decorated Block.
107108
await Promise.all(exportPromises);

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

+83-78
Original file line numberDiff line numberDiff line change
@@ -21,94 +21,99 @@ export async function importBlocks(block: Block, factory: BlockFactory, file: st
2121
let namedBlockReferences: Promise<[string, string, postcss.AtRule, Block]>[] = [];
2222

2323
if (!root) {
24-
throw new errors.InvalidBlockSyntax(`Error finding PostCSS root for block ${block.name}`);
25-
}
24+
block.addError(new errors.InvalidBlockSyntax(`Error finding PostCSS root for block ${block.name}`));
25+
} else {
26+
// For each `@block` expression, read in the block file, parse and
27+
// push to block references Promise array.
28+
root.walkAtRules(BLOCK_IMPORT, (atRule: postcss.AtRule) => {
29+
// imports: `<blocks-list> from <block-path>`
30+
// blockList: `<default-block> | <named-blocks> | <default-block> " , " <named-blocks> | <named-blocks> " , " <default-block>`
31+
// blockPath: `' " ' <any-value> ' " ' | " ' " <any-value> " ' "`
32+
let imports = atRule.params;
33+
let [blockList = "", blockPath = ""] = imports.split(FROM_EXPR);
34+
blockPath = stripQuotes(blockPath);
2635

27-
// For each `@block` expression, read in the block file, parse and
28-
// push to block references Promise array.
29-
root.walkAtRules(BLOCK_IMPORT, (atRule: postcss.AtRule) => {
30-
// imports: `<blocks-list> from <block-path>`
31-
// blockList: `<default-block> | <named-blocks> | <default-block> " , " <named-blocks> | <named-blocks> " , " <default-block>`
32-
// blockPath: `' " ' <any-value> ' " ' | " ' " <any-value> " ' "`
33-
let imports = atRule.params;
34-
let [blockList = "", blockPath = ""] = imports.split(FROM_EXPR);
35-
blockPath = stripQuotes(blockPath);
36+
if (!blockList || !blockPath) {
37+
block.addError(new errors.InvalidBlockSyntax(
38+
`Malformed block reference: \`@block ${atRule.params}\``,
39+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
40+
));
41+
} else {
42+
// Import file, then parse file, then save block reference.
43+
let blockPromise: Promise<Block> = factory.getBlockRelative(block.identifier, blockPath);
3644

37-
if (!blockList || !blockPath) {
38-
throw new errors.InvalidBlockSyntax(
39-
`Malformed block reference: \`@block ${atRule.params}\``,
40-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
41-
);
42-
}
45+
blockPromise = blockPromise.catch((e) => {
46+
if (e instanceof errors.CssBlockError) {
47+
e.importStack.push(sourceRange(factory.configuration, block.stylesheet, file, atRule));
48+
}
49+
throw e;
50+
});
4351

44-
// Import file, then parse file, then save block reference.
45-
let blockPromise: Promise<Block> = factory.getBlockRelative(block.identifier, blockPath);
52+
let blockNames = parseBlockNames(blockList, true);
53+
for (let localName of Object.keys(blockNames)) {
54+
let remoteName = blockNames[localName];
55+
// Validate our imported block name is a valid CSS identifier.
56+
if (!CLASS_NAME_IDENT.test(localName)) {
57+
block.addError(new errors.InvalidBlockSyntax(
58+
`Illegal block name in import. "${localName}" is not a legal CSS identifier.`,
59+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
60+
));
61+
}
62+
if (!CLASS_NAME_IDENT.test(remoteName)) {
63+
block.addError(new errors.InvalidBlockSyntax(
64+
`Illegal block name in import. "${remoteName}" is not a legal CSS identifier.`,
65+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
66+
));
67+
}
68+
if (localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT) {
69+
block.addError(new errors.InvalidBlockSyntax(
70+
`Default Block from "${blockPath}" must be aliased to a unique local identifier.`,
71+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
72+
));
73+
}
74+
if (isBlockNameReserved(localName)) {
75+
block.addError(new errors.InvalidBlockSyntax(
76+
`Cannot import "${remoteName}" as reserved word "${localName}"`,
77+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
78+
));
79+
}
4680

47-
blockPromise = blockPromise.catch((e) => {
48-
if (e instanceof errors.CssBlockError) {
49-
e.importStack.push(sourceRange(factory.configuration, block.stylesheet, file, atRule));
81+
// Once block is parsed, save named block reference
82+
let namedResult: Promise<[string, string, postcss.AtRule, Block]> = blockPromise.then((block: Block): [string, string, postcss.AtRule, Block] => {
83+
let referencedBlock = block.getExportedBlock(remoteName);
84+
if (!referencedBlock) {
85+
throw new errors.InvalidBlockSyntax(
86+
`Cannot import Block "${remoteName}". No Block named "${remoteName}" exported by "${blockPath}".`,
87+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
88+
);
89+
}
90+
return [localName, blockPath, atRule, referencedBlock];
91+
});
92+
namedBlockReferences.push(namedResult);
93+
}
5094
}
51-
throw e;
5295
});
5396

54-
let blockNames = parseBlockNames(blockList, true);
55-
for (let localName of Object.keys(blockNames)) {
56-
let remoteName = blockNames[localName];
57-
// Validate our imported block name is a valid CSS identifier.
58-
if (!CLASS_NAME_IDENT.test(localName)) {
59-
throw new errors.InvalidBlockSyntax(
60-
`Illegal block name in import. "${localName}" is not a legal CSS identifier.`,
61-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
62-
);
63-
}
64-
if (!CLASS_NAME_IDENT.test(remoteName)) {
65-
throw new errors.InvalidBlockSyntax(
66-
`Illegal block name in import. "${remoteName}" is not a legal CSS identifier.`,
67-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
68-
);
69-
}
70-
if (localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT) {
71-
throw new errors.InvalidBlockSyntax(
72-
`Default Block from "${blockPath}" must be aliased to a unique local identifier.`,
73-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
74-
);
75-
}
76-
if (isBlockNameReserved(localName)) {
77-
throw new errors.InvalidBlockSyntax(
78-
`Cannot import "${remoteName}" as reserved word "${localName}"`,
79-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
80-
);
81-
}
82-
83-
// Once block is parsed, save named block reference
84-
let namedResult: Promise<[string, string, postcss.AtRule, Block]> = blockPromise.then((block: Block): [string, string, postcss.AtRule, Block] => {
85-
let referencedBlock = block.getExportedBlock(remoteName);
86-
if (!referencedBlock) {
87-
throw new errors.InvalidBlockSyntax(
88-
`Cannot import Block "${remoteName}". No Block named "${remoteName}" exported by "${blockPath}".`,
97+
// When all import promises have resolved, save the block references and
98+
// resolve.
99+
let results;
100+
try {
101+
results = await Promise.all(namedBlockReferences);
102+
let localNames: ObjectDictionary<string> = {};
103+
results.forEach(([localName, importPath, atRule, otherBlock]) => {
104+
if (localNames[localName]) {
105+
block.addError(new errors.InvalidBlockSyntax(
106+
`Blocks ${localNames[localName]} and ${importPath} cannot both have the name ${localName} in this scope.`,
89107
sourceRange(factory.configuration, block.stylesheet, file, atRule),
90-
);
108+
));
109+
} else {
110+
block.addBlockReference(localName, otherBlock);
111+
localNames[localName] = importPath;
91112
}
92-
return [localName, blockPath, atRule, referencedBlock];
93113
});
94-
namedBlockReferences.push(namedResult);
114+
} catch (e) {
115+
block.addError(e);
95116
}
96-
97-
});
98-
99-
// When all import promises have resolved, save the block references and resolve.
100-
let results = await Promise.all(namedBlockReferences);
101-
let localNames: ObjectDictionary<string> = {};
102-
results.forEach(([localName, importPath, atRule, otherBlock]) => {
103-
if (localNames[localName]) {
104-
throw new errors.InvalidBlockSyntax(
105-
`Blocks ${localNames[localName]} and ${importPath} cannot both have the name ${localName} in this scope.`,
106-
sourceRange(factory.configuration, block.stylesheet, file, atRule),
107-
);
108-
} else {
109-
block.addBlockReference(localName, otherBlock);
110-
localNames[localName] = importPath;
111-
}
112-
});
117+
}
113118
return block;
114119
}

0 commit comments

Comments
 (0)