@@ -35,6 +35,10 @@ import {
3535 parseSlotScopeExpression ,
3636} from "../script"
3737
38+ const shorthandSign = / ^ [: @ # ] / u
39+ const shorthandNameMap = { ":" : "bind" , "@" : "on" , "#" : "slot" }
40+ const shorthandSignMap = { bind : ":" , on : "@" , slot : "#" }
41+
3842/**
3943 * Get the belonging document of the given node.
4044 * @param leafNode The node to get.
@@ -79,7 +83,7 @@ function createSimpleToken(
7983 * @param node The identifier node to parse.
8084 * @returns The directive key node.
8185 */
82- function createDirectiveKey ( node : VIdentifier ) : VDirectiveKey {
86+ function parseDirectiveKeyStatically ( node : VIdentifier ) : VDirectiveKey {
8387 const raw : DirectiveKeyParts = {
8488 name : "" ,
8589 argument : null ,
@@ -100,16 +104,10 @@ function createDirectiveKey(node: VIdentifier): VDirectiveKey {
100104 const rawId = node . rawName
101105 let i = 0
102106
103- if ( node . name . startsWith ( ":" ) ) {
104- ret . name = raw . name = "bind"
105- ret . shorthand = true
106- i = 1
107- } else if ( id . startsWith ( "@" ) ) {
108- ret . name = raw . name = "on"
109- ret . shorthand = true
110- i = 1
111- } else if ( id . startsWith ( "#" ) ) {
112- ret . name = raw . name = "slot"
107+ // Parse.
108+ if ( shorthandSign . test ( id ) ) {
109+ const sign = id [ 0 ] as ":" | "@" | "#"
110+ ret . name = raw . name = shorthandNameMap [ sign ]
113111 ret . shorthand = true
114112 i = 1
115113 } else {
@@ -133,11 +131,219 @@ function createDirectiveKey(node: VIdentifier): VDirectiveKey {
133131 ret . modifiers = dotSplit . slice ( 1 )
134132 raw . modifiers = dotSplitRaw . slice ( 1 )
135133
134+ return ret
135+ }
136+
137+ /**
138+ * Parse the tokens of a given key node.
139+ * @param node The key node to parse.
140+ */
141+ function parseDirectiveKeyTokens (
142+ node : VDirectiveKey ,
143+ locationCalculator : LocationCalculator ,
144+ ) : Token [ ] {
145+ const raw = node . raw
146+ const tokens : Token [ ] = [ ]
147+ let i = 0
148+
149+ if ( node . shorthand ) {
150+ const name = raw . name as "bind" | "on" | "slot"
151+ tokens . push (
152+ createSimpleToken (
153+ "Punctuator" ,
154+ node . range [ 0 ] ,
155+ node . range [ 0 ] + 1 ,
156+ shorthandSignMap [ name ] ,
157+ locationCalculator ,
158+ ) ,
159+ )
160+ i = 1
161+ } else if ( raw . name ) {
162+ tokens . push (
163+ createSimpleToken (
164+ "HTMLIdentifier" ,
165+ node . range [ 0 ] ,
166+ node . range [ 0 ] + raw . name . length ,
167+ raw . name ,
168+ locationCalculator ,
169+ ) ,
170+ )
171+ i = raw . name . length
172+
173+ if ( raw . argument ) {
174+ tokens . push (
175+ createSimpleToken (
176+ "Punctuator" ,
177+ node . range [ 0 ] + i ,
178+ node . range [ 0 ] + i + 1 ,
179+ ":" ,
180+ locationCalculator ,
181+ ) ,
182+ )
183+ i += 1
184+ }
185+ }
186+
187+ if ( raw . argument ) {
188+ tokens . push (
189+ createSimpleToken (
190+ "HTMLIdentifier" ,
191+ node . range [ 0 ] + i ,
192+ node . range [ 0 ] + i + raw . argument . length ,
193+ raw . argument ,
194+ locationCalculator ,
195+ ) ,
196+ )
197+ i += raw . argument . length
198+ }
199+
200+ for ( const modifier of raw . modifiers ) {
201+ tokens . push (
202+ createSimpleToken (
203+ "Punctuator" ,
204+ node . range [ 0 ] + i ,
205+ node . range [ 0 ] + i + 1 ,
206+ "." ,
207+ locationCalculator ,
208+ ) ,
209+ createSimpleToken (
210+ "HTMLIdentifier" ,
211+ node . range [ 0 ] + i + 1 ,
212+ node . range [ 0 ] + i + 1 + modifier . length ,
213+ modifier ,
214+ locationCalculator ,
215+ ) ,
216+ )
217+ i += 1 + modifier . length
218+ }
219+
220+ return tokens
221+ }
222+
223+ /**
224+ * Convert `node.argument` property to a `VExpressionContainer` node if it's a dynamic argument.
225+ * @param text The source code text of the directive key node.
226+ * @param node The directive key node to convert.
227+ * @param document The belonging document node.
228+ * @param parserOptions The parser options to parse.
229+ * @param locationCalculator The location calculator to parse.
230+ */
231+ function convertDynamicArgument (
232+ text : string ,
233+ node : VDirectiveKey ,
234+ document : VDocumentFragment | null ,
235+ parserOptions : any ,
236+ locationCalculator : LocationCalculator ,
237+ ) : void {
238+ const argument = node . raw . argument
239+ if (
240+ typeof argument !== "string" ||
241+ ! argument . startsWith ( "[" ) ||
242+ ! argument . endsWith ( "]" )
243+ ) {
244+ return
245+ }
246+
247+ const start = node . range [ 0 ] + text . indexOf ( argument )
248+ const end = start + argument . length
249+ try {
250+ const { comments, expression, references, tokens } = parseExpression (
251+ argument . slice ( 1 , - 1 ) ,
252+ locationCalculator . getSubCalculatorAfter ( start + 1 ) ,
253+ parserOptions ,
254+ )
255+
256+ node . argument = {
257+ type : "VExpressionContainer" ,
258+ range : [ start , end ] ,
259+ loc : {
260+ start : locationCalculator . getLocation ( start ) ,
261+ end : locationCalculator . getLocation ( end ) ,
262+ } ,
263+ parent : node ,
264+ expression,
265+ references,
266+ }
267+
268+ if ( expression != null ) {
269+ expression . parent = node . argument
270+ }
271+
272+ // Add tokens of `[` and `]`.
273+ tokens . unshift (
274+ createSimpleToken (
275+ "Punctuator" ,
276+ start ,
277+ start + 1 ,
278+ "[" ,
279+ locationCalculator ,
280+ ) ,
281+ )
282+ tokens . push (
283+ createSimpleToken (
284+ "Punctuator" ,
285+ end - 1 ,
286+ end ,
287+ "]" ,
288+ locationCalculator ,
289+ ) ,
290+ )
291+
292+ replaceTokens ( document , node . argument , tokens )
293+ insertComments ( document , comments )
294+ } catch ( error ) {
295+ debug ( "[template] Parse error: %s" , error )
296+
297+ if ( ParseError . isParseError ( error ) ) {
298+ node . argument = {
299+ type : "VExpressionContainer" ,
300+ range : [ start , end ] ,
301+ loc : {
302+ start : locationCalculator . getLocation ( start ) ,
303+ end : locationCalculator . getLocation ( end ) ,
304+ } ,
305+ parent : node ,
306+ expression : null ,
307+ references : [ ] ,
308+ }
309+ insertError ( document , error )
310+ } else {
311+ throw error
312+ }
313+ }
314+ }
315+
316+ /**
317+ * Parse the given attribute name as a directive key.
318+ * @param node The identifier node to parse.
319+ * @returns The directive key node.
320+ */
321+ function createDirectiveKey (
322+ node : VIdentifier ,
323+ document : VDocumentFragment | null ,
324+ parserOptions : any ,
325+ locationCalculator : LocationCalculator ,
326+ ) : VDirectiveKey {
327+ // Parse node and tokens.
328+ const ret = parseDirectiveKeyStatically ( node )
329+ const tokens = parseDirectiveKeyTokens ( ret , locationCalculator )
330+ replaceTokens ( document , ret , tokens )
331+
332+ // Drop `v-` prefix.
136333 if ( ret . name . startsWith ( "v-" ) ) {
137334 ret . name = ret . name . slice ( 2 )
138- raw . name = raw . name . slice ( 2 )
335+ ret . raw . name = ret . raw . name . slice ( 2 )
139336 }
140337
338+ // Parse dynamic argument.
339+ convertDynamicArgument (
340+ node . rawName ,
341+ ret ,
342+ document ,
343+ parserOptions ,
344+ locationCalculator ,
345+ )
346+
141347 return ret
142348}
143349
@@ -382,14 +588,19 @@ export function convertToDirective(
382588 node . range ,
383589 )
384590
591+ const document = getOwnerDocument ( node )
385592 const directive : VDirective = node as any
386593 directive . directive = true
387- directive . key = createDirectiveKey ( node . key )
594+ directive . key = createDirectiveKey (
595+ node . key ,
596+ document ,
597+ parserOptions ,
598+ locationCalculator ,
599+ )
388600
389601 if ( node . value == null ) {
390602 return
391603 }
392- const document = getOwnerDocument ( node )
393604
394605 try {
395606 const ret = parseAttributeValue (
0 commit comments