GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(@angular/build): use oxc-parser fallback when stripping source-ma… · clydin/angular-cli@575c988

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • packages/angular/build/src
    • tools
      • javascript-transformer
        • javascript-transformer-worker.ts
      • vite/plugins
        • angular-memory-plugin.ts
    • utils
      • source-map.ts
      • source-map_spec.ts
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎packages/angular/build/src/tools/javascript-transformer/javascript-transformer-worker.ts‎

Copy file name to clipboardExpand all lines: packages/angular/build/src/tools/javascript-transformer/javascript-transformer-worker.ts
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ export default async function transformJavaScript(
136136
textData = textDecoder.decode(trailing.code);
137137
isAlreadyStripped = true;
138138
} else {
139-
// Fallback to full decode and state-machine stripping
139+
// Not a valid trailing sourcemap: fall back to full decode and oxc comment stripping
140140
textData = textDecoder.decode(data);
141141
}
142142
} else {
143-
// Multiple comments or comment not at line start: fall back to full decode and string parser
143+
// Multiple comments or comment not at line start: fall back to full decode and oxc comment stripping
144144
textData = textDecoder.decode(data);
145145
}
146146
} else {
Collapse file

‎packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts‎

Copy file name to clipboardExpand all lines: packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts
+14-3Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,30 @@ export async function createAngularMemoryPlugin(
9999
return undefined;
100100
}
101101

102-
const code = Buffer.from(codeContents).toString('utf-8');
103102
const mapContents = outputFiles.get(relativeFile + '.map')?.contents;
103+
const strippedContents = mapContents ? removeSourceMappingURL(codeContents) : codeContents;
104104

105105
return {
106106
// Remove source map URL comments from the code if a sourcemap is present.
107107
// Vite will inline and add an additional sourcemap URL for the sourcemap.
108-
code: mapContents ? removeSourceMappingURL(code) : code,
109-
map: mapContents && Buffer.from(mapContents).toString('utf-8'),
108+
code: decodeUtf8(strippedContents),
109+
map: mapContents && decodeUtf8(mapContents),
110110
};
111111
},
112112
};
113113
}
114114

