33
44import type { Rule } from 'eslint' ;
55import { createVisitors } from '@html-eslint/eslint-plugin/lib/rules/utils/visitors.js' ;
6- import type { HtmlTagNode } from '../rule-types.js' ;
6+ import type { HtmlAttribute , HtmlTagNode } from '../rule-types.js' ;
77
88declare const __ELEMENTS_PAGES_BASE_URL__ : string ;
99const INLINE_EVENT_HANDLER = / ^ o n [ a - z ] + $ / i;
10+ const DATA_BINDING_PATTERNS = [
11+ / \$ \{ [ ^ } ] * \} / , // ${...} - JavaScript template literals
12+ / \{ \{ [ ^ } ] * \} \} / , // {{...}} - Vue, Angular, Handlebars
13+ / ^ \{ [ ^ } ] + \} $ / // {...} - JSX/React expressions (entire value is an expression)
14+ ] ;
15+
16+ function hasDataBinding ( attribute : HtmlAttribute ) : boolean {
17+ const value = attribute . value ?. value ;
18+ if ( ! value ) return false ;
19+
20+ if ( DATA_BINDING_PATTERNS . some ( pattern => pattern . test ( value ) ) ) {
21+ return true ;
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+ function isIncompleteEventHandler ( attribute : HtmlAttribute , sourceText : string ) : boolean {
30+ if ( attribute . value || ! attribute . range || sourceText [ attribute . range [ 1 ] ] !== '=' ) {
31+ return false ;
32+ }
33+
34+ const value = sourceText . slice ( attribute . range [ 1 ] + 1 ) ;
35+ if ( ! value ) {
36+ return true ;
37+ }
38+
39+ if ( value . startsWith ( '{' ) || value . startsWith ( '$' ) ) {
40+ return true ;
41+ }
42+
43+ if ( ! value . startsWith ( '"' ) ) {
44+ return false ;
45+ }
46+
47+ const quotedValue = value . slice ( 1 ) ;
48+ return ! quotedValue || quotedValue . startsWith ( '{' ) || quotedValue . startsWith ( '$' ) ;
49+ }
1050
1151const rule = {
1252 meta : {
@@ -24,13 +64,15 @@ const rule = {
2464 }
2565 } ,
2666 create ( context : Rule . RuleContext ) {
67+ const sourceText = context . sourceCode . getText ( ) ;
68+
2769 return createVisitors ( context , {
2870 Tag ( node : HtmlTagNode ) {
2971 ( node . attributes ?? [ ] ) . forEach ( attr => {
3072 if ( attr . type !== 'Attribute' || ! attr . key ?. value ) return ;
3173
3274 const name = 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