| // Copyright 2025 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /** |
| * @fileoverview A library to identify and templatize manually constructed Toolbars. |
| */ |
| |
| import type {TSESTree} from '@typescript-eslint/utils'; |
| |
| import {isIdentifier, isIdentifierChain, isMemberExpression} from './ast.ts'; |
| import {DomFragment} from './dom-fragment.ts'; |
| |
| type Node = TSESTree.Node; |
| |
| export const toolbar = { |
| create(context) { |
| const sourceCode = context.getSourceCode(); |
| return { |
| getEvent(event: Node): string | |
| null { |
| switch (sourceCode.getText(event)) { |
| case 'UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED': |
| return 'change'; |
| case 'UI.Toolbar.ToolbarInput.Event.ENTER_PRESSED': |
| return 'submit'; |
| case 'UI.Toolbar.ToolbarButton.Events.CLICK': |
| return 'click'; |
| default: |
| return null; |
| } |
| }, |
| methodCall(property: Node, firstArg: Node, _secondArg: Node, domFragment: DomFragment, _call: Node): boolean { |
| if (isIdentifier(property, 'appendToolbarItem')) { |
| domFragment.appendChild(firstArg, sourceCode); |
| return true; |
| } |
| return false; |
| }, |
| NewExpression(node) { |
| const toolbarItem = |
| isMemberExpression(node.callee, n => isIdentifierChain(n, ['UI', 'Toolbar']), n => n.type === 'Identifier'); |
| if (!toolbarItem) { |
| return; |
| } |
| if (isIdentifier(toolbarItem, ['ToolbarFilter', 'ToolbarInput'])) { |
| const domFragment = DomFragment.getOrCreate(node, sourceCode); |
| domFragment.tagName = 'devtools-toolbar-input'; |
| const type = isIdentifier(toolbarItem, 'ToolbarFilter') ? 'filter' : 'text'; |
| domFragment.attributes.push({ |
| key: 'type', |
| value: type, |
| }); |
| const args = [...node.arguments]; |
| const placeholder = args.shift(); |
| if (placeholder && !isIdentifier(placeholder, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'placeholder', |
| value: placeholder, |
| }); |
| } |
| if (type === 'text') { |
| const accesiblePlaceholder = args.shift(); |
| if (accesiblePlaceholder && !isIdentifier(accesiblePlaceholder, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'aria-label', |
| value: accesiblePlaceholder, |
| }); |
| } |
| } |
| const flexGrow = args.shift(); |
| if (flexGrow && !isIdentifier(flexGrow, 'undefined')) { |
| domFragment.style.push({ |
| key: 'flex-grow', |
| value: flexGrow, |
| }); |
| } |
| const flexShrink = args.shift(); |
| if (flexShrink && !isIdentifier(flexShrink, 'undefined')) { |
| domFragment.style.push({ |
| key: 'flex-shrink', |
| value: flexShrink, |
| }); |
| } |
| const title = args.shift(); |
| if (title && !isIdentifier(title, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'title', |
| value: title, |
| }); |
| } |
| const completions = args.shift(); |
| if (completions && !isIdentifier(completions, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'list', |
| value: 'completions', |
| }); |
| const dataList = domFragment.appendChild(completions, sourceCode); |
| dataList.tagName = 'datalist'; |
| dataList.attributes.push({ |
| key: 'id', |
| value: 'completions', |
| }); |
| dataList.textContent = completions; |
| } |
| args.shift(); // dynamicCompletions is not supported |
| const jslogContext = args.shift(); |
| if (jslogContext && !isIdentifier(jslogContext, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'id', |
| value: jslogContext, |
| }); |
| } |
| } |
| if (isIdentifier(toolbarItem, 'ToolbarButton')) { |
| const domFragment = DomFragment.getOrCreate(node, sourceCode); |
| domFragment.tagName = 'devtools-button'; |
| const title = node.arguments[0]; |
| domFragment.bindings.push({ |
| key: 'variant', |
| value: '${Buttons.Button.Variant.TOOLBAR}', |
| }); |
| if (title && !isIdentifier(title, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'title', |
| value: title, |
| }); |
| } |
| const glyph = node.arguments[1]; |
| if (glyph && !isIdentifier(glyph, 'undefined')) { |
| domFragment.bindings.push({ |
| key: 'iconName', |
| value: glyph, |
| }); |
| } |
| const text = node.arguments[2]; |
| if (text && !isIdentifier(text, 'undefined')) { |
| domFragment.textContent = text; |
| } |
| const jslogContext = node.arguments[3]; |
| if (jslogContext && !isIdentifier(jslogContext, 'undefined')) { |
| domFragment.bindings.push({ |
| key: 'jslogContext', |
| value: jslogContext, |
| }); |
| } |
| } |
| if (isIdentifier(toolbarItem, ['ToolbarCheckbox', 'ToolbarSettingCheckbox'])) { |
| const domFragment = DomFragment.getOrCreate(node, sourceCode); |
| domFragment.tagName = 'devtools-checkbox'; |
| const title = node.arguments[0]; |
| if (title && !isIdentifier(title, 'undefined')) { |
| let text: Node|string = title; |
| if (isIdentifier(toolbarItem, 'ToolbarSettingCheckbox')) { |
| domFragment.directives.push({ |
| name: 'bindToSetting', |
| arguments: [title], |
| }); |
| const alternateTitle = node.arguments[2]; |
| if (alternateTitle && !isIdentifier(alternateTitle, 'undefined')) { |
| text = alternateTitle; |
| } else { |
| text = '${' + sourceCode.getText(title) + '.title()}'; |
| } |
| } |
| domFragment.textContent = text; |
| } |
| const tooltip = node.arguments[1]; |
| if (tooltip && !isIdentifier(tooltip, 'undefined')) { |
| domFragment.attributes.push({ |
| key: 'title', |
| value: tooltip, |
| }); |
| } |
| if (isIdentifier(toolbarItem, 'ToolbarCheckbox')) { |
| const listener = node.arguments[2]; |
| if (listener && !isIdentifier(listener, 'undefined')) { |
| domFragment.eventListeners.push({ |
| key: 'click', |
| value: listener, |
| }); |
| } |
| const jslogContext = node.arguments[3]; |
| if (jslogContext && !isIdentifier(jslogContext, 'undefined')) { |
| domFragment.bindings.push({ |
| key: 'jslogContext', |
| value: jslogContext, |
| }); |
| } |
| } |
| } |
| } |
| }; |
| } |
| }; |