4
4
CssBlockError ,
5
5
SourceRange ,
6
6
isNamespaceReserved ,
7
+ DEFAULT_NAMESPACE ,
7
8
} from "@css-blocks/core" ;
8
9
import { AST , preprocess , Walker } from "@glimmer/syntax" ;
9
10
import { ElementNode } from "@glimmer/syntax/dist/types/lib/types/nodes" ;
@@ -27,7 +28,7 @@ function walkClasses(astNode: AST.Node, callback: (namespace: string, classAttr:
27
28
console . debug ( node ) ;
28
29
for ( let attrNode of node . attributes ) {
29
30
let nsAttr = parseNamespacedBlockAttribute ( attrNode ) ;
30
- if ( isClassAttribute ( nsAttr ) && attrNode . value . type === "TextNode" ) {
31
+ if ( nsAttr && isClassAttribute ( nsAttr ) && attrNode . value . type === "TextNode" ) {
31
32
callback ( nsAttr . ns , attrNode , attrNode . value ) ;
32
33
}
33
34
}
@@ -170,20 +171,51 @@ export async function validateTemplates(
170
171
} , new Map ( ) ) ;
171
172
}
172
173
173
- export const enum SupportedAttributes {
174
+ export const enum AttributeType {
174
175
state = "state" ,
175
176
class = "class" ,
176
177
scope = "scope" ,
178
+ ambiguous = "ambiguous"
177
179
}
178
180
179
- interface BlockSegments {
181
+ interface BlockAttributeBase {
182
+ attributeType : AttributeType ;
180
183
referencedBlock ?: string ;
181
- className ?: string ;
182
184
}
183
185
184
- interface ItemAtCursor extends BlockSegments {
185
- parentType : SupportedAttributes ;
186
- siblingBlocks ?: BlockSegments [ ] ;
186
+ export interface ScopeAttribute extends BlockAttributeBase {
187
+ attributeType : AttributeType . scope ;
188
+ }
189
+
190
+ export interface ClassAttribute extends BlockAttributeBase {
191
+ attributeType : AttributeType . class ;
192
+ name ?: string ;
193
+ }
194
+
195
+ export interface StateAttribute extends BlockAttributeBase {
196
+ attributeType : AttributeType . state ;
197
+ name : string ;
198
+ value ?: string ;
199
+ }
200
+
201
+ export interface AmbiguousAttribute extends BlockAttributeBase {
202
+ attributeType : AttributeType . ambiguous ;
203
+ referencedBlock ?: undefined ;
204
+ name : string ;
205
+ }
206
+
207
+ export type BlockAttribute = ScopeAttribute | ClassAttribute | StateAttribute | AmbiguousAttribute ;
208
+
209
+ interface NamespacedAttr {
210
+ ns : string ;
211
+ name : string ;
212
+ value ?: string ;
213
+ }
214
+
215
+
216
+ interface ItemAtCursor {
217
+ attribute : BlockAttribute ;
218
+ siblingAttributes : ClassAttribute [ ] ;
187
219
}
188
220
189
221
function getParentElement ( focusRoot : FocusPath | null ) : ElementNode | null {
@@ -199,32 +231,29 @@ function getParentElement(focusRoot: FocusPath | null): ElementNode | null {
199
231
return null ;
200
232
}
201
233
202
- function buildBlockSegments ( attr : NamespacedAttr | null , attrValue : AST . AttrNode [ "value" ] ) : BlockSegments | null {
234
+ function buildClassAttribute ( attr : NamespacedAttr | null , attrValue : AST . AttrNode [ "value" ] ) : ClassAttribute | null {
203
235
if ( attr === null ) return null ;
204
236
if ( attrValue . type === "TextNode" ) {
205
237
if ( attr . ns === "block" ) {
206
238
return {
207
- className : attrValue . chars ,
239
+ attributeType : AttributeType . class ,
240
+ name : attrValue . chars ,
208
241
} ;
209
242
} else {
210
243
return {
244
+ attributeType : AttributeType . class ,
211
245
referencedBlock : attr . ns ,
212
- className : attrValue . chars ,
246
+ name : attrValue . chars ,
213
247
} ;
214
248
}
215
249
} else {
216
250
return null ;
217
251
}
218
252
}
219
253
220
- interface NamespacedAttr {
221
- ns : string ;
222
- name : string ;
223
- }
224
-
225
254
function parseNamespacedBlockAttribute ( attrNode : AST . Node | null | undefined ) : NamespacedAttr | null {
226
255
if ( ! attrNode || ! isAttrNode ( attrNode ) ) return null ;
227
- if ( / ( [ ^ : ] + ) : ( [ ^ : ] + ) / . test ( attrNode . name ) ) {
256
+ if ( / ( [ ^ : ] + ) : ( [ ^ : ] + | $ ) / . test ( attrNode . name ) ) {
228
257
let ns = RegExp . $1 ;
229
258
let name = RegExp . $2 ;
230
259
if ( isNamespaceReserved ( ns ) ) {
@@ -239,14 +268,12 @@ function isAttrNode(node: FocusPath | AST.Node | NamespacedAttr | null): node is
239
268
return node !== null && ( ( < AST . Node > node ) . type ) === "AttrNode" ;
240
269
}
241
270
242
- function isStateAttribute ( attr : NamespacedAttr | null ) : attr is NamespacedAttr {
243
- if ( attr === null ) return false ;
244
- return attr . name !== SupportedAttributes . class && attr . name !== SupportedAttributes . scope ;
271
+ function isStateAttribute ( attr : NamespacedAttr ) : boolean {
272
+ return attr . name !== AttributeType . class && attr . name !== AttributeType . scope ;
245
273
}
246
274
247
- function isClassAttribute ( attr : NamespacedAttr | null ) : attr is NamespacedAttr {
248
- if ( attr === null ) return false ;
249
- return attr . name === SupportedAttributes . class ;
275
+ function isClassAttribute ( attr : NamespacedAttr ) : boolean {
276
+ return attr . name === AttributeType . class ;
250
277
}
251
278
252
279
// TODO: this will be handy when we add support for the scope attribute.
@@ -281,17 +308,25 @@ export function getItemAtCursor(text: string, position: Position): ItemAtCursor
281
308
282
309
let attr = parseNamespacedBlockAttribute ( attrNode ) ;
283
310
311
+ if ( ! attr ) {
312
+ return {
313
+ attribute : {
314
+ attributeType : AttributeType . ambiguous ,
315
+ name : attrNode . name ,
316
+ } ,
317
+ siblingAttributes : [ ] ,
318
+ } ;
319
+ }
320
+
284
321
if ( isStateAttribute ( attr ) ) {
285
- return getStateAtCursor ( focusRoot ) ;
322
+ return getStateAtCursor ( focusRoot , attr ) ;
286
323
}
287
324
288
325
// TODO: Handle the other types of attribute value nodes
289
326
if ( isClassAttribute ( attr ) && data && data . type === "TextNode" ) {
290
- let blockSegments = buildBlockSegments ( attr , data ) ;
291
- if ( blockSegments ) {
292
- return Object . assign ( {
293
- parentType : SupportedAttributes . class
294
- } , blockSegments ) ;
327
+ let attribute = buildClassAttribute ( attr , data ) ;
328
+ if ( attribute ) {
329
+ return { attribute, siblingAttributes : [ ] } ;
295
330
} else {
296
331
return null ;
297
332
}
@@ -300,29 +335,33 @@ export function getItemAtCursor(text: string, position: Position): ItemAtCursor
300
335
return null ;
301
336
}
302
337
303
- function getStateAtCursor ( focusRoot : FocusPath | null ) {
338
+ function getStateAtCursor ( focusRoot : FocusPath | null , attr : NamespacedAttr ) : ItemAtCursor | null {
304
339
let parentElement = getParentElement ( focusRoot ) ;
305
340
306
341
if ( ! parentElement ) {
307
342
return null ;
308
343
}
309
-
344
+ let attribute : StateAttribute = {
345
+ attributeType : AttributeType . state ,
346
+ referencedBlock : attr . ns === DEFAULT_NAMESPACE ? undefined : attr . ns ,
347
+ name : attr . name
348
+ } ;
310
349
let classAttributes = parentElement . attributes . map ( attrNode => {
311
350
return [ parseNamespacedBlockAttribute ( attrNode ) , attrNode . value ] as const ;
312
351
} ) . filter ( ( [ attr , _attrValue ] ) => {
313
- return isClassAttribute ( attr ) ;
352
+ return attr && isClassAttribute ( attr ) ;
314
353
} ) ;
315
354
316
- let siblingBlocks = classAttributes . map ( ( [ attr , attrValue ] ) => {
317
- return buildBlockSegments ( attr , attrValue ) ;
318
- } ) . filter ( ( bs ) : bs is BlockSegments => {
355
+ let siblingAttributes = classAttributes . map ( ( [ attr , attrValue ] ) => {
356
+ return buildClassAttribute ( attr , attrValue ) ;
357
+ } ) . filter ( ( bs ) : bs is ClassAttribute => {
319
358
return bs !== null ;
320
359
} ) ;
321
360
322
- if ( siblingBlocks . length > 0 ) {
361
+ if ( siblingAttributes . length > 0 ) {
323
362
return {
324
- parentType : SupportedAttributes . state ,
325
- siblingBlocks ,
363
+ attribute ,
364
+ siblingAttributes ,
326
365
} ;
327
366
} else {
328
367
return null ;
0 commit comments