@@ -2,6 +2,65 @@ import { parseExpressionAt } from 'acorn';
2
2
import repeat from '../../utils/repeat' ;
3
3
import { Parser } from '../index' ;
4
4
5
+ const DIRECTIVES = {
6
+ Ref : {
7
+ names : [ 'ref' ] ,
8
+ attribute ( start , end , type , name ) {
9
+ return { start, end, type, name } ;
10
+ }
11
+ } ,
12
+
13
+ EventHandler : {
14
+ names : [ 'on' ] ,
15
+ allowedExpressionTypes : [ 'CallExpression' ] ,
16
+ } ,
17
+
18
+ Binding : {
19
+ names : [ '' , 'bind' ] ,
20
+ allowedExpressionTypes : [ 'Identifier' , 'MemberExpression' ] ,
21
+ attribute ( start , end , type , name , expression , directiveName ) {
22
+ let value ;
23
+
24
+ // :foo is shorthand for foo='{{foo}}'
25
+ if ( ! directiveName ) {
26
+ const valueStart = start + 1 ;
27
+ const valueEnd = start + name . length ;
28
+ type = 'Attribute' ;
29
+ value = getShorthandValue ( start + 1 , name ) ;
30
+ } else {
31
+ value = expression || {
32
+ type : 'Identifier' ,
33
+ start : start + 5 ,
34
+ end,
35
+ name,
36
+ } ;
37
+ }
38
+
39
+ return { start, end, type, name, value } ;
40
+ } ,
41
+ } ,
42
+
43
+ Transition : {
44
+ names : [ 'in' , 'out' , 'transition' ] ,
45
+ allowedExpressionTypes : [ 'ObjectExpression' ] ,
46
+ attribute ( start , end , type , name , expression , directiveName ) {
47
+ return {
48
+ start, end, type, name, expression,
49
+ intro : directiveName === 'in' || directiveName === 'transition' ,
50
+ outro : directiveName === 'out' || directiveName === 'transition' ,
51
+ }
52
+ } ,
53
+ } ,
54
+ } ;
55
+
56
+
57
+ const lookupByName = { } ;
58
+
59
+ Object . keys ( DIRECTIVES ) . forEach ( name => {
60
+ const directive = DIRECTIVES [ name ] ;
61
+ directive . names . forEach ( type => lookupByName [ type ] = name ) ;
62
+ } ) ;
63
+
5
64
function readExpression ( parser : Parser , start : number , quoteMark : string | null ) {
6
65
let str = '' ;
7
66
let escaped = false ;
@@ -24,7 +83,7 @@ function readExpression(parser: Parser, start: number, quoteMark: string|null) {
24
83
} else {
25
84
str += char ;
26
85
}
27
- } else if ( / \s / . test ( char ) ) {
86
+ } else if ( / [ \s \r \n \/ > ] / . test ( char ) ) {
28
87
break ;
29
88
} else {
30
89
str += char ;
@@ -42,125 +101,70 @@ function readExpression(parser: Parser, start: number, quoteMark: string|null) {
42
101
return expression ;
43
102
}
44
103
45
- export function readEventHandlerDirective (
46
- parser : Parser ,
47
- start : number ,
48
- name : string ,
49
- hasValue : boolean
50
- ) {
51
- let expression ;
52
-
53
- if ( hasValue ) {
54
- const quoteMark = parser . eat ( `'` ) ? `'` : parser . eat ( `"` ) ? `"` : null ;
55
-
56
- const expressionStart = parser . index ;
57
-
58
- expression = readExpression ( parser , expressionStart , quoteMark ) ;
59
-
60
- if ( expression . type !== 'CallExpression' ) {
61
- parser . error ( `Expected call expression` , expressionStart ) ;
62
- }
63
- }
64
-
65
- return {
66
- start,
67
- end : parser . index ,
68
- type : 'EventHandler' ,
69
- name,
70
- expression,
71
- } ;
72
- }
73
-
74
- export function readBindingDirective (
104
+ export function readDirective (
75
105
parser : Parser ,
76
106
start : number ,
77
- name : string
107
+ attrName : string
78
108
) {
79
- let value ;
109
+ const [ directiveName , name ] = attrName . split ( ':' ) ;
110
+ if ( name === undefined ) return ; // No colon in the name
111
+
112
+ const type = lookupByName [ directiveName ] ;
113
+ if ( ! type ) return ; // not a registered directive
114
+
115
+ const directive = DIRECTIVES [ type ] ;
116
+ let expression = null ;
80
117
81
118
if ( parser . eat ( '=' ) ) {
82
119
const quoteMark = parser . eat ( `'` ) ? `'` : parser . eat ( `"` ) ? `"` : null ;
83
120
84
- const a = parser . index ;
121
+ const expressionStart = parser . index ;
85
122
86
123
if ( parser . eat ( '{{' ) ) {
87
- let message = 'bound values should not be wrapped' ;
88
- const b = parser . template . indexOf ( '}}' , a ) ;
89
- if ( b !== - 1 ) {
90
- const value = parser . template . slice ( parser . index , b ) ;
124
+ let message = 'directive values should not be wrapped' ;
125
+ const expressionEnd = parser . template . indexOf ( '}}' , expressionStart ) ;
126
+ if ( expressionEnd !== - 1 ) {
127
+ const value = parser . template . slice ( parser . index , expressionEnd ) ;
91
128
message += ` — use '${ value } ', not '{{${ value } }}'` ;
92
129
}
93
130
94
- parser . error ( message , a ) ;
131
+ parser . error ( message , expressionStart ) ;
95
132
}
96
133
97
- // this is a bit of a hack so that we can give Acorn something parseable
98
- let b ;
99
- if ( quoteMark ) {
100
- b = parser . index = parser . template . indexOf ( quoteMark , parser . index ) ;
101
- } else {
102
- parser . readUntil ( / [ \s \r \n \/ > ] / ) ;
103
- b = parser . index ;
104
- }
105
-
106
- const source = repeat ( ' ' , a ) + parser . template . slice ( a , b ) ;
107
- value = parseExpressionAt ( source , a , { ecmaVersion : 9 } ) ;
108
-
109
- if ( value . type !== 'Identifier' && value . type !== 'MemberExpression' ) {
110
- parser . error ( `Cannot bind to rvalue` , value . start ) ;
134
+ expression = readExpression ( parser , expressionStart , quoteMark ) ;
135
+ if ( directive . allowedExpressionTypes . indexOf ( expression . type ) === - 1 ) {
136
+ parser . error ( `Expected ${ directive . allowedExpressionTypes . join ( ' or ' ) } ` , expressionStart ) ;
111
137
}
138
+ }
112
139
113
- parser . allowWhitespace ( ) ;
114
-
115
- if ( quoteMark ) {
116
- parser . eat ( quoteMark , true ) ;
117
- }
140
+ if ( directive . attribute ) {
141
+ return directive . attribute ( start , parser . index , type , name , expression , directiveName ) ;
118
142
} else {
119
- // shorthand – bind:foo equivalent to bind:foo='foo'
120
- value = {
121
- type : 'Identifier' ,
122
- start : start + 5 ,
143
+ return {
144
+ start,
123
145
end : parser . index ,
146
+ type : type ,
124
147
name,
148
+ expression,
125
149
} ;
126
150
}
127
-
128
- return {
129
- start,
130
- end : parser . index ,
131
- type : 'Binding' ,
132
- name,
133
- value,
134
- } ;
135
151
}
136
152
137
- export function readTransitionDirective (
138
- parser : Parser ,
139
- start : number ,
140
- name : string ,
141
- type : string
142
- ) {
143
- let expression = null ;
144
-
145
- if ( parser . eat ( '=' ) ) {
146
- const quoteMark = parser . eat ( `'` ) ? `'` : parser . eat ( `"` ) ? `"` : null ;
147
-
148
- const expressionStart = parser . index ;
149
-
150
- expression = readExpression ( parser , expressionStart , quoteMark ) ;
151
-
152
- if ( expression . type !== 'ObjectExpression' ) {
153
- parser . error ( `Expected object expression` , expressionStart ) ;
154
- }
155
- }
156
153
157
- return {
158
- start,
159
- end : parser . index ,
160
- type : 'Transition' ,
161
- name,
162
- intro : type === 'in' || type === 'transition' ,
163
- outro : type === 'out' || type === 'transition' ,
164
- expression,
165
- } ;
154
+ function getShorthandValue ( start : number , name : string ) {
155
+ const end = start + name . length ;
156
+
157
+ return [
158
+ {
159
+ type : 'AttributeShorthand' ,
160
+ start,
161
+ end,
162
+ expression : {
163
+ type : 'Identifier' ,
164
+ start,
165
+ end,
166
+ name,
167
+ } ,
168
+ } ,
169
+ ] ;
166
170
}
0 commit comments