Skip to content

Commit 215d6bf

Browse files
committed
refactor(nextjs): flatten directive injection scanner
1 parent c1d0518 commit 215d6bf

File tree

2 files changed

+94
-62
lines changed

2 files changed

+94
-62
lines changed

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

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,16 @@ export function findInjectionIndexAfterDirectives(userCode: string): number {
1111
let lastDirectiveEndIndex: number | undefined;
1212

1313
while (index < userCode.length) {
14-
const scanStartIndex = index;
15-
16-
// Comments can appear between directive prologue entries, so keep scanning until we reach the next statement.
17-
while (index < userCode.length) {
18-
const char = userCode[index];
19-
20-
if (char && /\s/.test(char)) {
21-
index += 1;
22-
continue;
23-
}
24-
25-
if (userCode.startsWith('//', index)) {
26-
const newlineIndex = userCode.indexOf('\n', index + 2);
27-
index = newlineIndex === -1 ? userCode.length : newlineIndex + 1;
28-
continue;
29-
}
30-
31-
if (userCode.startsWith('/*', index)) {
32-
const commentEndIndex = userCode.indexOf('*/', index + 2);
33-
if (commentEndIndex === -1) {
34-
return lastDirectiveEndIndex ?? scanStartIndex;
35-
}
36-
37-
index = commentEndIndex + 2;
38-
continue;
39-
}
14+
const statementStartIndex = skipWhitespaceAndComments(userCode, index);
15+
if (statementStartIndex === undefined) {
16+
return lastDirectiveEndIndex ?? 0;
17+
}
4018

41-
break;
19+
index = statementStartIndex;
20+
if (statementStartIndex === userCode.length) {
21+
return lastDirectiveEndIndex ?? statementStartIndex;
4222
}
4323

44-
const statementStartIndex = index;
4524
const quote = userCode[statementStartIndex];
4625
if (quote !== '"' && quote !== "'") {
4726
return lastDirectiveEndIndex ?? statementStartIndex;
@@ -52,53 +31,49 @@ export function findInjectionIndexAfterDirectives(userCode: string): number {
5231
return lastDirectiveEndIndex ?? statementStartIndex;
5332
}
5433

55-
let statementEndIndex = stringEndIndex;
56-
57-
// Only a bare string literal followed by a statement terminator counts as a directive.
58-
while (statementEndIndex < userCode.length) {
59-
const char = userCode[statementEndIndex];
34+
const statementEndIndex = findDirectiveTerminator(userCode, stringEndIndex);
35+
if (statementEndIndex === undefined) {
36+
return lastDirectiveEndIndex ?? statementStartIndex;
37+
}
6038

61-
if (char === ';') {
62-
statementEndIndex += 1;
63-
break;
64-
}
39+
index = statementEndIndex;
40+
lastDirectiveEndIndex = statementEndIndex;
41+
}
6542

66-
if (char === '\n' || char === '\r' || char === '}') {
67-
break;
68-
}
43+
return lastDirectiveEndIndex ?? index;
44+
}
6945

70-
if (char && /\s/.test(char)) {
71-
statementEndIndex += 1;
72-
continue;
73-
}
46+
function skipWhitespaceAndComments(userCode: string, startIndex: number): number | undefined {
47+
let index = startIndex;
7448

75-
if (userCode.startsWith('//', statementEndIndex)) {
76-
break;
77-
}
49+
while (index < userCode.length) {
50+
const char = userCode[index];
7851

79-
if (userCode.startsWith('/*', statementEndIndex)) {
80-
const commentEndIndex = userCode.indexOf('*/', statementEndIndex + 2);
81-
if (commentEndIndex === -1) {
82-
return lastDirectiveEndIndex ?? statementStartIndex;
83-
}
52+
if (char && /\s/.test(char)) {
53+
index += 1;
54+
continue;
55+
}
8456

85-
const comment = userCode.slice(statementEndIndex + 2, commentEndIndex);
86-
if (comment.includes('\n') || comment.includes('\r')) {
87-
break;
88-
}
57+
if (userCode.startsWith('//', index)) {
58+
const newlineIndex = userCode.indexOf('\n', index + 2);
59+
index = newlineIndex === -1 ? userCode.length : newlineIndex + 1;
60+
continue;
61+
}
8962

90-
statementEndIndex = commentEndIndex + 2;
91-
continue;
63+
if (userCode.startsWith('/*', index)) {
64+
const commentEndIndex = userCode.indexOf('*/', index + 2);
65+
if (commentEndIndex === -1) {
66+
return undefined;
9267
}
9368

94-
return lastDirectiveEndIndex ?? statementStartIndex;
69+
index = commentEndIndex + 2;
70+
continue;
9571
}
9672

97-
index = statementEndIndex;
98-
lastDirectiveEndIndex = statementEndIndex;
73+
break;
9974
}
10075

101-
return lastDirectiveEndIndex ?? index;
76+
return index;
10277
}
10378

10479
function findStringLiteralEnd(userCode: string, startIndex: number): number | undefined {
@@ -127,6 +102,51 @@ function findStringLiteralEnd(userCode: string, startIndex: number): number | un
127102
return undefined;
128103
}
129104

105+
function findDirectiveTerminator(userCode: string, startIndex: number): number | undefined {
106+
let index = startIndex;
107+
108+
// Only a bare string literal followed by a statement terminator counts as a directive.
109+
while (index < userCode.length) {
110+
const char = userCode[index];
111+
112+
if (char === ';') {
113+
return index + 1;
114+
}
115+
116+
if (char === '\n' || char === '\r' || char === '}') {
117+
return index;
118+
}
119+
120+
if (char && /\s/.test(char)) {
121+
index += 1;
122+
continue;
123+
}
124+
125+
if (userCode.startsWith('//', index)) {
126+
return index;
127+
}
128+
129+
if (userCode.startsWith('/*', index)) {
130+
const commentEndIndex = userCode.indexOf('*/', index + 2);
131+
if (commentEndIndex === -1) {
132+
return undefined;
133+
}
134+
135+
const comment = userCode.slice(index + 2, commentEndIndex);
136+
if (comment.includes('\n') || comment.includes('\r')) {
137+
return index;
138+
}
139+
140+
index = commentEndIndex + 2;
141+
continue;
142+
}
143+
144+
return undefined;
145+
}
146+
147+
return index;
148+
}
149+
130150
/**
131151
* Set values on the global/window object at the start of a module.
132152
*

packages/nextjs/test/config/valueInjectionLoader.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,18 @@ describe('findInjectionIndexAfterDirectives', () => {
227227
expect(findInjectionIndexAfterDirectives(userCode)).toBe(0);
228228
});
229229

230+
it('returns 0 for an unterminated leading block comment', () => {
231+
const userCode = '/* unterminated';
232+
233+
expect(findInjectionIndexAfterDirectives(userCode)).toBe(0);
234+
});
235+
236+
it('returns the last complete directive when followed by an unterminated block comment', () => {
237+
const userCode = '"use client"; /* unterminated';
238+
239+
expect(findInjectionIndexAfterDirectives(userCode)).toBe('"use client";'.length);
240+
});
241+
230242
it('treats a block comment without a line break as part of the same statement', () => {
231243
const userCode = '"use client" /* comment */ + suffix;';
232244

0 commit comments

Comments
 (0)