@@ -5,12 +5,17 @@ import { BLOCK_IMPORT, CLASS_NAME_IDENT, DEFAULT_EXPORT, isBlockNameReserved } f
5
5
import { Block } from "../../BlockTree" ;
6
6
import * as errors from "../../errors" ;
7
7
import { sourceRange } from "../../SourceLocation" ;
8
- import { allDone } from "../../util" ;
9
8
import { BlockFactory } from "../index" ;
10
9
import { parseBlockNames , stripQuotes } from "../utils" ;
11
10
12
11
const FROM_EXPR = / \s + f r o m \s + / ;
13
12
13
+ interface ParsedImport {
14
+ blockPath : string ;
15
+ atRule : postcss . AtRule ;
16
+ names : Array < { localName : string ; remoteName : string } > ;
17
+ }
18
+
14
19
/**
15
20
* Resolve all block references for a given block.
16
21
* @param block Block to resolve references for
@@ -19,102 +24,125 @@ const FROM_EXPR = /\s+from\s+/;
19
24
export async function importBlocks ( block : Block , factory : BlockFactory , file : string ) : Promise < Block > {
20
25
21
26
let root : postcss . Root | undefined = block . stylesheet ;
22
- let namedBlockReferences : Promise < [ string , string , postcss . AtRule , Block ] > [ ] = [ ] ;
23
27
24
28
if ( ! root ) {
25
- block . addError ( new errors . InvalidBlockSyntax ( `Error finding PostCSS root for block ${ block . name } ` ) ) ;
26
- } else {
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
-
37
- if ( ! blockList || ! blockPath ) {
38
- block . addError ( new errors . InvalidBlockSyntax (
39
- `Malformed block reference: \`@block ${ atRule . params } \`` ,
40
- sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
41
- ) ) ;
42
- } else {
43
- // Import file, then parse file, then save block reference.
44
- let blockPromise : Promise < Block > = factory . getBlockRelative ( block . identifier , blockPath ) ;
45
-
46
- blockPromise = blockPromise . catch ( ( e ) => {
47
- if ( e instanceof errors . CssBlockError ) {
48
- e . importStack . push ( sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ) ;
49
- }
50
- throw e ;
51
- } ) ;
29
+ block . addError ( new errors . InvalidBlockSyntax ( `Internal Error: Cannot find PostCSS root for block ${ block . name } ` ) ) ;
30
+ return block ;
31
+ }
52
32
53
- let blockNames = parseBlockNames ( blockList , true ) ;
54
- for ( let localName of Object . keys ( blockNames ) ) {
55
- let remoteName = blockNames [ localName ] ;
56
- // Validate our imported block name is a valid CSS identifier.
57
- if ( ! CLASS_NAME_IDENT . test ( localName ) ) {
58
- block . addError ( new errors . InvalidBlockSyntax (
59
- `Illegal block name in import. "${ localName } " is not a legal CSS identifier.` ,
60
- sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
61
- ) ) ;
62
- }
63
- if ( ! CLASS_NAME_IDENT . test ( remoteName ) ) {
64
- block . addError ( new errors . InvalidBlockSyntax (
65
- `Illegal block name in import. "${ remoteName } " is not a legal CSS identifier.` ,
66
- sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
67
- ) ) ;
68
- }
69
- if ( localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT ) {
70
- block . addError ( new errors . InvalidBlockSyntax (
71
- `Default Block from "${ blockPath } " must be aliased to a unique local identifier.` ,
72
- sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
73
- ) ) ;
74
- }
75
- if ( isBlockNameReserved ( localName ) ) {
76
- block . addError ( new errors . InvalidBlockSyntax (
77
- `Cannot import "${ remoteName } " as reserved word "${ localName } "` ,
78
- sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
79
- ) ) ;
80
- }
33
+ let parsedImports = new Array < ParsedImport > ( ) ;
34
+ // For each `@block` expression, read in the block file, parse and
35
+ // push to block references Promise array.
36
+ root . walkAtRules ( BLOCK_IMPORT , ( atRule : postcss . AtRule ) => {
37
+ // imports: `<blocks-list> from <block-path>`
38
+ // blockList: `<default-block> | <named-blocks> | <default-block> " , " <named-blocks> | <named-blocks> " , " <default-block>`
39
+ // blockPath: `' " ' <any-value> ' " ' | " ' " <any-value> " ' "`
40
+ let imports = atRule . params ;
41
+ let [ blockList = "" , blockPath = "" ] = imports . split ( FROM_EXPR ) ;
42
+ blockPath = stripQuotes ( blockPath ) ;
81
43
82
- // Once block is parsed, save named block reference
83
- let namedResult : Promise < [ string , string , postcss . AtRule , Block ] > = blockPromise . then ( ( block : Block ) : [ string , string , postcss . AtRule , Block ] => {
84
- let referencedBlock = block . getExportedBlock ( remoteName ) ;
85
- if ( ! referencedBlock ) {
86
- throw new errors . InvalidBlockSyntax (
87
- `Cannot import Block "${ remoteName } ". No Block named "${ remoteName } " exported by "${ blockPath } ".` ,
88
- sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
89
- ) ;
90
- }
91
- return [ localName , blockPath , atRule , referencedBlock ] ;
92
- } ) ;
93
- namedBlockReferences . push ( namedResult ) ;
44
+ if ( ! blockList || ! blockPath ) {
45
+ block . addError ( new errors . InvalidBlockSyntax (
46
+ `Malformed block reference: \`@block ${ atRule . params } \`` ,
47
+ sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
48
+ ) ) ;
49
+ } else {
50
+ let names : ParsedImport [ "names" ] = [ ] ;
51
+ let blockNames = parseBlockNames ( blockList , true ) ;
52
+ for ( let localName of Object . keys ( blockNames ) ) {
53
+ let remoteName = blockNames [ localName ] ;
54
+ let hasInvalidNames = validateBlockNames ( factory . configuration , block , blockPath , localName , remoteName , file , atRule ) ;
55
+ if ( ! hasInvalidNames ) {
56
+ names . push ( { localName, remoteName } ) ;
94
57
}
95
58
}
96
- } ) ;
59
+ parsedImports . push ( { blockPath, atRule, names } ) ;
60
+ }
61
+ } ) ;
62
+
63
+ let localNames : ObjectDictionary < string > = { } ;
64
+ for ( let parsedImport of parsedImports ) {
65
+ let { blockPath, atRule, names} = parsedImport ;
66
+ let referencedBlock : Block | null = null ;
97
67
98
- // When all import promises have resolved, save the block references and
99
- // resolve.
100
- let results : Array < [ string , string , postcss . AtRule , Block ] > ;
68
+ // Import the main block file referenced by the import path.
101
69
try {
102
- results = await allDone ( namedBlockReferences ) ;
103
- let localNames : ObjectDictionary < string > = { } ;
104
- results . forEach ( ( [ localName , importPath , atRule , otherBlock ] ) => {
105
- if ( localNames [ localName ] ) {
70
+ referencedBlock = await factory . getBlockRelative ( block . identifier , parsedImport . blockPath ) ;
71
+ } catch ( err ) {
72
+ block . addError ( new errors . CascadingError (
73
+ "Error in imported block." ,
74
+ err ,
75
+ sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
76
+ ) ) ;
77
+ }
78
+
79
+ for ( let { localName, remoteName} of names ) {
80
+ // check for duplicate local names
81
+ if ( localNames [ localName ] ) {
82
+ block . addError ( new errors . InvalidBlockSyntax (
83
+ `Blocks ${ localNames [ localName ] } and ${ blockPath } cannot both have the name ${ localName } in this scope.` ,
84
+ sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
85
+ ) ) ;
86
+ continue ;
87
+ } else {
88
+ localNames [ localName ] = blockPath ;
89
+ }
90
+
91
+ // Store a reference to the local block if possible
92
+ if ( referencedBlock ) {
93
+ let exportedBlock = referencedBlock . getExportedBlock ( remoteName ) ;
94
+ if ( exportedBlock ) {
95
+ block . addBlockReference ( localName , exportedBlock ) ;
96
+ } else {
106
97
block . addError ( new errors . InvalidBlockSyntax (
107
- `Blocks ${ localNames [ localName ] } and ${ importPath } cannot both have the name ${ localName } in this scope .` ,
98
+ `Cannot import Block " ${ remoteName } ". No Block named " ${ remoteName } " exported by " ${ blockPath } " .` ,
108
99
sourceRange ( factory . configuration , block . stylesheet , file , atRule ) ,
109
100
) ) ;
110
- } else {
111
- block . addBlockReference ( localName , otherBlock ) ;
112
- localNames [ localName ] = importPath ;
113
101
}
114
- } ) ;
115
- } catch ( e ) {
116
- block . addError ( e ) ;
102
+ }
117
103
}
118
104
}
119
105
return block ;
120
106
}
107
+
108
+ function validateBlockNames (
109
+ config : BlockFactory [ "configuration" ] ,
110
+ block : Block ,
111
+ blockPath : string ,
112
+ localName : string ,
113
+ remoteName : string ,
114
+ file : string ,
115
+ atRule : postcss . AtRule ,
116
+ ) : boolean {
117
+ let hasInvalidNames = false ;
118
+ // Validate our imported block name is a valid CSS identifier.
119
+ if ( ! CLASS_NAME_IDENT . test ( localName ) ) {
120
+ hasInvalidNames = true ;
121
+ block . addError ( new errors . InvalidBlockSyntax (
122
+ `Illegal block name in import. "${ localName } " is not a legal CSS identifier.` ,
123
+ sourceRange ( config , block . stylesheet , file , atRule ) ,
124
+ ) ) ;
125
+ }
126
+ if ( ! CLASS_NAME_IDENT . test ( remoteName ) ) {
127
+ hasInvalidNames = true ;
128
+ block . addError ( new errors . InvalidBlockSyntax (
129
+ `Illegal block name in import. "${ remoteName } " is not a legal CSS identifier.` ,
130
+ sourceRange ( config , block . stylesheet , file , atRule ) ,
131
+ ) ) ;
132
+ }
133
+ if ( localName === DEFAULT_EXPORT && remoteName === DEFAULT_EXPORT ) {
134
+ hasInvalidNames = true ;
135
+ block . addError ( new errors . InvalidBlockSyntax (
136
+ `Default Block from "${ blockPath } " must be aliased to a unique local identifier.` ,
137
+ sourceRange ( config , block . stylesheet , file , atRule ) ,
138
+ ) ) ;
139
+ }
140
+ if ( isBlockNameReserved ( localName ) ) {
141
+ hasInvalidNames = true ;
142
+ block . addError ( new errors . InvalidBlockSyntax (
143
+ `Cannot import "${ remoteName } " as reserved word "${ localName } "` ,
144
+ sourceRange ( config , block . stylesheet , file , atRule ) ,
145
+ ) ) ;
146
+ }
147
+ return hasInvalidNames ;
148
+ }
0 commit comments