UNPKG

chrome-devtools-frontend

Version:
214 lines (209 loc) • 7.83 kB
// 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 UI.UIUtils and related calls. */ import type {TSESTree} from '@typescript-eslint/utils'; import {isIdentifier, isIdentifierChain, isMemberExpression} from './ast.ts'; import {DomFragment} from './dom-fragment.ts'; type CallExpression = TSESTree.CallExpression; type Node = TSESTree.Node; export const uiUtils = { create(context) { const sourceCode = context.getSourceCode(); return { CallExpression(node: CallExpression) { let func = isMemberExpression( node.callee, n => isIdentifierChain(n, ['UI', 'UIUtils', 'CheckboxLabel']), n => isIdentifier(n, ['create', 'createWithStringLiteral'])); if (func) { const domFragment = DomFragment.getOrCreate(node, sourceCode); domFragment.tagName = 'devtools-checkbox'; const args = node.arguments; const title = args.shift(); if (title && !isIdentifier(title, 'undefined')) { domFragment.textContent = title; } const checked = args.shift(); if (checked && !isIdentifier(checked, 'undefined')) { domFragment.booleanAttributes.push({ key: 'checked', value: checked, }); } if (isIdentifier(func, 'create')) { args.shift(); // TODO(b/348173254): Support subtitle } const jslogContext = args.shift(); if (jslogContext && !isIdentifier(jslogContext, 'undefined')) { domFragment.bindings.push({ key: 'jslogContext', value: jslogContext, }); } const small = args.shift(); if (small && !isIdentifier(small, 'undefined')) { domFragment.classList.push('small'); } } func = isMemberExpression(node.callee, n => isIdentifierChain(n, ['UI', 'UIUtils']), n => n.type === 'Identifier'); if (!func) { return; } if (isIdentifier(func, 'createLabel')) { const domFragment = DomFragment.getOrCreate(node, sourceCode); domFragment.tagName = 'label'; const title = node.arguments[0]; if (title) { domFragment.textContent = title; } const className = node.arguments[1]; if (className) { domFragment.classList.push(className); } const associatedControl = node.arguments[2]; if (associatedControl) { domFragment.appendChild(associatedControl, sourceCode); } } if (isIdentifier(func, 'createTextButton')) { const opts = node.arguments[2]; if (opts && opts.type !== 'ObjectExpression') { return; } const domFragment = DomFragment.getOrCreate(node, sourceCode); domFragment.tagName = 'devtools-button'; const text = node.arguments[0]; if (text) { domFragment.textContent = text; } const clickHandler = node.arguments[1]; if (clickHandler) { domFragment.eventListeners.push({ key: 'click', value: clickHandler, }); } let variant: string|Node = 'Buttons.Button.Variant.OUTLINED'; if (opts) { for (const property of opts.properties) { if (property.type !== 'Property') { continue; } if (isIdentifier(property.key, 'className')) { domFragment.classList.push(property.value); } if (isIdentifier(property.key, 'jslogContext')) { domFragment.bindings.push({ key: 'jslogContext', value: property.value, }); } if (isIdentifier(property.key, 'variant')) { variant = property.value; } if (isIdentifier(property.key, 'title')) { domFragment.attributes.push({ key: 'title', value: property.value, }); } if (isIdentifier(property.key, 'icon')) { domFragment.bindings.push({ key: 'iconName', value: property.value, }); } } } domFragment.bindings.push({ key: 'variant', value: variant, }); } if (isIdentifier(func, 'createInput')) { const domFragment = DomFragment.getOrCreate(node, sourceCode); domFragment.tagName = 'input'; domFragment.attributes.push({ key: 'spellcheck', value: 'false', }); domFragment.classList.push('harmony-input'); const className = node.arguments[0]; if (className && !isIdentifier(className, 'undefined')) { domFragment.classList.push(className); } const type = node.arguments[1]; if (type && !isIdentifier(type, 'undefined')) { domFragment.attributes.push({ key: 'type', value: type, }); } const jslogContext = node.arguments[2]; if (jslogContext && !isIdentifier(jslogContext, 'undefined')) { domFragment.attributes.push({ key: 'jslog', value: '${VisualLogging.textField(' + sourceCode.getText(jslogContext) + ').track({keydown: \'Enter\', change: true})}' }); } } if (isIdentifier(func, 'createOption')) { const domFragment = DomFragment.getOrCreate(node, sourceCode); domFragment.tagName = 'option'; const title = node.arguments[0]; if (title) { domFragment.textContent = title; } const value = node.arguments[1]; if (value) { domFragment.attributes.push({ key: 'value', value, }); } const jslogContext = node.arguments[1]; if (jslogContext) { domFragment.attributes.push({ key: 'jslog', value: '${VisualLogging.dropDown(' + sourceCode.getText(jslogContext) + ').track({click: true})}', }); } } }, functionCall(call: CallExpression, _firstArg: Node, secondArg: Node, domFragment: DomFragment): boolean { if (isIdentifierChain(call.callee, ['UI', 'SettingsUI', 'bindCheckbox'])) { let setting = secondArg; if (setting.type === 'CallExpression' && isMemberExpression( setting.callee, n => n.type === 'CallExpression' && isIdentifierChain(n.callee, ['Common', 'Settings', 'Settings', 'instance']), n => isIdentifier(n, 'moduleSetting'))) { setting = setting.arguments[0]; } domFragment.directives.push({ name: 'bindToSetting', arguments: [setting], }); return true; } if (isIdentifierChain(call.callee, ['UI', 'UIUtils', 'createTextChild'])) { domFragment.textContent = secondArg; return true; } if (isIdentifierChain(call.callee, ['UI', 'Tooltip', 'Tooltip', 'install'])) { domFragment.attributes.push({ key: 'title', value: secondArg, }); return true; } return false; }, }; } };