chrome-devtools-frontend
Version:
Chrome DevTools UI
154 lines (150 loc) • 6.81 kB
text/typescript
// 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 DataGrid.
*/
import type {TSESTree} from '@typescript-eslint/utils';
import {isIdentifier, isIdentifierChain, isMemberExpression} from './ast.ts';
import {DomFragment} from './dom-fragment.ts';
type Identifier = TSESTree.Identifier;
type Node = TSESTree.Node;
type CallExpression = TSESTree.CallExpression;
export const dataGrid = {
create(context) {
const sourceCode = context.getSourceCode();
return {
methodCall(
property: Identifier, firstArg: Node, _secondArg: Node|undefined, domFragment: DomFragment,
_call: CallExpression) {
if (domFragment.tagName !== 'devtools-data-grid') {
return false;
}
if (isIdentifier(property, 'setRowContextMenuCallback')) {
domFragment.eventListeners.push({
key: 'contextmenu',
value: firstArg,
});
return true;
}
if (isIdentifier(property, 'setStriped')) {
domFragment.booleanAttributes.push({
key: 'striped',
value: firstArg,
});
return true;
}
if (isIdentifier(property, 'renderInline')) {
domFragment.booleanAttributes.push({
key: 'inline',
value: 'true',
});
return true;
}
return false;
},
getEvent(event: Node): string |
null {
switch (sourceCode.getText(event)) {
case 'DataGrid.DataGrid.Events.SELECTED_NODE':
case 'DataGrid.DataGrid.Events.DESELECTED_NODE':
return 'select';
case 'DataGrid.DataGrid.Events.SORTING_CHANGED':
return 'sort';
default:
return null;
}
},
NewExpression(node) {
if (isIdentifierChain(node.callee, ['DataGrid', 'DataGrid', 'DataGridImpl']) ||
isIdentifierChain(node.callee, ['DataGrid', 'ViewportDataGrid', 'ViewportDataGrid']) ||
isIdentifierChain(node.callee, ['DataGrid', 'SortableDataGrid', 'SortableDataGrid'])) {
const domFragment = DomFragment.getOrCreate(node, sourceCode);
domFragment.tagName = 'devtools-data-grid';
if (!node.arguments.length) {
return;
}
const tableFragment = domFragment.appendChild(node.arguments[0], sourceCode);
tableFragment.tagName = 'table';
const params = tableFragment.initializer ?? node.arguments[0];
if (params.type !== 'ObjectExpression') {
return;
}
for (const property of params.properties) {
if (property.type !== 'Property') {
continue;
}
if (isIdentifier(property.key, 'displayName')) {
domFragment.attributes.push({key: 'name', value: property.value});
} else if (isIdentifier(property.key, 'columns')) {
const columnsFragment = tableFragment.appendChild(property.value, sourceCode);
columnsFragment.tagName = 'tr';
const columns = columnsFragment.initializer ?? property.value;
if (columns?.type !== 'ArrayExpression') {
continue;
}
for (const column of columns.elements) {
if (column?.type !== 'ObjectExpression') {
continue;
}
const columnFragment = columnsFragment.appendChild(column, sourceCode);
columnFragment.tagName = 'th';
for (const property of column.properties) {
if (property.type !== 'Property' || isIdentifier(property.value, 'undefined')) {
continue;
}
if (isIdentifier(property.key, ['id', 'weight', 'width'])) {
columnFragment.attributes.push({key: property.key.name, value: property.value});
} else if (isIdentifier(property.key, 'align')) {
const value: Node|string|null = isMemberExpression(
property.value, n => isIdentifierChain(n, ['DataGrid', 'DataGrid', 'Align']),
n => n.type === 'Identifier');
columnFragment.attributes.push({
key: property.key.name,
value: value ? (value as Identifier).name.toLowerCase() : property.value
});
} else if (isIdentifier(property.key, ['sortable', 'editable'])) {
columnFragment.booleanAttributes.push({key: property.key.name, value: property.value});
} else if (isIdentifier(property.key, 'fixedWidth')) {
columnFragment.booleanAttributes.push({key: 'fixed', value: property.value});
} else if (isIdentifier(property.key, 'dataType')) {
const value: Node|string|null = isMemberExpression(
property.value, n => isIdentifierChain(n, ['DataGrid', 'DataGrid', 'DataType']),
n => n.type === 'Identifier');
columnFragment.attributes.push({
key: property.key.name,
value: value ? (value as Identifier).name.toLowerCase() : property.value
});
} else if (isIdentifier(property.key, ['title', 'titleDOMFragment'])) {
columnFragment.textContent = property.value;
}
}
}
} else if (isIdentifier(property.key, 'refreshCallback') && !isIdentifier(property.value, 'undefined')) {
domFragment.eventListeners.push({
key: 'refresh',
value: property.value,
});
} else if (isIdentifier(property.key, 'deleteCallback') && !isIdentifier(property.value, 'undefined')) {
domFragment.eventListeners.push({
key: 'delete',
value: property.value,
});
}
}
}
},
CallExpression(node: CallExpression) {
if (node.callee.type !== 'MemberExpression' ||
!isIdentifier(node.callee.property, ['createTD', 'createTDWithClass', 'createCell'])) {
return;
}
const domFragment = DomFragment.getOrCreate(node, sourceCode);
domFragment.tagName = 'td';
if (isIdentifier(node.callee.property, 'createTDWithClass')) {
domFragment.classList.push(node.arguments[0]);
}
},
};
}
};