Skip to content

Commit d413c32

Browse files
committed
lib: unify ICU and no-ICU TextDecoder
1 parent 0847010 commit d413c32

File tree

1 file changed

+67
-107
lines changed

1 file changed

+67
-107
lines changed

lib/internal/encoding.js

Lines changed: 67 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const {
2525
ERR_INVALID_THIS,
2626
ERR_NO_ICU,
2727
} = require('internal/errors').codes;
28-
const kMethod = Symbol('method');
28+
const kSingleByte = Symbol('method');
2929
const kHandle = Symbol('handle');
3030
const kFlags = Symbol('flags');
3131
const kEncoding = Symbol('encoding');
@@ -447,137 +447,99 @@ function parseInput(input) {
447447
}
448448
}
449449

450-
const TextDecoder =
451-
internalBinding('config').hasIntl ?
452-
makeTextDecoderICU() :
453-
makeTextDecoderJS();
450+
const { hasIntl } = internalBinding('config');
454451

455-
function makeTextDecoderICU() {
456-
const {
452+
let _decode, getConverter
453+
if (hasIntl) {
454+
;({
457455
decode: _decode,
458456
getConverter,
459-
} = internalBinding('icu');
457+
} = internalBinding('icu'));
458+
}
460459

461-
class TextDecoder {
462-
constructor(encoding = 'utf-8', options = kEmptyObject) {
463-
encoding = `${encoding}`;
464-
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
460+
const kBOMSeen = Symbol('BOM seen');
465461

466-
const enc = getEncodingFromLabel(encoding);
467-
if (enc === undefined)
468-
throw new ERR_ENCODING_NOT_SUPPORTED(encoding);
462+
let StringDecoder;
463+
function lazyStringDecoder() {
464+
if (StringDecoder === undefined)
465+
({ StringDecoder } = require('string_decoder'));
466+
return StringDecoder;
467+
}
469468

470-
let flags = 0;
471-
if (options !== null) {
472-
flags |= options.fatal ? CONVERTER_FLAGS_FATAL : 0;
473-
flags |= options.ignoreBOM ? CONVERTER_FLAGS_IGNORE_BOM : 0;
474-
}
469+
class TextDecoder {
470+
constructor(encoding = 'utf-8', options = kEmptyObject) {
471+
encoding = `${encoding}`;
472+
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
475473

476-
this[kDecoder] = true;
477-
this[kFlags] = flags;
478-
this[kEncoding] = enc;
479-
this[kIgnoreBOM] = Boolean(options?.ignoreBOM);
480-
this[kFatal] = Boolean(options?.fatal);
481-
// Only support fast path for UTF-8.
482-
this[kUTF8FastPath] = enc === 'utf-8';
483-
this[kHandle] = undefined;
484-
this[kMethod] = undefined;
485-
486-
if (isSinglebyteEncoding(this.encoding)) {
487-
this[kMethod] = createSinglebyteDecoder(this.encoding, this[kFatal]);
488-
} else if (!this[kUTF8FastPath]) {
489-
this.#prepareConverter();
490-
}
474+
const enc = getEncodingFromLabel(encoding);
475+
if (enc === undefined)
476+
throw new ERR_ENCODING_NOT_SUPPORTED(encoding);
477+
478+
let flags = 0;
479+
if (options !== null) {
480+
flags |= options.fatal ? CONVERTER_FLAGS_FATAL : 0;
481+
flags |= options.ignoreBOM ? CONVERTER_FLAGS_IGNORE_BOM : 0;
491482
}
492483

493-
#prepareConverter() {
494-
if (this[kHandle] !== undefined) return;
484+
this[kDecoder] = true;
485+
this[kFlags] = flags;
486+
this[kEncoding] = enc;
487+
this[kIgnoreBOM] = Boolean(options?.ignoreBOM);
488+
this[kFatal] = Boolean(options?.fatal);
489+
// Only support fast path for UTF-8.
490+
this[kUTF8FastPath] = enc === 'utf-8';
491+
this[kHandle] = undefined;
492+
this[kSingleByte] = undefined; // Does not care about streaming or BOM
493+
494+
if (isSinglebyteEncoding(enc)) {
495+
this[kSingleByte] = createSinglebyteDecoder(enc, this[kFatal]);
496+
} else if (!this[kUTF8FastPath]) {
497+
this.#prepareConverter();
498+
}
499+
}
500+
501+
#prepareConverter() {
502+
if (this[kHandle] !== undefined) return;
503+
if (hasIntl) {
495504
let icuEncoding = this[kEncoding];
496505
if (icuEncoding === 'gbk') icuEncoding = 'gb18030'; // 10.1.1. GBK's decoder is gb18030's decoder
497506
const handle = getConverter(icuEncoding, this[kFlags]);
498507
if (handle === undefined)
499508
throw new ERR_ENCODING_NOT_SUPPORTED(this[kEncoding]);
500509
this[kHandle] = handle;
501-
}
502-
503-
decode(input = empty, options = kEmptyObject) {
504-
validateDecoder(this);
505-
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
506-
507-
if (this[kMethod]) return this[kMethod](input);
508-
509-
this[kUTF8FastPath] &&= !(options?.stream);
510-
511-
if (this[kUTF8FastPath]) {
512-
return decodeUTF8(input, this[kIgnoreBOM], this[kFatal]);
510+
} else {
511+
if (enc !== 'utf-8' && enc !== 'utf-16le') {
512+
throw new ERR_ENCODING_NOT_SUPPORTED(`${encoding}`);
513513
}
514514

515-
this.#prepareConverter();
516-
517-
let flags = 0;
518-
if (options !== null)
519-
flags |= options.stream ? 0 : CONVERTER_FLAGS_FLUSH;
520-
521-
return _decode(this[kHandle], input, flags, this.encoding);
515+
if (this[kFatal]) throw new ERR_NO_ICU('"fatal" option');
516+
// StringDecoder will normalize WHATWG encoding to Node.js encoding.
517+
this[kHandle] = new (lazyStringDecoder())(enc);
518+
this[kBOMSeen] = false;
522519
}
523520
}
524521

525-
return TextDecoder;
526-
}
527-
528-
function makeTextDecoderJS() {
529-
let StringDecoder;
530-
function lazyStringDecoder() {
531-
if (StringDecoder === undefined)
532-
({ StringDecoder } = require('string_decoder'));
533-
return StringDecoder;
534-
}
535-
536-
const kBOMSeen = Symbol('BOM seen');
537-
538-
function hasConverter(encoding) {
539-
return encoding === 'utf-8' || encoding === 'utf-16le';
540-
}
522+
decode(input = empty, options = kEmptyObject) {
523+
validateDecoder(this);
524+
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
541525

542-
class TextDecoder {
543-
constructor(encoding = 'utf-8', options = kEmptyObject) {
544-
encoding = `${encoding}`;
545-
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
526+
if (this[kSingleByte]) return this[kSingleByte](input);
546527

547-
const enc = getEncodingFromLabel(encoding);
548-
if (enc === undefined)
549-
throw new ERR_ENCODING_NOT_SUPPORTED(encoding);
528+
this[kUTF8FastPath] &&= !(options?.stream);
550529

551-
let flags = 0;
552-
if (options !== null) {
553-
flags |= options.fatal ? CONVERTER_FLAGS_FATAL : 0;
554-
flags |= options.ignoreBOM ? CONVERTER_FLAGS_IGNORE_BOM : 0;
555-
}
556-
557-
this[kDecoder] = true;
558-
this[kFlags] = flags;
559-
this[kEncoding] = enc;
560-
this[kIgnoreBOM] = Boolean(options?.ignoreBOM);
561-
this[kFatal] = Boolean(options?.fatal);
562-
this[kBOMSeen] = false;
563-
this[kMethod] = undefined;
564-
565-
if (isSinglebyteEncoding(enc)) {
566-
this[kMethod] = createSinglebyteDecoder(enc, this[kFatal]);
567-
} else {
568-
if (!hasConverter(enc)) throw new ERR_ENCODING_NOT_SUPPORTED(encoding);
569-
if (this[kFatal]) throw new ERR_NO_ICU('"fatal" option');
570-
// StringDecoder will normalize WHATWG encoding to Node.js encoding.
571-
this[kHandle] = new (lazyStringDecoder())(enc);
572-
}
530+
if (this[kUTF8FastPath]) {
531+
return decodeUTF8(input, this[kIgnoreBOM], this[kFatal]);
573532
}
574533

575-
decode(input = empty, options = kEmptyObject) {
576-
validateDecoder(this);
577-
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
534+
this.#prepareConverter();
578535

579-
if (this[kMethod]) return this[kMethod](input);
536+
if (hasIntl) {
537+
let flags = 0;
538+
if (options !== null)
539+
flags |= options.stream ? 0 : CONVERTER_FLAGS_FLUSH;
580540

541+
return _decode(this[kHandle], input, flags, this.encoding);
542+
} else {
581543
if (this[kFlags] & CONVERTER_FLAGS_FLUSH) {
582544
this[kBOMSeen] = false;
583545
}
@@ -605,8 +567,6 @@ function makeTextDecoderJS() {
605567
return result;
606568
}
607569
}
608-
609-
return TextDecoder;
610570
}
611571

612572
// Mix in some shared properties.

0 commit comments

Comments
 (0)