Skip to content

Commit 469b818

Browse files
danilsomsikovDevtools-frontend LUCI CQ
authored andcommitted
Support createChild and toolbar items in the preferTemplateLiterals rule
Bug: 400353541 Change-Id: I27a18bb4829336dfaba4dcdd697c84d6591fb60e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6357270 Commit-Queue: Philip Pfaffe <[email protected]> Reviewed-by: Philip Pfaffe <[email protected]> Auto-Submit: Danil Somsikov <[email protected]> Commit-Queue: Danil Somsikov <[email protected]>
1 parent f916795 commit 469b818

File tree

2 files changed

+147
-3
lines changed

2 files changed

+147
-3
lines changed

scripts/eslint_rules/lib/no-imperative-dom-api.js

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ function isIdentifier(node, name) {
1313
return node.type === 'Identifier' && (Array.isArray(name) ? name.includes(node.name) : node.name === name);
1414
}
1515

16+
function isMemberExpression(node, objectPredicate, propertyPredicate) {
17+
return node.type === 'MemberExpression' && objectPredicate(node.object) && propertyPredicate(node.property);
18+
}
19+
1620
function getEnclosingExpression(node) {
1721
while (node.parent) {
1822
if (node.parent.type === 'BlockStatement') {
@@ -199,6 +203,24 @@ module.exports = {
199203
return null;
200204
}
201205

206+
/**
207+
* @param {Node} event
208+
* @return {string|null}
209+
*/
210+
function getEvent(event) {
211+
switch (sourceCode.getText(event)) {
212+
case 'UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED':
213+
return 'change';
214+
case 'UI.Toolbar.ToolbarInput.Event.ENTER_PRESSED':
215+
return 'submit';
216+
default:
217+
if (event.type === 'Literal') {
218+
return event.value.toString();
219+
}
220+
return null;
221+
}
222+
}
223+
202224
/**
203225
* @param {DomFragmentReference} reference
204226
* @param {DomFragment} domFragment
@@ -237,11 +259,15 @@ module.exports = {
237259
domFragment.attributes.push({key: attribute.value.toString(), value});
238260
}
239261
} else if (isMethodCall && isIdentifier(property, 'addEventListener')) {
240-
const event = firstArg;
262+
const event = getEvent(firstArg);
241263
const value = secondArg;
242-
if (event.type === 'Literal' && value.type !== 'SpreadElement') {
243-
domFragment.eventListeners.push({key: event.value.toString(), value});
264+
if (event && value.type !== 'SpreadElement') {
265+
domFragment.eventListeners.push({key: event, value});
244266
}
267+
} else if (isMethodCall && isIdentifier(property, 'appendToolbarItem')) {
268+
const childFragment = getOrCreateDomFragment(firstArg);
269+
childFragment.parent = domFragment;
270+
domFragment.children.push(childFragment);
245271
} else if (
246272
isPropertyMethodCall && isIdentifier(property, 'classList') && isIdentifier(grandParent.property, 'add')) {
247273
domFragment.classList.push(propertyMethodArgument);
@@ -253,6 +279,16 @@ module.exports = {
253279
value: subpropertyValue,
254280
});
255281
}
282+
} else if (isMethodCall && isIdentifier(property, 'createChild')) {
283+
if (firstArg?.type === 'Literal') {
284+
const childFragment = getOrCreateDomFragment(grandParent);
285+
childFragment.tagName = String(firstArg.value);
286+
childFragment.parent = domFragment;
287+
domFragment.children.push(childFragment);
288+
if (secondArg) {
289+
childFragment.classList.push(secondArg);
290+
}
291+
}
256292
} else if (isMethodCall && isIdentifier(property, 'appendChild')) {
257293
const childFragment = getOrCreateDomFragment(firstArg);
258294
childFragment.parent = domFragment;
@@ -310,6 +346,53 @@ export const DEFAULT_VIEW = (input, _output, target) => {
310346
}
311347
}
312348
},
349+
NewExpression(node) {
350+
if (isMemberExpression(
351+
node.callee, n => isMemberExpression(n, n => isIdentifier(n, 'UI'), n => isIdentifier(n, 'Toolbar')),
352+
n => isIdentifier(n, 'ToolbarFilter'))) {
353+
const domFragment = getOrCreateDomFragment(node);
354+
domFragment.tagName = 'devtools-toolbar-input';
355+
domFragment.attributes.push({
356+
key: 'type',
357+
value: /** @type {Node} */ ({type: 'Literal', value: 'filter'}),
358+
});
359+
const placeholder = node.arguments[0];
360+
const flexGrow = node.arguments[1];
361+
const flexShrink = node.arguments[2];
362+
const title = node.arguments[3];
363+
const jslogContext = node.arguments[6];
364+
if (placeholder && !isIdentifier(placeholder, 'undefined')) {
365+
domFragment.attributes.push({
366+
key: 'placeholder',
367+
value: placeholder,
368+
});
369+
}
370+
if (flexGrow && !isIdentifier(flexGrow, 'undefined')) {
371+
domFragment.style.push({
372+
key: 'flex-grow',
373+
value: flexGrow,
374+
});
375+
}
376+
if (flexShrink && !isIdentifier(flexShrink, 'undefined')) {
377+
domFragment.style.push({
378+
key: 'flex-shrink',
379+
value: flexShrink,
380+
});
381+
}
382+
if (title && !isIdentifier(title, 'undefined')) {
383+
domFragment.attributes.push({
384+
key: 'title',
385+
value: title,
386+
});
387+
}
388+
if (jslogContext && !isIdentifier(jslogContext, 'undefined')) {
389+
domFragment.attributes.push({
390+
key: 'id',
391+
value: jslogContext,
392+
});
393+
}
394+
}
395+
},
313396
'Program:exit'() {
314397
while (queue.length) {
315398
const domFragment = queue.pop();

scripts/eslint_rules/tests/no-imperative-dom-api.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,67 @@ class SomeWidget extends UI.Widget.Widget {
149149
const div = document.createElement('div');
150150
div.className = 'some-class';
151151
}
152+
}`,
153+
errors: [{messageId: 'preferTemplateLiterals'}],
154+
},
155+
{
156+
filename: 'front_end/ui/components/component/file.ts',
157+
code: `
158+
class SomeWidget extends UI.Widget.Widget {
159+
constructor() {
160+
super();
161+
this.contentElement.createChild('span', 'some-class');
162+
}
163+
}`,
164+
output: `
165+
166+
export const DEFAULT_VIEW = (input, _output, target) => {
167+
render(html\`
168+
<div>
169+
<span class="some-class"></span>
170+
</div>\`,
171+
target, {host: input});
172+
};
173+
174+
class SomeWidget extends UI.Widget.Widget {
175+
constructor() {
176+
super();
177+
}
178+
}`,
179+
errors: [{messageId: 'preferTemplateLiterals'}],
180+
},
181+
{
182+
filename: 'front_end/ui/components/component/file.ts',
183+
code: `
184+
class SomeWidget extends UI.Widget.Widget {
185+
constructor() {
186+
super();
187+
const toolbar = this.contentElement.createChild('devtools-toolbar');
188+
const filterInput = new UI.Toolbar.ToolbarFilter('some-placeholder', 0.5, 1, undefined, undefined, false, 'some-filter');
189+
filterInput.addEventListener(UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED, this.onFilterChanged.bind(this));
190+
toolbar.appendToolbarItem(filterInput);
191+
}
192+
}`,
193+
output: `
194+
195+
export const DEFAULT_VIEW = (input, _output, target) => {
196+
render(html\`
197+
<div>
198+
<devtools-toolbar>
199+
<devtools-toolbar-input type="filter" placeholder="some-placeholder" id="some-filter"
200+
@change=\${this.onFilterChanged.bind(this)} style="flex-grow:0.5; flex-shrink:1"></devtools-toolbar-input>
201+
</devtools-toolbar>
202+
</div>\`,
203+
target, {host: input});
204+
};
205+
206+
class SomeWidget extends UI.Widget.Widget {
207+
constructor() {
208+
super();
209+
const filterInput = new UI.Toolbar.ToolbarFilter('some-placeholder', 0.5, 1, undefined, undefined, false, 'some-filter');
210+
filterInput.addEventListener(UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED, this.onFilterChanged.bind(this));
211+
toolbar.appendToolbarItem(filterInput);
212+
}
152213
}`,
153214
errors: [{messageId: 'preferTemplateLiterals'}],
154215
},

0 commit comments

Comments
 (0)