Skip to content

Commit 495112d

Browse files
author
Benjamin Pasero
committed
Revert "decouple vs/base/node/encoding.ts from node streams for microsoft#79275 (microsoft#99413)"
This reverts commit 1dbfecd.
1 parent 1dbfecd commit 495112d

12 files changed

Lines changed: 239 additions & 240 deletions

File tree

build/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"gulp-bom": "^1.0.0",
3939
"gulp-sourcemaps": "^1.11.0",
4040
"gulp-uglify": "^3.0.0",
41-
"iconv-lite": "0.6.0",
41+
"iconv-lite": "0.4.23",
4242
"mime": "^1.3.4",
4343
"minimatch": "3.0.4",
4444
"minimist": "^1.2.3",

build/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,10 +1415,10 @@ http-signature@~1.2.0:
14151415
jsprim "^1.2.2"
14161416
sshpk "^1.7.0"
14171417

1418-
iconv-lite@0.6.0:
1419-
version "0.6.0"
1420-
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.0.tgz#66a93b80df0bd05d2a43a7426296b7f91073f125"
1421-
integrity sha512-43ZpGYZ9QtuutX5l6WC1DSO8ane9N+Ct5qPLF2OV7vM9abM69gnAbVkh66ibaZd3aOGkoP1ZmringlKhLBkw2Q==
1418+
iconv-lite@0.4.23:
1419+
version "0.4.23"
1420+
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
1421+
integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
14221422
dependencies:
14231423
safer-buffer ">= 2.1.2 < 3"
14241424

