Skip to content

Commit f7b53fd

Browse files
committed
feat: Use sourcemaps for errors involving non-selector nodes.
1 parent 78756f2 commit f7b53fd

19 files changed

+104
-76
lines changed

packages/@css-blocks/cli/test/cli-test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ describe("validate with preprocessors", () => {
8989
\t3: .foo {
9090
\t4: color: red;
9191
Found 1 error in 1 file.
92+
`);
93+
assert.equal(cli.exitCode, 1);
94+
});
95+
it("can check syntax for a style lookup failure", async () => {
96+
let cli = new CLI();
97+
await cli.run(["validate", "--preprocessors", distFile("test/preprocessors"), fixture("scss/missing-style.block.scss")]);
98+
assert.equal(cli.output, `error\ttest/fixtures/scss/missing-style.block.scss
99+
\tNo style "simple[state|light]" found.
100+
\tAt compiled output of test/fixtures/scss/missing-style.block.scss:3:3
101+
\t2: .composer {
102+
\t3: composes: "simple[state|light]";
103+
\t4: color: blue;
104+
\tSource Mapped to test/fixtures/scss/missing-style.block.scss:4:3
105+
\t3: .composer {
106+
\t4: composes: "simple[state|light]";
107+
\t5: color: blue;
108+
Found 1 error in 1 file.
92109
`);
93110
assert.equal(cli.exitCode, 1);
94111
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@block simple from "./simple.block.scss";
2+
3+
.composer {
4+
composes: "simple[state|light]";
5+
color: blue;
6+
}

packages/@css-blocks/core/src/BlockCompiler/ConflictResolver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,6 @@ export class ConflictResolver {
435435
}
436436
sourceRange(block: Block, node: postcss.Node): SourceRange | SourceFile | undefined {
437437
let blockPath = this.config.importer.debugIdentifier(block.identifier, this.config);
438-
return sourceRange(blockPath, node);
438+
return sourceRange(this.config, block.stylesheet, blockPath, node);
439439
}
440440
}

packages/@css-blocks/core/src/BlockCompiler/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class BlockCompiler {
7373
*/
7474
public processDebugStatements(sourceFile: string, root: postcss.Root, block: Block) {
7575
root.walkAtRules(BLOCK_DEBUG, (atRule) => {
76-
let {block: ref, channel} = parseBlockDebug(atRule, sourceFile, block);
76+
let {block: ref, channel} = parseBlockDebug(this.config, root, atRule, sourceFile, block);
7777
if (channel === "comment") {
7878
let text = `${ref.debug(this.config).join("\n * ")}\n`;
7979
atRule.replaceWith(this.postcss.comment({ text }));

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ export class BlockParser {
6666
debug(`Begin parse: "${debugIdent}"`);
6767

6868
// Discover the block's preferred name.
69-
name = await discoverName(root, name, sourceFile);
69+
name = await discoverName(this.factory.configuration, root, name, sourceFile);
7070

7171
// Create our new Block object and save reference to the raw AST.
7272
let block = new Block(name, identifier, root);
7373

7474
// Throw if we encounter any `!important` decls.
7575
debug(` - Disallow Import`);
76-
await disallowImportant(root, sourceFile);
76+
await disallowImportant(this.factory.configuration, root, sourceFile);
7777
// Discover and parse all block references included by this block.
7878
debug(` - Import Blocks`);
7979
await importBlocks(block, this.factory, sourceFile);
@@ -82,21 +82,21 @@ export class BlockParser {
8282
await exportBlocks(block, this.factory, sourceFile);
8383
// Handle any global attributes defined by this block.
8484
debug(` - Global Attributes`);
85-
await globalAttributes(root, block, sourceFile);
85+
await globalAttributes(this.factory.configuration, root, block, sourceFile);
8686
// Parse all block styles and build block tree.
8787
debug(` - Construct Block`);
88-
await constructBlock(root, block, this.factory.configuration, debugIdent);
88+
await constructBlock(this.factory.configuration, root, block, debugIdent);
8989
// Verify that external blocks referenced have been imported, have defined the attribute being selected, and have marked it as a global state.
9090
debug(` - Assert Foreign Globals`);
9191
await assertForeignGlobalAttribute(this.factory.configuration, root, block, debugIdent);
9292
// Construct block extensions and validate.
9393
debug(` - Extend Block`);
94-
await extendBlock(root, block, debugIdent);
94+
await extendBlock(this.factory.configuration, root, block, debugIdent);
9595
// Validate that all required Styles are implemented.
9696
debug(` - Implement Block`);
97-
await implementBlock(root, block, debugIdent);
97+
await implementBlock(this.factory.configuration, root, block, debugIdent);
9898
// Register all block compositions.
99-
await composeBlock(root, block, debugIdent);
99+
await composeBlock(this.factory.configuration, root, block, debugIdent);
100100
// Log any debug statements discovered.
101101
debug(` - Process Debugs`);
102102
await processDebugStatements(root, block, debugIdent, this.config);

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as errors from "../../errors";
77
import { sourceRange } from "../../SourceLocation";
88
import { getStyleTargets } from "../block-intermediates";
99
import { stripQuotes } from "../utils";
10+
import { Configuration } from "../../configuration";
1011

1112
/**
1213
* For each `composes` property found in the passed ruleset, track the foreign
@@ -15,26 +16,26 @@ import { stripQuotes } from "../utils";
1516
* @param sourceFile Source file name, used for error output.
1617
* @param rule Ruleset to crawl
1718
*/
18-
export async function composeBlock(root: postcss.Root, block: Block, sourceFile: string) {
19+
export async function composeBlock(configuration: Configuration, root: postcss.Root, block: Block, sourceFile: string) {
1920
root.walkDecls(COMPOSES, (decl) => {
20-
if (!isRule(decl.parent)) { throw new errors.InvalidBlockSyntax(`The "composes" property may only be used in a rule set.`, sourceRange(sourceFile, decl)); }
21+
if (!isRule(decl.parent)) { throw new errors.InvalidBlockSyntax(`The "composes" property may only be used in a rule set.`, sourceRange(configuration, root, sourceFile, decl)); }
2122
let rule = decl.parent;
2223

2324
// TODO: Move to Block Syntax as parseBlockRefList().
2425
let refNames = decl.value.split(/,\s*/).map(stripQuotes);
2526
for (let refName of refNames) {
2627
let refStyle = block.lookup(refName);
2728
if (!refStyle) {
28-
throw new errors.InvalidBlockSyntax(`No style "${refName}" found.`, sourceRange(sourceFile, decl));
29+
throw new errors.InvalidBlockSyntax(`No style "${refName}" found.`, sourceRange(configuration, root, sourceFile, decl));
2930
}
3031
if (refStyle.block === block) {
31-
throw new errors.InvalidBlockSyntax(`Styles from the same Block may not be composed together.`, sourceRange(sourceFile, decl));
32+
throw new errors.InvalidBlockSyntax(`Styles from the same Block may not be composed together.`, sourceRange(configuration, root, sourceFile, decl));
3233
}
3334

3435
const parsedSel = block.getParsedSelectors(rule);
3536
for (let sel of parsedSel) {
3637
if (sel.selector.next) {
37-
throw new errors.InvalidBlockSyntax(`Style composition is not allowed in rule sets with a scope selector.`, sourceRange(sourceFile, decl));
38+
throw new errors.InvalidBlockSyntax(`Style composition is not allowed in rule sets with a scope selector.`, sourceRange(configuration, root, sourceFile, decl));
3839
}
3940
let foundStyles = getStyleTargets(block, sel.selector);
4041
for (let blockClass of foundStyles.blockClasses) {

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ function shouldBeParsedAsBlockSelector(rule: postcss.Rule): boolean {
3838
* @param file string The filepath of the file we are parsing for error reporting.
3939
* @returns The ParsedSelector array.
4040
**/
41-
function getParsedSelectors(block: Block, rule: postcss.Rule, file: string): ParsedSelector[] {
41+
function getParsedSelectors(configuration: Configuration, block: Block, rule: postcss.Rule, file: string): ParsedSelector[] {
4242
let res;
4343
try { res = block.getParsedSelectors(rule); }
44-
catch (e) { throw new errors.InvalidBlockSyntax(e.message, sourceRange(file, rule)); }
44+
catch (e) { throw new errors.InvalidBlockSyntax(e.message, sourceRange(configuration, block.stylesheet, file, rule)); }
4545
return res;
4646
}
4747

48-
export async function constructBlock(root: postcss.Root, block: Block, configuration: Configuration, file: string): Promise<Block> {
48+
export async function constructBlock(configuration: Configuration, root: postcss.Root, block: Block, file: string): Promise<Block> {
4949

5050
let styleRuleTuples: Set<[Style, postcss.Rule]> = new Set();
5151

@@ -56,7 +56,7 @@ export async function constructBlock(root: postcss.Root, block: Block, configura
5656
if (!shouldBeParsedAsBlockSelector(rule)) { return; }
5757

5858
// Fetch the parsed selectors list. Throw a helpful error if we can't parse.
59-
let parsedSelectors = getParsedSelectors(block, rule, file);
59+
let parsedSelectors = getParsedSelectors(configuration, block, rule, file);
6060

6161
// Iterate over the all selectors for this rule – one for each comma separated selector.
6262
parsedSelectors.forEach((iSel) => {
@@ -99,7 +99,7 @@ export async function constructBlock(root: postcss.Root, block: Block, configura
9999
// To allow self-referential block lookup when constructing ruleset concerns,
100100
// we need to run `addRuleset()` only *after* all Styles have been created.
101101
for (let [style, rule] of styleRuleTuples) {
102-
style.rulesets.addRuleset(file, rule);
102+
style.rulesets.addRuleset(configuration, file, rule);
103103
}
104104

105105
return block;

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import { postcss } from "opticss";
22

33
import * as errors from "../../errors";
44
import { sourceRange } from "../../SourceLocation";
5+
import { Configuration } from "../../configuration";
56

6-
export async function disallowImportant(root: postcss.Root, file: string): Promise<postcss.Root> {
7+
export async function disallowImportant(configuration: Configuration, root: postcss.Root, file: string): Promise<postcss.Root> {
78
root.walkDecls((decl) => {
89

910
// `!important` is not allowed in Blocks. If contains `!important` declaration, throw.
1011
if (decl.important) {
1112
throw new errors.InvalidBlockSyntax(
1213
`!important is not allowed for \`${decl.prop}\` in \`${(<postcss.Rule>decl.parent).selector}\``,
13-
sourceRange(file, decl),
14+
sourceRange(configuration, root, file, decl),
1415
);
1516
}
1617

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import { postcss } from "opticss";
33
import { BLOCK_NAME, CLASS_NAME_IDENT } from "../../BlockSyntax";
44
import * as errors from "../../errors";
55
import { sourceRange } from "../../SourceLocation";
6+
import { Configuration } from "../../configuration";
67

7-
export async function discoverName(root: postcss.Root, defaultName: string, file: string): Promise<string> {
8+
export async function discoverName(configuration: Configuration, root: postcss.Root, defaultName: string, file: string): Promise<string> {
89

910
// Eagerly fetch custom `block-name` from the root block rule.
1011
root.walkRules(":scope", (rule) => {
1112
rule.walkDecls(BLOCK_NAME, (decl) => {
1213
if (!CLASS_NAME_IDENT.test(decl.value)) {
1314
throw new errors.InvalidBlockSyntax(
1415
`Illegal block name. '${decl.value}' is not a legal CSS identifier.`,
15-
sourceRange(file, decl),
16+
sourceRange(configuration, root, file, decl),
1617
);
1718
}
1819

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function exportBlocks(block: Block, factory: BlockFactory, file: st
3838
if (!exportList) {
3939
throw new errors.InvalidBlockSyntax(
4040
`Malformed block export: \`@export ${atRule.params}\``,
41-
sourceRange(file, atRule),
41+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
4242
);
4343
}
4444

@@ -55,40 +55,40 @@ export async function exportBlocks(block: Block, factory: BlockFactory, file: st
5555
if (remoteNames.has(remoteName)) {
5656
throw new errors.InvalidBlockSyntax(
5757
`Can not have duplicate Block export of same name: "${remoteName}".`,
58-
sourceRange(file, atRule),
58+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
5959
);
6060
}
6161
let localName = blockNames[remoteName];
6262
if (!CLASS_NAME_IDENT.test(localName)) {
6363
throw new errors.InvalidBlockSyntax(
6464
`Illegal block name in export. "${localName}" is not a legal CSS identifier.`,
65-
sourceRange(file, atRule),
65+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
6666
);
6767
}
6868
if (!CLASS_NAME_IDENT.test(remoteName)) {
6969
throw new errors.InvalidBlockSyntax(
7070
`Illegal block name in import. "${remoteName}" is not a legal CSS identifier.`,
71-
sourceRange(file, atRule),
71+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
7272
);
7373
}
7474
if (localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT) {
7575
throw new errors.InvalidBlockSyntax(
7676
`Unnecessary re-export of default Block.`,
77-
sourceRange(file, atRule),
77+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
7878
);
7979
}
8080
if (remoteName === DEFAULT_EXPORT) {
8181
throw new errors.InvalidBlockSyntax(
8282
`Can not export "${localName}" as reserved word "${DEFAULT_EXPORT}"`,
83-
sourceRange(file, atRule),
83+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
8484
);
8585
}
8686

8787
let referencedBlock = srcBlock.getReferencedBlock(localName);
8888
if (!referencedBlock) {
8989
throw new errors.InvalidBlockSyntax(
9090
`Can not export Block "${localName}". No Block named "${localName}" in "${file}".`,
91-
sourceRange(file, atRule),
91+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
9292
);
9393
}
9494

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,23 @@ import { EXTENDS } from "../../BlockSyntax";
44
import { Block } from "../../BlockTree";
55
import * as errors from "../../errors";
66
import { sourceRange } from "../../SourceLocation";
7+
import { Configuration } from "../../configuration";
78

89
/**
910
* For each `extends` property found in the passed ruleset, set the block's base
1011
* to the foreign block. If block is not found, throw.
1112
* @param block Block object being processed.
1213
* @param sourceFile Source file name, used for error output.
13-
* @param rule Ruleset to crawl.
14+
* @param root Ruleset to crawl.
1415
*/
15-
export async function extendBlock(rule: postcss.Root, block: Block, sourceFile: string) {
16-
rule.walkDecls(EXTENDS, (decl) => {
16+
export async function extendBlock(configuration: Configuration, root: postcss.Root, block: Block, sourceFile: string) {
17+
root.walkDecls(EXTENDS, (decl) => {
1718
if (block.base) {
18-
throw new errors.InvalidBlockSyntax(`A block can only be extended once.`, sourceRange(sourceFile, decl));
19+
throw new errors.InvalidBlockSyntax(`A block can only be extended once.`, sourceRange(configuration, root, sourceFile, decl));
1920
}
2021
let baseBlock = block.getReferencedBlock(decl.value);
2122
if (!baseBlock) {
22-
throw new errors.InvalidBlockSyntax(`No Block named "${decl.value}" found in scope.`, sourceRange(sourceFile, decl));
23+
throw new errors.InvalidBlockSyntax(`No Block named "${decl.value}" found in scope.`, sourceRange(configuration, root, sourceFile, decl));
2324
}
2425
block.setBase(baseBlock);
2526
});

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { Block } from "../../BlockTree";
55
import * as errors from "../../errors";
66
import { sourceRange as range } from "../../SourceLocation";
77
import { toAttrToken } from "../block-intermediates";
8+
import { Configuration } from "../../configuration";
89

9-
export async function globalAttributes(root: postcss.Root, block: Block, file: string): Promise<Block> {
10+
export async function globalAttributes(configuration: Configuration, root: postcss.Root, block: Block, file: string): Promise<Block> {
1011
root.walkAtRules(BLOCK_GLOBAL, (atRule) => {
1112

1213
let selectors = parseSelector(atRule.params.trim());
@@ -22,7 +23,7 @@ export async function globalAttributes(root: postcss.Root, block: Block, file: s
2223
} else {
2324
throw new errors.InvalidBlockSyntax(
2425
`Illegal global attribute declaration: ${atRule.toString()}`,
25-
range(file, atRule),
26+
range(configuration, root, file, atRule),
2627
);
2728
}
2829
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IMPLEMENTS } from "../../BlockSyntax";
44
import { Block } from "../../BlockTree";
55
import * as errors from "../../errors";
66
import { sourceRange } from "../../SourceLocation";
7+
import { Configuration } from "../../configuration";
78

89
/**
910
* For each `implements` property found in the passed ruleset, track the foreign
@@ -12,13 +13,13 @@ import { sourceRange } from "../../SourceLocation";
1213
* @param sourceFile Source file name, used for error output.
1314
* @param rule Ruleset to crawl
1415
*/
15-
export async function implementBlock(rule: postcss.Root, block: Block, sourceFile: string) {
16+
export async function implementBlock(configuration: Configuration, rule: postcss.Root, block: Block, sourceFile: string) {
1617
rule.walkDecls(IMPLEMENTS, (decl) => {
1718
let refNames = decl.value.split(/,\s*/);
1819
refNames.forEach((refName) => {
1920
let refBlock = block.getReferencedBlock(refName);
2021
if (!refBlock) {
21-
throw new errors.InvalidBlockSyntax(`No Block named "${refName}" found in scope.`, sourceRange(sourceFile, decl));
22+
throw new errors.InvalidBlockSyntax(`No Block named "${refName}" found in scope.`, sourceRange(configuration, block.stylesheet, sourceFile, decl));
2223
}
2324
block.addImplementation(refBlock);
2425
});

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export async function importBlocks(block: Block, factory: BlockFactory, file: st
3737
if (!blockList || !blockPath) {
3838
throw new errors.InvalidBlockSyntax(
3939
`Malformed block reference: \`@block ${atRule.params}\``,
40-
sourceRange(file, atRule),
40+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
4141
);
4242
}
4343

@@ -51,25 +51,25 @@ export async function importBlocks(block: Block, factory: BlockFactory, file: st
5151
if (!CLASS_NAME_IDENT.test(localName)) {
5252
throw new errors.InvalidBlockSyntax(
5353
`Illegal block name in import. "${localName}" is not a legal CSS identifier.`,
54-
sourceRange(file, atRule),
54+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
5555
);
5656
}
5757
if (!CLASS_NAME_IDENT.test(remoteName)) {
5858
throw new errors.InvalidBlockSyntax(
5959
`Illegal block name in import. "${remoteName}" is not a legal CSS identifier.`,
60-
sourceRange(file, atRule),
60+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
6161
);
6262
}
6363
if (localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT) {
6464
throw new errors.InvalidBlockSyntax(
6565
`Default Block from "${blockPath}" must be aliased to a unique local identifier.`,
66-
sourceRange(file, atRule),
66+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
6767
);
6868
}
6969
if (localName === DEFAULT_EXPORT) {
7070
throw new errors.InvalidBlockSyntax(
7171
`Can not import "${remoteName}" as reserved word "${DEFAULT_EXPORT}"`,
72-
sourceRange(file, atRule),
72+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
7373
);
7474
}
7575

@@ -79,7 +79,7 @@ export async function importBlocks(block: Block, factory: BlockFactory, file: st
7979
if (!referencedBlock) {
8080
throw new errors.InvalidBlockSyntax(
8181
`Can not import Block "${remoteName}". No Block named "${remoteName}" exported by "${blockPath}".`,
82-
sourceRange(file, atRule),
82+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
8383
);
8484
}
8585
return [localName, blockPath, atRule, referencedBlock];
@@ -96,7 +96,7 @@ export async function importBlocks(block: Block, factory: BlockFactory, file: st
9696
if (localNames[localName]) {
9797
throw new errors.InvalidBlockSyntax(
9898
`Blocks ${localNames[localName]} and ${importPath} cannot both have the name ${localName} in this scope.`,
99-
sourceRange(file, atRule),
99+
sourceRange(factory.configuration, block.stylesheet, file, atRule),
100100
);
101101
} else {
102102
block.addBlockReference(localName, otherBlock);

0 commit comments

Comments
 (0)