@@ -4,52 +4,24 @@ export type ValueInjectionLoaderOptions = {
44 values : Record < string , unknown > ;
55} ;
66
7- // We need to be careful not to inject anything before any `"use strict";`s or "use client"s or really any other directives.
7+ /**
8+ * Finds the index in user code at which to inject statements.
9+ *
10+ * The injection must come AFTER all prologue directives ("use strict", "use client", etc.)
11+ * and any surrounding whitespace/comments, but before any actual statements.
12+ *
13+ * Handles multiple directives, comments between directives, directives without semicolons,
14+ * escape sequences in strings, and strings followed by operators (which are not directives).
15+ */
816export function findInjectionIndexAfterDirectives ( userCode : string ) : number {
917 let index = 0 ;
10- let lastDirectiveEndIndex : number | undefined ;
11-
12- while ( index < userCode . length ) {
13- const statementStartIndex = skipWhitespaceAndComments ( userCode , index ) ;
14- if ( statementStartIndex === undefined ) {
15- return lastDirectiveEndIndex ?? 0 ;
16- }
17-
18- index = statementStartIndex ;
19- if ( statementStartIndex === userCode . length ) {
20- return lastDirectiveEndIndex ?? statementStartIndex ;
21- }
22-
23- const quote = userCode [ statementStartIndex ] ;
24- if ( quote !== '"' && quote !== "'" ) {
25- return lastDirectiveEndIndex ?? statementStartIndex ;
26- }
27-
28- const stringEndIndex = findStringLiteralEnd ( userCode , statementStartIndex ) ;
29- if ( stringEndIndex === undefined ) {
30- return lastDirectiveEndIndex ?? statementStartIndex ;
31- }
32-
33- const statementEndIndex = findDirectiveTerminator ( userCode , stringEndIndex ) ;
34- if ( statementEndIndex === undefined ) {
35- return lastDirectiveEndIndex ?? statementStartIndex ;
36- }
37-
38- index = statementEndIndex ;
39- lastDirectiveEndIndex = statementEndIndex ;
40- }
41-
42- return lastDirectiveEndIndex ?? index ;
43- }
44-
45- function skipWhitespaceAndComments ( userCode : string , startIndex : number ) : number | undefined {
46- let index = startIndex ;
18+ let afterLastDirective : number | undefined ;
4719
4820 while ( index < userCode . length ) {
4921 const char = userCode [ index ] ;
5022
5123 if ( char && / \s / . test ( char ) ) {
52- index += 1 ;
24+ index ++ ;
5325 continue ;
5426 }
5527
@@ -62,49 +34,76 @@ function skipWhitespaceAndComments(userCode: string, startIndex: number): number
6234 if ( userCode . startsWith ( '/*' , index ) ) {
6335 const commentEndIndex = userCode . indexOf ( '*/' , index + 2 ) ;
6436 if ( commentEndIndex === - 1 ) {
65- return undefined ;
37+ return afterLastDirective ?? 0 ;
6638 }
6739
6840 index = commentEndIndex + 2 ;
6941 continue ;
7042 }
7143
72- break ;
44+ if ( char === '"' || char === "'" ) {
45+ const stringEnd = findStringLiteralEnd ( userCode , index ) ;
46+ if ( stringEnd === null ) {
47+ return afterLastDirective ?? index ;
48+ }
49+
50+ const terminatorEnd = findDirectiveTerminator ( userCode , stringEnd ) ;
51+ if ( terminatorEnd === null ) {
52+ return afterLastDirective ?? index ;
53+ }
54+
55+ afterLastDirective = terminatorEnd ;
56+ index = terminatorEnd ;
57+ continue ;
58+ }
59+
60+ return afterLastDirective ?? index ;
7361 }
7462
75- return index ;
63+ return afterLastDirective ?? index ;
7664}
7765
78- function findStringLiteralEnd ( userCode : string , startIndex : number ) : number | undefined {
66+ /**
67+ * Scans a string literal starting at `start` (which must be a quote character),
68+ * correctly handling escape sequences and rejecting unterminated/multiline strings.
69+ * Returns the index after the closing quote, or null if the string is unterminated.
70+ */
71+ function findStringLiteralEnd ( userCode : string , startIndex : number ) : number | null {
7972 const quote = userCode [ startIndex ] ;
8073 let index = startIndex + 1 ;
8174
8275 while ( index < userCode . length ) {
8376 const char = userCode [ index ] ;
8477
8578 if ( char === '\\' ) {
79+ // skip escaped character
8680 index += 2 ;
8781 continue ;
8882 }
8983
9084 if ( char === quote ) {
91- return index + 1 ;
85+ return index + 1 ; // found closing quote
9286 }
9387
9488 if ( char === '\n' || char === '\r' ) {
95- return undefined ;
89+ return null ; // unterminated
9690 }
9791
98- index += 1 ;
92+ index ++ ;
9993 }
10094
101- return undefined ;
95+ return null ; // unterminated
10296}
10397
104- function findDirectiveTerminator ( userCode : string , startIndex : number ) : number | undefined {
98+ /**
99+ * Starting at `i`, skips horizontal whitespace and single-line block comments,
100+ * then checks for a valid directive terminator: `;`, newline, `//`, or EOF.
101+ * Returns the index after the terminator, or null if no valid terminator is found
102+ * (meaning the preceding string literal is not a directive).
103+ */
104+ function findDirectiveTerminator ( userCode : string , startIndex : number ) : number | null {
105105 let index = startIndex ;
106106
107- // Only a bare string literal followed by a statement terminator counts as a directive.
108107 while ( index < userCode . length ) {
109108 const char = userCode [ index ] ;
110109
@@ -117,7 +116,7 @@ function findDirectiveTerminator(userCode: string, startIndex: number): number |
117116 }
118117
119118 if ( char && / \s / . test ( char ) ) {
120- index += 1 ;
119+ index ++ ;
121120 continue ;
122121 }
123122
@@ -128,7 +127,7 @@ function findDirectiveTerminator(userCode: string, startIndex: number): number |
128127 if ( userCode . startsWith ( '/*' , index ) ) {
129128 const commentEndIndex = userCode . indexOf ( '*/' , index + 2 ) ;
130129 if ( commentEndIndex === - 1 ) {
131- return undefined ;
130+ return null ;
132131 }
133132
134133 const comment = userCode . slice ( index + 2 , commentEndIndex ) ;
@@ -140,10 +139,10 @@ function findDirectiveTerminator(userCode: string, startIndex: number): number |
140139 continue ;
141140 }
142141
143- return undefined ;
142+ return null ; // operator or any other token → not a directive
144143 }
145144
146- return index ;
145+ return index ; // EOF is a valid terminator
147146}
148147
149148/**
0 commit comments