extensions/git/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1878,7 +1878,7 @@
18781878
"dependencies": {
18791879
"byline": "^5.0.0",
18801880
"file-type": "^7.2.0",
1881-
"iconv-lite": "0.6.0",
1881+
"iconv-lite": "^0.4.24",
18821882
"jschardet": "2.1.1",
18831883
"vscode-extension-telemetry": "0.1.1",
18841884
"vscode-nls": "^4.0.0",

extensions/git/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -425,10 +425,10 @@ https-proxy-agent@^2.2.1:
425425
agent-base "^4.3.0"
426426
debug "^3.1.0"
427427

428-
iconv-lite@0.6.0:
429-
version "0.6.0"
430-
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.0.tgz#66a93b80df0bd05d2a43a7426296b7f91073f125"
431-
integrity sha512-43ZpGYZ9QtuutX5l6WC1DSO8ane9N+Ct5qPLF2OV7vM9abM69gnAbVkh66ibaZd3aOGkoP1ZmringlKhLBkw2Q==
428+
iconv-lite@^0.4.24:
429+
version "0.4.24"
430+
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
431+
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
432432
dependencies:
433433
safer-buffer ">= 2.1.2 < 3"
434434

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"graceful-fs": "4.2.3",
4242
"http-proxy-agent": "^2.1.0",
4343
"https-proxy-agent": "^2.2.3",
44-
"iconv-lite": "0.6.0",
44+
"iconv-lite": "0.5.0",
4545
"jschardet": "2.1.1",
4646
"keytar": "^5.5.0",
4747
"minimist": "^1.2.5",

remote/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"graceful-fs": "4.2.3",
99
"http-proxy-agent": "^2.1.0",
1010
"https-proxy-agent": "^2.2.3",
11-
"iconv-lite": "0.6.0",
11+
"iconv-lite": "0.5.0",
1212
"jschardet": "2.1.1",
1313
"minimist": "^1.2.5",
1414
"native-watchdog": "1.3.0",

remote/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,10 @@ https-proxy-agent@^2.2.3:
176176
agent-base "^4.3.0"
177177
debug "^3.1.0"
178178

179-
iconv-lite@0.6.0:
180-
version "0.6.0"
181-
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.0.tgz#66a93b80df0bd05d2a43a7426296b7f91073f125"
182-
integrity sha512-43ZpGYZ9QtuutX5l6WC1DSO8ane9N+Ct5qPLF2OV7vM9abM69gnAbVkh66ibaZd3aOGkoP1ZmringlKhLBkw2Q==
179+
iconv-lite@0.5.0:
180+
version "0.5.0"
181+
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.0.tgz#59cdde0a2a297cc2aeb0c6445a195ee89f127550"
182+
integrity sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==
183183
dependencies:
184184
safer-buffer ">= 2.1.2 < 3"
185185

src/vs/base/node/encoding.ts

Lines changed: 86 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as iconv from 'iconv-lite';
7-
import { Readable, ReadableStream, newWriteableStream } from 'vs/base/common/stream';
8-
import { isUndefinedOrNull, isUndefined, isNumber } from 'vs/base/common/types';
9-
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
7+
import { Readable, Writable } from 'stream';
8+
import { VSBuffer } from 'vs/base/common/buffer';
109

1110
export const UTF8 = 'utf8';
1211
export const UTF8_with_bom = 'utf8bom';
@@ -36,135 +35,121 @@ export interface IDecodeStreamOptions {
3635
}
3736

3837
export interface IDecodeStreamResult {
39-
stream: ReadableStream<string>;
38+
stream: NodeJS.ReadableStream;
4039
detected: IDetectedEncodingResult;
4140
}
4241

43-
export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
42+
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
4443
if (!options.minBytesRequiredForDetection) {
4544
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
4645
}
4746

4847
return new Promise<IDecodeStreamResult>((resolve, reject) => {
49-
const target = newWriteableStream<string>(strings => strings.join(''));
50-
51-
const bufferedChunks: VSBuffer[] = [];
52-
let bytesBuffered = 0;
53-
let decoder: iconv.DecoderStream | null = null;
54-
55-
const startDecodeStream = () => {
56-
return Promise.resolve()
57-
.then(() =>
58-
// detect encoding from buffer
59-
detectEncodingFromBuffer({
60-
buffer: Buffer.from(VSBuffer.concat(bufferedChunks).buffer),
61-
bytesRead: bytesBuffered
62-
}, options.guessEncoding)
63-
)
64-
.then(detected => {
65-
// ensure to respect overwrite of encoding
66-
detected.encoding = options.overwriteEncoding(detected.encoding);
48+
const writer = new class extends Writable {
49+
private decodeStream: NodeJS.ReadWriteStream | undefined;
50+
private decodeStreamPromise: Promise<void> | undefined;
6751

68-
// decode and write buffered content
69-
decoder = iconv.getDecoder(toNodeEncoding(detected.encoding));
70-
const nodeBuffer = Buffer.from(VSBuffer.concat(bufferedChunks).buffer);
71-
target.write(decoder.write(nodeBuffer));
72-
bufferedChunks.length = 0;
52+
private bufferedChunks: Buffer[] = [];
53+
private bytesBuffered = 0;
7354

74-
// signal to the outside our detected encoding
75-
// and final decoder stream
76-
resolve({
77-
stream: target,
78-
detected,
79-
});
80-
})
81-
.catch(reject);
82-
};
55+
_write(chunk: Buffer, encoding: string, callback: (error: Error | null | undefined) => void): void {
56+
if (!Buffer.isBuffer(chunk)) {
57+
return callback(new Error('toDecodeStream(): data must be a buffer'));
58+
}
8359

84-
source.on('error', target.error);
85-
source.on('data', (chunk) => {
86-
// if the decoder is ready, we just write directly
87-
if (!isUndefinedOrNull(decoder)) {
88-
target.write(decoder.write(Buffer.from(chunk.buffer)));
89-
return;
90-
}
60+
// if the decode stream is ready, we just write directly
61+
if (this.decodeStream) {
62+
this.decodeStream.write(chunk, callback);
9163

92-
// otherwise we need to buffer the data until the stream is ready
93-
bufferedChunks.push(chunk);
94-
bytesBuffered += chunk.byteLength;
64+
return;
65+
}
9566

96-
// buffered enough data for encoding detection, create stream and forward data
97-
if (isNumber(options.minBytesRequiredForDetection) && bytesBuffered >= options.minBytesRequiredForDetection) {
98-
startDecodeStream();
99-
}
100-
});
101-
source.on('end', () => {
102-
// normal finish
103-
if (!isUndefinedOrNull(decoder)) {
104-
target.end(decoder.end());
105-
}
67+
// otherwise we need to buffer the data until the stream is ready
68+
this.bufferedChunks.push(chunk);
69+
this.bytesBuffered += chunk.byteLength;
10670

107-
// we were still waiting for data to do the encoding
108-
// detection. thus, wrap up starting the stream even
109-
// without all the data to get things going
110-
else {
111-
startDecodeStream().then(() => {
112-
target.end(decoder?.end());
113-
});
71+
// waiting for the decoder to be ready
72+
if (this.decodeStreamPromise) {
73+
this.decodeStreamPromise.then(() => callback(null), error => callback(error));
74+
}
75+
76+
// buffered enough data for encoding detection, create stream and forward data
77+
else if (typeof options.minBytesRequiredForDetection === 'number' && this.bytesBuffered >= options.minBytesRequiredForDetection) {
78+
this._startDecodeStream(callback);
79+
}
80+
81+
// only buffering until enough data for encoding detection is there
82+
else {
83+
callback(null);
84+
}
11485
}
115-
});
116-
});
117-
}
11886

119-
export function toEncodeReadable(readable: Readable<string>, encoding: string, options?: { addBOM?: boolean }): VSBufferReadable {
120-
const encoder = iconv.getEncoder(toNodeEncoding(encoding), options);
121-
let bytesRead = 0;
122-
let done = false;
87+
_startDecodeStream(callback: (error: Error | null | undefined) => void): void {
88+
89+
// detect encoding from buffer
90+
this.decodeStreamPromise = Promise.resolve(detectEncodingFromBuffer({
91+
buffer: Buffer.concat(this.bufferedChunks),
92+
bytesRead: this.bytesBuffered
93+
}, options.guessEncoding)).then(detected => {
94+
95+
// ensure to respect overwrite of encoding
96+
detected.encoding = options.overwriteEncoding(detected.encoding);
97+
98+
// decode and write buffer
99+
this.decodeStream = decodeStream(detected.encoding);
100+
this.decodeStream.write(Buffer.concat(this.bufferedChunks), callback);
101+
this.bufferedChunks.length = 0;
123102

124-
return {
125-
read() {
126-
if (done) {
127-
return null;
103+
// signal to the outside our detected encoding
104+
// and final decoder stream
105+
resolve({ detected, stream: this.decodeStream });
106+
}, error => {
107+
this.emit('error', error);
108+
109+
callback(error);
110+
});
128111
}
129112

130-
const chunk = readable.read();
131-
if (isUndefinedOrNull(chunk)) {
132-
done = true;
133-
134-
// If we are instructed to add a BOM but we detect that no
135-
// bytes have been read, we must ensure to return the BOM
136-
// ourselves so that we comply with the contract.
137-
if (bytesRead === 0 && options?.addBOM) {
138-
switch (encoding) {
139-
case UTF8:
140-
case UTF8_with_bom:
141-
return VSBuffer.wrap(Buffer.from(UTF8_BOM));
142-
case UTF16be:
143-
return VSBuffer.wrap(Buffer.from(UTF16be_BOM));
144-
case UTF16le:
145-
return VSBuffer.wrap(Buffer.from(UTF16le_BOM));
146-
}
147-
}
113+
_final(callback: () => void) {
148114

149-
const leftovers = encoder.end();
150-
if (!isUndefined(leftovers) && leftovers.length > 0) {
151-
return VSBuffer.wrap(leftovers);
115+
// normal finish
116+
if (this.decodeStream) {
117+
this.decodeStream.end(callback);
152118
}
153119

154-
return null;
120+
// we were still waiting for data to do the encoding
121+
// detection. thus, wrap up starting the stream even
122+
// without all the data to get things going
123+
else {
124+
this._startDecodeStream(() => {
125+
if (this.decodeStream) {
126+
this.decodeStream.end(callback);
127+
}
128+
});
129+
}
155130
}
131+
};
156132

157-
bytesRead += chunk.length;
133+
// errors
134+
readable.on('error', reject);
158135

159-
return VSBuffer.wrap(encoder.write(chunk));
160-
}
161-
};
136+
// pipe through
137+
readable.pipe(writer);
138+
});
162139
}
163140

164141
export function encodingExists(encoding: string): boolean {
165142
return iconv.encodingExists(toNodeEncoding(encoding));
166143
}
167144

145+
function decodeStream(encoding: string | null): NodeJS.ReadWriteStream {
146+
return iconv.decodeStream(toNodeEncoding(encoding));
147+
}
148+
149+
export function encodeStream(encoding: string, options?: { addBOM?: boolean }): NodeJS.ReadWriteStream {
150+
return iconv.encodeStream(toNodeEncoding(encoding), options);
151+
}
152+
168153
export function toNodeEncoding(enc: string | null): string {
169154
if (enc === UTF8_with_bom || enc === null) {
170155
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it

src/vs/base/node/stream.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { VSBufferReadableStream } from 'vs/base/common/buffer';
6+
import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer';
77
import { Readable } from 'stream';
8+
import { isUndefinedOrNull } from 'vs/base/common/types';
9+
import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding';
810

911
export function streamToNodeReadable(stream: VSBufferReadableStream): Readable {
1012
return new class extends Readable {
@@ -49,3 +51,62 @@ export function streamToNodeReadable(stream: VSBufferReadableStream): Readable {
4951
}
5052
};
5153
}
54+
55+
export function nodeReadableToString(stream: NodeJS.ReadableStream): Promise<string> {
56+
return new Promise((resolve, reject) => {
57+
let result = '';
58+
59+
stream.on('data', chunk => result += chunk);
60+
stream.on('error', reject);
61+
stream.on('end', () => resolve(result));
62+
});
63+
}
64+
65+
export function nodeStreamToVSBufferReadable(stream: NodeJS.ReadWriteStream, addBOM?: { encoding: UTF_ENCODING }): VSBufferReadable {
66+
let bytesRead = 0;
67+
let done = false;
68+
69+
return {
70+
read(): VSBuffer | null {
71+
if (done) {
72+
return null;
73+
}
74+
75+
const res = stream.read();
76+
if (isUndefinedOrNull(res)) {
77+
done = true;
78+
79+
// If we are instructed to add a BOM but we detect that no
80+
// bytes have been read, we must ensure to return the BOM
81+
// ourselves so that we comply with the contract.
82+
if (bytesRead === 0 && addBOM) {
83+
switch (addBOM.encoding) {
84+
case UTF8:
85+
case UTF8_with_bom:
86+
return VSBuffer.wrap(Buffer.from(UTF8_BOM));
87+
case UTF16be:
88+
return VSBuffer.wrap(Buffer.from(UTF16be_BOM));
89+
case UTF16le:
90+
return VSBuffer.wrap(Buffer.from(UTF16le_BOM));
91+
}
92+
}
93+
94+
return null;
95+
}
96+
97+
// Handle String
98+
if (typeof res === 'string') {
99+
bytesRead += res.length;
100+
101+
return VSBuffer.fromString(res);
102+
}
103+
104+
// Handle Buffer
105+
else {
106+
bytesRead += res.byteLength;
107+
108+
return VSBuffer.wrap(res);
109+
}
110+
}
111+
};
112+
}

0 commit comments

Comments
 (0)