Skip to content

Commit 519ffb4

Browse files
s1gr1dmydea
authored andcommitted
ref(nextjs): Refactor findInjectionIndexAfterDirectives for better readability (#20310)
Builds on top of this PR: #20103 Adds some explaining comments and refactors the directive-scanning logic in the value injection loader from 4 functions down to 3 by inlining whitespace and comment skipping into the main loop. The previous implementation split the scanning into a separate `skipWhitespaceAndComments` function. The new version handles whitespace, line comments, and block comments as continue branches directly in the main while loop, which: - Eliminates one level of function calls - Unterminated block comments are handled inline with an early return - Makes the control flow easier to follow. Each iteration of the loop either skips something inert (whitespace/comments), successfully parses a directive and advances, or exits
1 parent 004e146 commit 519ffb4

File tree

1 file changed

+52
-53
lines changed

1 file changed

+52
-53
lines changed

packages/nextjs/src/config/loaders/valueInjectionLoader.ts

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
*/
816
export 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

Comments
 (0)