Skip to content

Commit 4a1e72d

Browse files
committed
fix(lint): support framework event bindings
Signed-off-by: John Yanarella <jyanarella@nvidia.com>
1 parent b6c09ba commit 4a1e72d

2 files changed

Lines changed: 85 additions & 3 deletions

File tree

‎projects/lint/src/eslint/rules/no-invalid-event-listeners.test.ts‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,52 @@ describe('noInvalidEventListeners', () => {
5757
valid: [
5858
'<button @click="handleClick">Click</button>',
5959
'<button (click)="handleClick()">Click</button>',
60-
'<button v-on:click="handleClick">Click</button>'
60+
'<button v-on:click="handleClick">Click</button>',
61+
'<button onclick="${handleClick}">Click</button>',
62+
'<button onclick="{{handleClick}}">Click</button>',
63+
'<button onclick="{handleClick}">Click</button>',
64+
// React synthetic event binding
65+
'<button onClick={handleClick}>Click</button>',
66+
// React custom element native event binding
67+
`function SharedDropdown() {
68+
return (
69+
<nve-dropdown id="row-actions-dropdown" alignment="end" ontoggle={() => undefined}>
70+
<nve-menu>
71+
<nve-menu-item>action 1</nve-menu-item>
72+
<nve-menu-item>action 2</nve-menu-item>
73+
<nve-menu-item>action 3</nve-menu-item>
74+
</nve-menu>
75+
</nve-dropdown>
76+
);
77+
}`
6178
],
6279
invalid: []
6380
});
6481
});
6582

83+
it('should defer errors for incomplete event handler bindings',()=>{
84+
tester.run('incomplete event handler bindings',rule,{
85+
valid: [
86+
'<button onClick=',
87+
'<button onClick={',
88+
'<button onClick={()',
89+
'<button onClick="',
90+
'<button onClick="${',
91+
'<button onClick="{{'
92+
],
93+
invalid: [
94+
{
95+
code: '<button onClick=handleClick',
96+
errors: [{messageId: 'no-inline-event-handler',data: {attribute: 'onClick'}}]
97+
},
98+
{
99+
code: '<button onClick="handleClick',
100+
errors: [{messageId: 'no-inline-event-handler',data: {attribute: 'onClick'}}]
101+
}
102+
]
103+
});
104+
});
105+
66106
it('should report inline onclick handler',()=>{
67107
tester.run('should report inline onclick handler',rule,{
68108
valid: [],

‎projects/lint/src/eslint/rules/no-invalid-event-listeners.ts‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,50 @@
33

44
importtype{Rule}from'eslint';
55
import{createVisitors}from'@html-eslint/eslint-plugin/lib/rules/utils/visitors.js';
6-
importtype{HtmlTagNode}from'../rule-types.js';
6+
importtype{HtmlAttribute,HtmlTagNode}from'../rule-types.js';
77

88
declareconst__ELEMENTS_PAGES_BASE_URL__: string;
99
constINLINE_EVENT_HANDLER=/^on[a-z]+$/i;
10+
constDATA_BINDING_PATTERNS=[
11+
/\$\{[^}]*\}/,// ${...} - JavaScript template literals
12+
/\{\{[^}]*\}\}/,// {{...}} - Vue, Angular, Handlebars
13+
/^\{[^}]+\}$/// {...} - JSX/React expressions (entire value is an expression)
14+
];
15+
16+
functionhasDataBinding(attribute: HtmlAttribute): boolean{
17+
constvalue=attribute.value?.value;
18+
if(!value)returnfalse;
19+
20+
if(DATA_BINDING_PATTERNS.some(pattern=>pattern.test(value))){
21+
returntrue;
22+
}
23+
24+
// NOTE: The HTML parser truncates unquoted JSX callbacks to a value beginning with "{".
25+
return!attribute.startWrapper&&value.startsWith('{');
26+
}
27+
28+
// Defer lint errors while developers are typing an event binding in the editor.
29+
functionisIncompleteEventHandler(attribute: HtmlAttribute,sourceText: string): boolean{
30+
if(attribute.value||!attribute.range||sourceText[attribute.range[1]]!=='='){
31+
returnfalse;
32+
}
33+
34+
constvalue=sourceText.slice(attribute.range[1]+1);
35+
if(!value){
36+
returntrue;
37+
}
38+
39+
if(value.startsWith('{')||value.startsWith('$')){
40+
returntrue;
41+
}
42+
43+
if(!value.startsWith('"')){
44+
returnfalse;
45+
}
46+
47+
constquotedValue=value.slice(1);
48+
return!quotedValue||quotedValue.startsWith('{')||quotedValue.startsWith('$');
49+
}
1050

1151
construle={
1252
meta: {
@@ -24,13 +64,15 @@ const rule = {
2464
}
2565
},
2666
create(context: Rule.RuleContext){
67+
constsourceText=context.sourceCode.getText();
68+
2769
returncreateVisitors(context,{
2870
Tag(node: HtmlTagNode){
2971
(node.attributes??[]).forEach(attr=>{
3072
if(attr.type!=='Attribute'||!attr.key?.value)return;
3173

3274
constname=attr.key.value;
33-
if(INLINE_EVENT_HANDLER.test(name)){
75+
if(INLINE_EVENT_HANDLER.test(name)&&!hasDataBinding(attr)&&!isIncompleteEventHandler(attr,sourceText)){
3476
context.report({
3577
node: attr,
3678
messageId: 'no-inline-event-handler',

0 commit comments

Comments
 (0)