99 */
1010'use strict' ;
1111
12+ /**
13+ * @param {Node } node
14+ * @param {string|Array<string> } name
15+ * @return {boolean }
16+ */
1217function isIdentifier ( node , name ) {
1318 return node . type === 'Identifier' && ( Array . isArray ( name ) ? name . includes ( node . name ) : node . name === name ) ;
1419}
1520
21+ /**
22+ * @param {Node } node
23+ * @param {function(Node): boolean } objectPredicate
24+ * @param {function(Node): boolean } propertyPredicate
25+ */
1626function isMemberExpression ( node , objectPredicate , propertyPredicate ) {
17- return node . type === 'MemberExpression' && objectPredicate ( node . object ) && propertyPredicate ( node . property ) ;
27+ return node . type === 'MemberExpression' && objectPredicate ( /** @type {Node } */ ( node . object ) ) &&
28+ propertyPredicate ( /** @type {Node } */ ( node . property ) ) ;
1829}
1930
31+ /** @param {Node } node */
2032function getEnclosingExpression ( node ) {
2133 while ( node . parent ) {
2234 if ( node . parent . type === 'BlockStatement' ) {
@@ -27,6 +39,7 @@ function getEnclosingExpression(node) {
2739 return null ;
2840}
2941
42+ /** @param {Node } node */
3043function getEnclosingClassDeclaration ( node ) {
3144 let parent = node . parent ;
3245 while ( parent && parent . type !== 'ClassDeclaration' ) {
@@ -35,6 +48,7 @@ function getEnclosingClassDeclaration(node) {
3548 return parent ;
3649}
3750
51+ /** @param {string } outputString */
3852function attributeValue ( outputString ) {
3953 if ( outputString . startsWith ( '${' ) && outputString . endsWith ( '}' ) ) {
4054 return outputString ;
@@ -50,9 +64,10 @@ function attributeValue(outputString) {
5064class DomFragment {
5165 /** @type {string|undefined } */ tagName ;
5266 /** @type {Node[] } */ classList = [ ] ;
53- /** @type {{key: string, value: Node}[] } */ attributes = [ ] ;
67+ /** @type {{key: string, value: Node|string }[] } */ attributes = [ ] ;
5468 /** @type {{key: string, value: Node}[] } */ style = [ ] ;
5569 /** @type {{key: string, value: Node}[] } */ eventListeners = [ ] ;
70+ /** @type {{key: string, value: Node|string}[] } */ bindings = [ ] ;
5671 /** @type {Node } */ textContent ;
5772 /** @type {DomFragment[] } */ children = [ ] ;
5873 /** @type {DomFragment|undefined } */ parent ;
@@ -65,9 +80,18 @@ class DomFragment {
6580 if ( this . expression && ! this . tagName ) {
6681 return [ `\n${ ' ' . repeat ( indent ) } ` , '${' , this . expression , '}' ] ;
6782 }
68- function toOutputString ( node ) {
69- if ( node . type === 'Literal' ) {
70- return node . value ;
83+
84+ /**
85+ * @param {Node|string } node
86+ * @param {boolean } quoteLiterals
87+ * @return {string }
88+ */
89+ function toOutputString ( node , quoteLiterals = false ) {
90+ if ( typeof node === 'string' ) {
91+ return node ;
92+ }
93+ if ( node . type === 'Literal' && ! quoteLiterals ) {
94+ return node . value . toString ( ) ;
7195 }
7296 const text = sourceCode . getText ( node ) ;
7397 if ( node . type === 'TemplateLiteral' ) {
@@ -97,14 +121,17 @@ class DomFragment {
97121 lineLength += this . tagName . length + 1 ;
98122 }
99123 if ( this . classList . length ) {
100- appendExpression ( `class="${ this . classList . map ( toOutputString ) . join ( ' ' ) } "` ) ;
124+ appendExpression ( `class="${ this . classList . map ( c => toOutputString ( c ) ) . join ( ' ' ) } "` ) ;
101125 }
102126 for ( const attribute of this . attributes || [ ] ) {
103127 appendExpression ( `${ attribute . key } =${ attributeValue ( toOutputString ( attribute . value ) ) } ` ) ;
104128 }
105129 for ( const eventListener of this . eventListeners || [ ] ) {
106130 appendExpression ( `@${ eventListener . key } =${ attributeValue ( toOutputString ( eventListener . value ) ) } ` ) ;
107131 }
132+ for ( const binding of this . bindings || [ ] ) {
133+ appendExpression ( `.${ binding . key } =${ toOutputString ( binding . value , /* quoteLiterals=*/ true ) } ` ) ;
134+ }
108135 if ( this . style . length ) {
109136 const style = this . style . map ( s => `${ s . key } :${ toOutputString ( s . value ) } ` ) . join ( '; ' ) ;
110137 appendExpression ( `style="${ style } "` ) ;
@@ -228,7 +255,7 @@ module.exports = {
228255 function processReference ( reference , domFragment ) {
229256 const parent = reference . parent ;
230257 const isAccessed = parent . type === 'MemberExpression' && parent . object === reference ;
231- const property = isAccessed ? parent . property : null ;
258+ const property = isAccessed ? /** @type { Node } */ ( parent . property ) : null ;
232259 const grandParent = parent . parent ;
233260 const isPropertyAssignment =
234261 isAccessed && grandParent . type === 'AssignmentExpression' && grandParent . left === parent ;
@@ -258,20 +285,18 @@ module.exports = {
258285 if ( property . type !== 'Property' ) {
259286 continue ;
260287 }
261- if ( isIdentifier ( property . key , 'name' ) ) {
288+ const key = /** @type {Node } */ ( property . key ) ;
289+ if ( isIdentifier ( key , 'name' ) ) {
262290 domFragment . attributes . push ( {
263291 key : 'aria-label' ,
264292 value : /** @type {Node } */ ( property . value ) ,
265293 } ) ;
266294 }
267- if ( isIdentifier ( property . key , 'jslogContext' ) ) {
268- domFragment . attributes . push ( {
269- key : 'jslog' ,
270- value : /** @type {Node } */ (
271- { type : 'Literal' , value : '${VisualLogging.adorner(' + sourceCode . getText ( property . value ) + ')}' } )
272- } ) ;
295+ if ( isIdentifier ( key , 'jslogContext' ) ) {
296+ domFragment . attributes . push (
297+ { key : 'jslog' , value : '${VisualLogging.adorner(' + sourceCode . getText ( property . value ) + ')}' } ) ;
273298 }
274- if ( isIdentifier ( property . key , 'content' ) ) {
299+ if ( isIdentifier ( key , 'content' ) ) {
275300 const childFragment = getOrCreateDomFragment ( /** @type {Node } */ ( property . value ) ) ;
276301 childFragment . parent = domFragment ;
277302 domFragment . children . push ( childFragment ) ;
@@ -294,7 +319,8 @@ module.exports = {
294319 childFragment . parent = domFragment ;
295320 domFragment . children . push ( childFragment ) ;
296321 } else if (
297- isPropertyMethodCall && isIdentifier ( property , 'classList' ) && isIdentifier ( grandParent . property , 'add' ) ) {
322+ isPropertyMethodCall && isIdentifier ( property , 'classList' ) &&
323+ isIdentifier ( /** @type {Node } */ ( grandParent . property ) , 'add' ) ) {
298324 domFragment . classList . push ( propertyMethodArgument ) ;
299325 } else if ( isSubpropertyAssignment && isIdentifier ( property , 'style' ) ) {
300326 const property = subproperty . name . replace ( / ( [ a - z ] ) ( [ A - Z ] ) / g, '$1-$2' ) . toLowerCase ( ) ;
@@ -405,7 +431,7 @@ export const DEFAULT_VIEW = (input, _output, target) => {
405431 const type = isIdentifier ( node . callee . property , 'ToolbarFilter' ) ? 'filter' : 'text' ;
406432 domFragment . attributes . push ( {
407433 key : 'type' ,
408- value : /** @ type { Node } */ ( { type : 'Literal' , value : type } ) ,
434+ value : type ,
409435 } ) ;
410436 const args = [ ...node . arguments ] ;
411437 const placeholder = args . shift ( ) ;
@@ -449,13 +475,13 @@ export const DEFAULT_VIEW = (input, _output, target) => {
449475 if ( completions && ! isIdentifier ( completions , 'undefined' ) ) {
450476 domFragment . attributes . push ( {
451477 key : 'list' ,
452- value : /** @type { Node } */ ( { type : 'Literal' , value : ' completions'} ) ,
478+ value : ' completions',
453479 } ) ;
454480 const dataList = getOrCreateDomFragment ( completions ) ;
455481 dataList . tagName = 'datalist' ;
456482 dataList . attributes . push ( {
457483 key : 'id' ,
458- value : /** @type { Node } */ ( { type : 'Literal' , value : ' completions'} ) ,
484+ value : ' completions',
459485 } ) ;
460486 dataList . textContent = completions ;
461487 domFragment . children . push ( dataList ) ;
@@ -477,6 +503,41 @@ export const DEFAULT_VIEW = (input, _output, target) => {
477503 const domFragment = getOrCreateDomFragment ( node ) ;
478504 domFragment . tagName = 'devtools-adorner' ;
479505 }
506+ if ( isMemberExpression (
507+ node . callee , n => isMemberExpression ( n , n => isIdentifier ( n , 'UI' ) , n => isIdentifier ( n , 'Toolbar' ) ) ,
508+ n => isIdentifier ( n , 'ToolbarButton' ) ) ) {
509+ const domFragment = getOrCreateDomFragment ( node ) ;
510+ domFragment . tagName = 'devtools-button' ;
511+ const title = node . arguments [ 0 ] ;
512+ domFragment . bindings . push ( {
513+ key : 'variant' ,
514+ value : '${Buttons.Button.Variant.TOOLBAR}' ,
515+ } ) ;
516+ if ( title && ! isIdentifier ( title , 'undefined' ) ) {
517+ domFragment . attributes . push ( {
518+ key : 'title' ,
519+ value : title ,
520+ } ) ;
521+ }
522+ const glyph = node . arguments [ 1 ] ;
523+ if ( glyph && ! isIdentifier ( glyph , 'undefined' ) ) {
524+ domFragment . bindings . push ( {
525+ key : 'iconName' ,
526+ value : glyph ,
527+ } ) ;
528+ }
529+ const text = node . arguments [ 2 ] ;
530+ if ( text && ! isIdentifier ( text , 'undefined' ) ) {
531+ domFragment . textContent = text ;
532+ }
533+ const jslogContext = node . arguments [ 3 ] ;
534+ if ( jslogContext && ! isIdentifier ( jslogContext , 'undefined' ) ) {
535+ domFragment . bindings . push ( {
536+ key : 'jslogContext' ,
537+ value : jslogContext ,
538+ } ) ;
539+ }
540+ }
480541 } ,
481542 'Program:exit' ( ) {
482543 while ( queue . length ) {
0 commit comments