44 CssBlockError ,
55 SourceRange ,
66 isNamespaceReserved ,
7+ DEFAULT_NAMESPACE ,
78} from "@css-blocks/core" ;
89import { AST , preprocess , Walker } from "@glimmer/syntax" ;
910import { ElementNode } from "@glimmer/syntax/dist/types/lib/types/nodes" ;
@@ -27,7 +28,7 @@ function walkClasses(astNode: AST.Node, callback: (namespace: string, classAttr:
2728 console . debug ( node ) ;
2829 for ( let attrNode of node . attributes ) {
2930 let nsAttr = parseNamespacedBlockAttribute ( attrNode ) ;
30- if ( isClassAttribute ( nsAttr ) && attrNode . value . type === "TextNode" ) {
31+ if ( nsAttr && isClassAttribute ( nsAttr ) && attrNode . value . type === "TextNode" ) {
3132 callback ( nsAttr . ns , attrNode , attrNode . value ) ;
3233 }
3334 }
@@ -170,20 +171,51 @@ export async function validateTemplates(
170171 } , new Map ( ) ) ;
171172}
172173
173- export const enum SupportedAttributes {
174+ export const enum AttributeType {
174175 state = "state" ,
175176 class = "class" ,
176177 scope = "scope" ,
178+ ambiguous = "ambiguous"
177179}
178180
179- interface BlockSegments {
181+ interface BlockAttributeBase {
182+ attributeType : AttributeType ;
180183 referencedBlock ?: string ;
181- className ?: string ;
182184}
183185
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 [ ] ;
187219}
188220
189221function getParentElement ( focusRoot : FocusPath | null ) : ElementNode | null {
@@ -199,32 +231,29 @@ function getParentElement(focusRoot: FocusPath | null): ElementNode | null {
199231 return null ;
200232}
201233
202- function buildBlockSegments ( attr : NamespacedAttr | null , attrValue : AST . AttrNode [ "value" ] ) : BlockSegments | null {
234+ function buildClassAttribute ( attr : NamespacedAttr | null , attrValue : AST . AttrNode [ "value" ] ) : ClassAttribute | null {
203235 if ( attr === null ) return null ;
204236 if ( attrValue . type === "TextNode" ) {
205237 if ( attr . ns === "block" ) {
206238 return {
207- className : attrValue . chars ,
239+ attributeType : AttributeType . class ,
240+ name : attrValue . chars ,
208241 } ;
209242 } else {
210243 return {
244+ attributeType : AttributeType . class ,
211245 referencedBlock : attr . ns ,
212- className : attrValue . chars ,
246+ name : attrValue . chars ,
213247 } ;
214248 }
215249 } else {
216250 return null ;
217251 }
218252}
219253
220- interface NamespacedAttr {
221- ns : string ;
222- name : string ;
223- }
224-
225254function parseNamespacedBlockAttribute ( attrNode : AST . Node | null | undefined ) : NamespacedAttr | null {
226255 if ( ! attrNode || ! isAttrNode ( attrNode ) ) return null ;
227- if ( / ( [ ^ : ] + ) : ( [ ^ : ] + ) / . test ( attrNode . name ) ) {
256+ if ( / ( [ ^ : ] + ) : ( [ ^ : ] + | $ ) / . test ( attrNode . name ) ) {
228257 let ns = RegExp . $1 ;
229258 let name = RegExp . $2 ;
230259 if ( isNamespaceReserved ( ns ) ) {
@@ -239,14 +268,12 @@ function isAttrNode(node: FocusPath | AST.Node | NamespacedAttr | null): node is
239268 return node !== null && ( ( < AST . Node > node ) . type ) === "AttrNode" ;
240269}
241270
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 ;
245273}
246274
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 ;
250277}
251278
252279// 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
281308
282309 let attr = parseNamespacedBlockAttribute ( attrNode ) ;
283310
311+ if ( ! attr ) {
312+ return {
313+ attribute : {
314+ attributeType : AttributeType . ambiguous ,
315+ name : attrNode . name ,
316+ } ,
317+ siblingAttributes : [ ] ,
318+ } ;
319+ }
320+
284321 if ( isStateAttribute ( attr ) ) {
285- return getStateAtCursor ( focusRoot ) ;
322+ return getStateAtCursor ( focusRoot , attr ) ;
286323 }
287324
288325 // TODO: Handle the other types of attribute value nodes
289326 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 : [ ] } ;
295330 } else {
296331 return null ;
297332 }
@@ -300,29 +335,33 @@ export function getItemAtCursor(text: string, position: Position): ItemAtCursor
300335 return null ;
301336}
302337
303- function getStateAtCursor ( focusRoot : FocusPath | null ) {
338+ function getStateAtCursor ( focusRoot : FocusPath | null , attr : NamespacedAttr ) : ItemAtCursor | null {
304339 let parentElement = getParentElement ( focusRoot ) ;
305340
306341 if ( ! parentElement ) {
307342 return null ;
308343 }
309-
344+ let attribute : StateAttribute = {
345+ attributeType : AttributeType . state ,
346+ referencedBlock : attr . ns === DEFAULT_NAMESPACE ? undefined : attr . ns ,
347+ name : attr . name
348+ } ;
310349 let classAttributes = parentElement . attributes . map ( attrNode => {
311350 return [ parseNamespacedBlockAttribute ( attrNode ) , attrNode . value ] as const ;
312351 } ) . filter ( ( [ attr , _attrValue ] ) => {
313- return isClassAttribute ( attr ) ;
352+ return attr && isClassAttribute ( attr ) ;
314353 } ) ;
315354
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 => {
319358 return bs !== null ;
320359 } ) ;
321360
322- if ( siblingBlocks . length > 0 ) {
361+ if ( siblingAttributes . length > 0 ) {
323362 return {
324- parentType : SupportedAttributes . state ,
325- siblingBlocks ,
363+ attribute ,
364+ siblingAttributes ,
326365 } ;
327366 } else {
328367 return null ;
0 commit comments