115+
/**
116+
* Decodes UTF-8 bytes into a string without copying the underlying memory.
117+
*/
118+
function decodeUtf8(data: Uint8Array): string {
119+
const buffer = Buffer.isBuffer(data)
120+
? data
121+
: Buffer.from(data.buffer, data.byteOffset, data.byteLength);
122+
123+
return buffer.toString('utf-8');
124+
}
125+
115126
/**
116127
* Reads the resolved Vite client code from disk and updates the content to remove
117128
* an unactionable suggestion to update the Vite configuration file to disable the
Collapse file

‎packages/angular/build/src/utils/source-map.ts‎

Copy file name to clipboardExpand all lines: packages/angular/build/src/utils/source-map.ts
+105-150Lines changed: 105 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88

99
import type { EncodedSourceMap } from '@ampproject/remapping';
1010
import { existsSync, readFileSync } from 'node:fs';
11+
import { createRequire } from 'node:module';
1112
import { dirname, resolve } from 'node:path';
1213
import { fileURLToPath } from 'node:url';
1314

1415
export const SOURCEMAP_COMMENT_PREFIX = '//# sourceMappingURL=';
1516
export const SOURCEMAP_COMMENT_BYTES = Buffer.from(SOURCEMAP_COMMENT_PREFIX);
17+
const SOURCEMAP_COMMENT_VALUE_PREFIX = SOURCEMAP_COMMENT_PREFIX.slice(2);
18+
19+
let parseSync: typeof import('oxc-parser').parseSync | undefined;
1620

1721
/**
1822
* Checks for a single trailing `//# sourceMappingURL=` comment on a raw buffer.
@@ -59,10 +63,11 @@ export function findTrailingSourceMapComment(
5963

6064
/**
6165
* Removes `//# sourceMappingURL=` comments safely from the given JavaScript code,
62-
* ignoring any occurrences that are inside string literals, template literals, or block comments.
66+
* ignoring any occurrences that are inside string literals, template literals, regex literals,
67+
* or block comments.
6368
*
64-
* For raw Uint8Array / Buffer inputs, it optimizes performance by inspecting trailing byte sequences
65-
* to slice the buffer directly without full string decoding.
69+
* Single trailing sourcemap comments are sliced directly via a fast path; files with multiple
70+
* or non-trailing occurrences lazily fall back to `oxc-parser` comment extraction.
6671
*
6772
* @param code The JavaScript source code as a string or Uint8Array.
6873
* @returns The code with top-level sourcemap comments removed.
@@ -83,179 +88,120 @@ export function removeSourceMappingURL(code: string | Uint8Array): string | Uint
8388
return trailing.code;
8489
}
8590

86-
// Fallback to full decode and state-machine stripping for multiple comments or non-trailing comments
91+
// Fallback to full decode and AST lexer comment stripping for multiple or non-trailing comments
8792
const dataBuffer = Buffer.isBuffer(code)
8893
? code
8994
: Buffer.from(code.buffer, code.byteOffset, code.byteLength);
9095
const text = dataBuffer.toString('utf-8');
91-
const stripped = removeSourceMappingURLFromString(text);
96+
const stripped = removeSourceMappingURLFromString(text, true);
9297

9398
return stripped === text ? code : Buffer.from(stripped, 'utf-8');
9499
}
95100

96-
function removeSourceMappingURLFromString(code: string): string {
97-
if (!code.includes(SOURCEMAP_COMMENT_PREFIX)) {
101+
function removeSourceMappingURLFromString(code: string, skipTrailingCheck = false): string {
102+
const firstIndex = code.indexOf(SOURCEMAP_COMMENT_PREFIX);
103+
if (firstIndex === -1) {
98104
return code;
99105
}
100106

107+
if (!skipTrailingCheck) {
108+
const lastIndex = code.lastIndexOf(SOURCEMAP_COMMENT_PREFIX);
109+
let prevIdx = lastIndex - 1;
110+
while (prevIdx >= 0 && (code[prevIdx] === ' ' || code[prevIdx] === '\t')) {
111+
prevIdx--;
112+
}
113+
const isLineStart = prevIdx < 0 || code[prevIdx] === '\n' || code[prevIdx] === '\r';
114+
if (
115+
firstIndex === lastIndex &&
116+
isLineStart &&
117+
isTrailingSourceMapComment(code.slice(lastIndex + SOURCEMAP_COMMENT_PREFIX.length))
118+
) {
119+
return code.slice(0, prevIdx < 0 ? 0 : prevIdx + 1);
120+
}
121+
}
122+
123+
parseSync ??= (createRequire(__filename)('oxc-parser') as typeof import('oxc-parser')).parseSync;
124+
125+
const { comments } = parseSync('source.js', code, {
126+
lang: 'js',
127+
sourceType: 'unambiguous',
128+
});
129+
101130
const result: string[] = [];
102131
let lastCopiedIndex = 0;
103-
let i = 0;
104-
const len = code.length;
105-
106-
// Stack to track template literal state and curly brace depth for nested interpolations.
107-
const stack: { type: 'template'; braceDepth: number }[] = [];
108-
let currentState:
109-
'normal' | 'string_double' | 'string_single' | 'template' | 'comment_block' | 'comment_line' =
110-
'normal';
111-
112-
while (i < len) {
113-
const char = code[i];
114-
const nextChar = code[i + 1];
115-
116-
if (currentState === 'normal') {
117-
if (char === '/' && nextChar === '*') {
118-
currentState = 'comment_block';
119-
i += 2;
120-
continue;
121-
}
122-
if (char === '/' && nextChar === '/') {
123-
// Detect if the comment is escaped (e.g. inside a regex literal like `/\/\/#/`).
124-
let isEscaped = false;
125-
let prevIdx = i - 1;
126-
while (prevIdx >= 0 && /\s/.test(code[prevIdx])) {
127-
prevIdx--;
128-
}
129-
if (prevIdx >= 0 && code[prevIdx] === '\\') {
130-
let bsCount = 0;
131-
while (prevIdx >= 0 && code[prevIdx] === '\\') {
132-
bsCount++;
133-
prevIdx--;
134-
}
135-
if (bsCount % 2 === 1) {
136-
isEscaped = true;
137-
}
138-
}
139-
140-
if (!isEscaped && code.startsWith(SOURCEMAP_COMMENT_PREFIX, i)) {
141-
if (i > lastCopiedIndex) {
142-
result.push(code.slice(lastCopiedIndex, i));
143-
}
144-
// Skip the rest of the comment line up to the newline
145-
i += SOURCEMAP_COMMENT_PREFIX.length;
146-
while (i < len && code[i] !== '\n' && code[i] !== '\r') {
147-
i++;
148-
}
149-
lastCopiedIndex = i;
150-
continue;
151-
} else {
152-
currentState = 'comment_line';
153-
i += 2;
154-
continue;
155-
}
156-
}
157-
if (char === '"') {
158-
currentState = 'string_double';
159-
i++;
160-
continue;
161-
}
162-
if (char === "'") {
163-
currentState = 'string_single';
164-
i++;
165-
continue;
166-
}
167-
if (char === '`') {
168-
currentState = 'template';
169-
stack.push({ type: 'template', braceDepth: 0 });
170-
i++;
171-
continue;
172-
}
173-
if (char === '{') {
174-
const top = stack[stack.length - 1];
175-
if (top) {
176-
top.braceDepth++;
177-
}
178-
i++;
179-
continue;
180-
}
181-
if (char === '}') {
182-
const top = stack[stack.length - 1];
183-
if (top) {
184-
top.braceDepth--;
185-
if (top.braceDepth < 0) {
186-
// Exiting a template literal interpolation ${ ... }
187-
stack.pop();
188-
currentState = 'template';
189-
i++;
190-
continue;
191-
}
192-
}
193-
i++;
194-
continue;
195-
}
196132

197-
i++;
198-
} else if (currentState === 'string_double') {
199-
if (char === '\\') {
200-
i += 2;
201-
continue;
202-
}
203-
if (char === '"') {
204-
currentState = 'normal';
205-
}
206-
i++;
207-
} else if (currentState === 'string_single') {
208-
if (char === '\\') {
209-
i += 2;
210-
continue;
211-
}
212-
if (char === "'") {
213-
currentState = 'normal';
214-
}
215-
i++;
216-
} else if (currentState === 'template') {
217-
if (char === '\\') {
218-
i += 2;
219-
continue;
220-
}
221-
if (char === '$' && nextChar === '{') {
222-
// Entering template literal interpolation context
223-
currentState = 'normal';
224-
stack.push({ type: 'template', braceDepth: 0 });
225-
i += 2;
226-
continue;
227-
}
228-
if (char === '`') {
229-
stack.pop();
230-
currentState = 'normal';
231-
}
232-
i++;
233-
} else if (currentState === 'comment_block') {
234-
if (char === '*' && nextChar === '/') {
235-
currentState = 'normal';
236-
i += 2;
237-
continue;
238-
}
239-
i++;
240-
} else if (currentState === 'comment_line') {
241-
if (char === '\n' || char === '\r') {
242-
currentState = 'normal';
133+
for (const comment of comments) {
134+
if (comment.type !== 'Line' || !comment.value.startsWith(SOURCEMAP_COMMENT_VALUE_PREFIX)) {
135+
continue;
136+
}
137+
138+
let start = comment.start;
139+
let end = comment.end;
140+
141+
// Preserve line terminators on non-trailing comments so subsequent line numbers stay aligned
142+
// with already-generated sourcemaps (and ASI is unaffected when comments follow code). When a
143+
// comment is the only content on its line, strip leading horizontal whitespace—and if only
144+
// whitespace follows to EOF, strip to the end of the file to match the trailing fast path.
145+
let lineStart = start;
146+
while (lineStart > lastCopiedIndex && isHorizontalWhitespace(code.charCodeAt(lineStart - 1))) {
147+
lineStart--;
148+
}
149+
if (lineStart === 0 || isLineTerminator(code.charCodeAt(lineStart - 1))) {
150+
start = lineStart;
151+
if (isWhitespaceToEnd(code, end)) {
152+
end = code.length;
243153
}
244-
i++;
245154
}
155+
156+
if (start > lastCopiedIndex) {
157+
result.push(code.slice(lastCopiedIndex, start));
158+
}
159+
lastCopiedIndex = end;
246160
}
247161

248162
if (lastCopiedIndex === 0) {
249163
return code;
250164
}
251165

252-
if (lastCopiedIndex < len) {
166+
if (lastCopiedIndex < code.length) {
253167
result.push(code.slice(lastCopiedIndex));
254168
}
255169

256170
return result.join('');
257171
}
258172

173+
function isHorizontalWhitespace(code: number): boolean {
174+
return code === 32 || code === 9; // space, tab
175+
}
176+
177+
function isLineTerminator(code: number): boolean {
178+
return code === 10 || code === 13; // \n, \r
179+
}
180+
181+
function isWhitespaceToEnd(code: string, start: number): boolean {
182+
for (let i = start; i < code.length; i++) {
183+
const char = code.charCodeAt(i);
184+
if (!isHorizontalWhitespace(char) && !isLineTerminator(char)) {
185+
return false;
186+
}
187+
}
188+
189+
return true;
190+
}
191+
192+
function isBase64Char(code: number): boolean {
193+
return (
194+
(code >= 65 && code <= 90) || // A-Z
195+
(code >= 97 && code <= 122) || // a-z
196+
(code >= 48 && code <= 57) || // 0-9
197+
code === 43 || // +
198+
code === 47 || // /
199+
code === 61 || // =
200+
code === 45 || // -
201+
code === 95 // _
202+
);
203+
}
204+
259205
/**
260206
* Extracts the base64 payload from an inline sourcemap data URI line and verifies
261207
* that only trailing whitespace follows the payload.
@@ -274,13 +220,22 @@ function extractTrailingBase64Payload(urlLine: string): string | undefined {
274220

275221
const payloadStart = base64StartIndex + 7;
276222
let payloadEnd = urlLine.length;
277-
// Find the first trailing whitespace character that marks the end of the base64 payload.
223+
// Find the first trailing whitespace character that marks the end of the base64 payload,
224+
// while ensuring all payload characters are valid base64 (preventing false positives when
225+
// closing quotes, backticks, or `*/` immediately follow the payload).
278226
for (let i = payloadStart; i < urlLine.length; i++) {
279227
const char = urlLine[i];
280228
if (char === ' ' || char === '\r' || char === '\n' || char === '\t') {
281229
payloadEnd = i;
282230
break;
283231
}
232+
if (!isBase64Char(urlLine.charCodeAt(i))) {
233+
return undefined;
234+
}
235+
}
236+
237+
if (payloadEnd === payloadStart) {
238+
return undefined;
284239
}
285240

286241
// Verify that everything after the base64 payload is trailing whitespace
@@ -307,7 +262,7 @@ function extractTrailingUrl(urlLine: string): string | undefined {
307262
}
308263

309264
const urlMatch = /^([^\r\n\s'"`]+)/.exec(urlLine);
310-
if (!urlMatch) {
265+
if (!urlMatch || urlMatch[1].includes('*/')) {
311266
return undefined;
312267
}
313268

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.