From 17a177011d3cd398a171ac12c32ae3b95831f142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20M=C3=BCller?= Date: Mon, 15 May 2017 09:57:54 +0200 Subject: [PATCH 1/5] Move linting into an npm script. Relates to #2805 --- Makefile | 3 +-- package.json | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 33408bfbad..ad8b097e4e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ BROWSERIFY := "node_modules/.bin/browserify" -ESLINT := "node_modules/.bin/eslint" KARMA := "node_modules/.bin/karma" MOCHA := "bin/mocha" NYC := "node_modules/.bin/nyc" @@ -34,7 +33,7 @@ clean: lint: @printf "==> [Test :: Lint]\n" - $(ESLINT) . "bin/*" + npm run lint test-node: test-bdd test-tdd test-qunit test-exports test-unit test-integration test-jsapi test-compilers test-glob test-requires test-reporters test-only test-global-only diff --git a/package.json b/package.json index f681ba3e16..a02ab9e803 100644 --- a/package.json +++ b/package.json @@ -299,6 +299,7 @@ "npm": ">= 1.4.x" }, "scripts": { + "lint": "eslint . bin/*", "test": "make test && make clean", "precoverage": "rm -rf coverage", "coverage": "COVERAGE=true npm run test", From 3e7152f1bbcf0715d115a2281383c6d84fa47462 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 22 May 2017 21:40:51 +0200 Subject: [PATCH 2/5] Remove call to deprecated os.tmpDir (#2802) --- test/acceptance/file-utils.spec.js | 2 +- test/integration/reporters.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/acceptance/file-utils.spec.js b/test/acceptance/file-utils.spec.js index 12dbddf878..540589821d 100644 --- a/test/acceptance/file-utils.spec.js +++ b/test/acceptance/file-utils.spec.js @@ -8,7 +8,7 @@ var mkdirp = require('mkdirp'); var rimraf = require('rimraf'); describe('file utils', function () { - var tmpDir = path.join(os.tmpDir(), 'mocha-file-lookup'); + var tmpDir = path.join(os.tmpdir(), 'mocha-file-lookup'); var existsSync = fs.existsSync; var tmpFile = path.join.bind(path, tmpDir); var symlinkSupported = false; diff --git a/test/integration/reporters.spec.js b/test/integration/reporters.spec.js index 15b7e6533b..19b4d8e30c 100644 --- a/test/integration/reporters.spec.js +++ b/test/integration/reporters.spec.js @@ -36,7 +36,7 @@ describe('reporters', function () { describe('xunit', function () { it('prints test cases with --reporter-options output (issue: 1864)', function (done) { var randomStr = crypto.randomBytes(8).toString('hex'); - var tmpDir = os.tmpDir().replace(new RegExp(path.sep + '$'), ''); + var tmpDir = os.tmpdir().replace(new RegExp(path.sep + '$'), ''); var tmpFile = tmpDir + path.sep + 'test-issue-1864-' + randomStr + '.xml'; var args = ['--reporter=xunit', '--reporter-options', 'output=' + tmpFile]; From 10ff0ec56932e575d93066de0c602c4c854072db Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 24 May 2017 08:34:20 -0700 Subject: [PATCH 3/5] Eagerly set process.exitCode (#2820) To workaround process being terminated earlier than expected. Simpler version of PR #2714 which only eagerly sets the exitcode without playing with the draining semantics. --- bin/_mocha | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/_mocha b/bin/_mocha index 00514b5ce0..7f3d01aa1d 100755 --- a/bin/_mocha +++ b/bin/_mocha @@ -476,12 +476,18 @@ function exitLater (code) { } function exit (code) { + var clampedCode = Math.min(code, 255); + + // Eagerly set the process's exit code in case stream.write doesn't + // execute its callback before the process terminates. + process.exitCode = clampedCode; + // flush output for Node.js Windows pipe bug // https://github.com/joyent/node/issues/6247 is just one bug example // https://github.com/visionmedia/mocha/issues/333 has a good discussion function done () { if (!(draining--)) { - process.exit(Math.min(code, 255)); + process.exit(clampedCode); } } From fc802a9ea5665dada4ae9f30738a34f237036aa3 Mon Sep 17 00:00:00 2001 From: David da Silva Date: Wed, 24 May 2017 17:56:48 +0200 Subject: [PATCH 4/5] :memo: Add Changelog for v3.4.2 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7544a7254..98d445b880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# 3.4.2 / 2017-05-24 + +## :bug: Fixes + +- [#2802]: Remove call to deprecated os.tmpDir ([@makepanic]) +- [#2820]: Eagerly set process.exitCode ([@chrisleck]) + +## :nut_and_bolt: Other + +- [#2778]: Move linting into an npm script ([@Munter]) + +[@chrisleck]: https://github.com/chrisleck +[@makepanic]: https://github.com/makepanic +[@Munter]: https://github.com/Munter + +[#2802]: https://github.com/mochajs/mocha/issues/2802 +[#2820]: https://github.com/mochajs/mocha/pull/2820 + # 3.4.1 / 2017-05-14 Fixed a publishing mishap with git's autocrlf settings. From a15b20aca567a2e57129bccae773e782796bfaa3 Mon Sep 17 00:00:00 2001 From: David da Silva Date: Wed, 24 May 2017 17:59:03 +0200 Subject: [PATCH 5/5] :ship: Release v3.4.2 --- mocha.js | 607 +++++++++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 272 insertions(+), 337 deletions(-) diff --git a/mocha.js b/mocha.js index 07bf7ac288..4e59d156cf 100644 --- a/mocha.js +++ b/mocha.js @@ -6775,7 +6775,7 @@ exports.isPromise = function isPromise (value) { exports.noop = function () {}; }).call(this,require('_process'),require("buffer").Buffer) -},{"./to-iso-string":37,"_process":82,"buffer":44,"debug":2,"fs":42,"glob":42,"json3":69,"path":42,"util":100}],39:[function(require,module,exports){ +},{"./to-iso-string":37,"_process":82,"buffer":44,"debug":2,"fs":42,"glob":42,"json3":69,"path":42,"util":99}],39:[function(require,module,exports){ 'use strict' exports.byteLength = byteLength @@ -6922,7 +6922,7 @@ BrowserStdout.prototype._write = function(chunks, encoding, cb) { } }).call(this,require('_process')) -},{"_process":82,"stream":95,"util":100}],42:[function(require,module,exports){ +},{"_process":82,"stream":94,"util":99}],42:[function(require,module,exports){ arguments[4][40][0].apply(exports,arguments) },{"dup":40}],43:[function(require,module,exports){ (function (global){ @@ -13319,10 +13319,6 @@ process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; -process.prependListener = noop; -process.prependOnceListener = noop; - -process.listeners = function (name) { return [] } process.binding = function (name) { throw new Error('process.binding is not supported'); @@ -13335,7 +13331,7 @@ process.chdir = function (dir) { process.umask = function() { return 0; }; },{}],83:[function(require,module,exports){ -module.exports = require('./lib/_stream_duplex.js'); +module.exports = require("./lib/_stream_duplex.js") },{"./lib/_stream_duplex.js":84}],84:[function(require,module,exports){ // a duplex stream is just a stream that is both readable and writable. @@ -13454,10 +13450,6 @@ var processNextTick = require('process-nextick-args'); var isArray = require('isarray'); /**/ -/**/ -var Duplex; -/**/ - Readable.ReadableState = ReadableState; /**/ @@ -13469,7 +13461,14 @@ var EElistenerCount = function (emitter, type) { /**/ /**/ -var Stream = require('./internal/streams/stream'); +var Stream; +(function () { + try { + Stream = require('st' + 'ream'); + } catch (_) {} finally { + if (!Stream) Stream = require('events').EventEmitter; + } +})(); /**/ var Buffer = require('buffer').Buffer; @@ -13497,11 +13496,7 @@ var StringDecoder; util.inherits(Readable, Stream); -var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; - function prependListener(emitter, event, fn) { - // Sadly this is not cacheable as some libraries bundle their own - // event emitter implementation with them. if (typeof emitter.prependListener === 'function') { return emitter.prependListener(event, fn); } else { @@ -13513,6 +13508,7 @@ function prependListener(emitter, event, fn) { } } +var Duplex; function ReadableState(options, stream) { Duplex = Duplex || require('./_stream_duplex'); @@ -13531,7 +13527,7 @@ function ReadableState(options, stream) { this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; // cast to ints. - this.highWaterMark = ~~this.highWaterMark; + this.highWaterMark = ~ ~this.highWaterMark; // A linked list is used to store data chunks instead of an array because the // linked list can remove elements from the beginning faster than @@ -13582,6 +13578,7 @@ function ReadableState(options, stream) { } } +var Duplex; function Readable(options) { Duplex = Duplex || require('./_stream_duplex'); @@ -13904,7 +13901,7 @@ function maybeReadMore_(stream, state) { // for virtual (non-string, non-buffer) streams, "length" is somewhat // arbitrary, and perhaps not very meaningful. Readable.prototype._read = function (n) { - this.emit('error', new Error('_read() is not implemented')); + this.emit('error', new Error('not implemented')); }; Readable.prototype.pipe = function (dest, pipeOpts) { @@ -14082,16 +14079,16 @@ Readable.prototype.unpipe = function (dest) { state.pipesCount = 0; state.flowing = false; - for (var i = 0; i < len; i++) { - dests[i].emit('unpipe', this); + for (var _i = 0; _i < len; _i++) { + dests[_i].emit('unpipe', this); }return this; } // try to find the right one. - var index = indexOf(state.pipes, dest); - if (index === -1) return this; + var i = indexOf(state.pipes, dest); + if (i === -1) return this; - state.pipes.splice(index, 1); + state.pipes.splice(i, 1); state.pipesCount -= 1; if (state.pipesCount === 1) state.pipes = state.pipes[0]; @@ -14223,9 +14220,10 @@ Readable.prototype.wrap = function (stream) { } // proxy certain important events. - for (var n = 0; n < kProxyEvents.length; n++) { - stream.on(kProxyEvents[n], self.emit.bind(self, kProxyEvents[n])); - } + var events = ['error', 'close', 'destroy', 'pause', 'resume']; + forEach(events, function (ev) { + stream.on(ev, self.emit.bind(self, ev)); + }); // when we try to consume some more bytes, simply unpause the // underlying stream. @@ -14378,7 +14376,7 @@ function indexOf(xs, x) { return -1; } }).call(this,require('_process')) -},{"./_stream_duplex":84,"./internal/streams/BufferList":89,"./internal/streams/stream":90,"_process":82,"buffer":44,"buffer-shims":43,"core-util-is":45,"events":63,"inherits":66,"isarray":68,"process-nextick-args":81,"string_decoder/":96,"util":40}],87:[function(require,module,exports){ +},{"./_stream_duplex":84,"./internal/streams/BufferList":89,"_process":82,"buffer":44,"buffer-shims":43,"core-util-is":45,"events":63,"inherits":66,"isarray":68,"process-nextick-args":81,"string_decoder/":95,"util":40}],87:[function(require,module,exports){ // a transform stream is a readable/writable stream where you do // something with the data. Sometimes it's called a "filter", // but that's not a great name for it, since that implies a thing where @@ -14475,6 +14473,7 @@ function Transform(options) { this._transformState = new TransformState(this); + // when the writable side finishes, then flush out anything remaining. var stream = this; // start out asking for a readable event once data is transformed. @@ -14491,10 +14490,9 @@ function Transform(options) { if (typeof options.flush === 'function') this._flush = options.flush; } - // When the writable side finishes, then flush out anything remaining. this.once('prefinish', function () { - if (typeof this._flush === 'function') this._flush(function (er, data) { - done(stream, er, data); + if (typeof this._flush === 'function') this._flush(function (er) { + done(stream, er); });else done(stream); }); } @@ -14515,7 +14513,7 @@ Transform.prototype.push = function (chunk, encoding) { // an error, then that'll put the hurt on the whole operation. If you // never call cb(), then you'll never get another chunk. Transform.prototype._transform = function (chunk, encoding, cb) { - throw new Error('_transform() is not implemented'); + throw new Error('Not implemented'); }; Transform.prototype._write = function (chunk, encoding, cb) { @@ -14545,11 +14543,9 @@ Transform.prototype._read = function (n) { } }; -function done(stream, er, data) { +function done(stream, er) { if (er) return stream.emit('error', er); - if (data !== null && data !== undefined) stream.push(data); - // if there's nothing in the write buffer, then that means // that nothing more will ever be provided var ws = stream._writableState; @@ -14579,10 +14575,6 @@ var processNextTick = require('process-nextick-args'); var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick; /**/ -/**/ -var Duplex; -/**/ - Writable.WritableState = WritableState; /**/ @@ -14597,7 +14589,14 @@ var internalUtil = { /**/ /**/ -var Stream = require('./internal/streams/stream'); +var Stream; +(function () { + try { + Stream = require('st' + 'ream'); + } catch (_) {} finally { + if (!Stream) Stream = require('events').EventEmitter; + } +})(); /**/ var Buffer = require('buffer').Buffer; @@ -14616,6 +14615,7 @@ function WriteReq(chunk, encoding, cb) { this.next = null; } +var Duplex; function WritableState(options, stream) { Duplex = Duplex || require('./_stream_duplex'); @@ -14635,9 +14635,8 @@ function WritableState(options, stream) { this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; // cast to ints. - this.highWaterMark = ~~this.highWaterMark; + this.highWaterMark = ~ ~this.highWaterMark; - // drain event flag. this.needDrain = false; // at the start of calling end() this.ending = false; @@ -14712,7 +14711,7 @@ function WritableState(options, stream) { this.corkedRequestsFree = new CorkedRequest(this); } -WritableState.prototype.getBuffer = function getBuffer() { +WritableState.prototype.getBuffer = function writableStateGetBuffer() { var current = this.bufferedRequest; var out = []; while (current) { @@ -14732,37 +14731,13 @@ WritableState.prototype.getBuffer = function getBuffer() { } catch (_) {} })(); -// Test _writableState for inheritance to account for Duplex streams, -// whose prototype chain only points to Readable. -var realHasInstance; -if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { - realHasInstance = Function.prototype[Symbol.hasInstance]; - Object.defineProperty(Writable, Symbol.hasInstance, { - value: function (object) { - if (realHasInstance.call(this, object)) return true; - - return object && object._writableState instanceof WritableState; - } - }); -} else { - realHasInstance = function (object) { - return object instanceof this; - }; -} - +var Duplex; function Writable(options) { Duplex = Duplex || require('./_stream_duplex'); - // Writable ctor is applied to Duplexes, too. - // `realHasInstance` is necessary because using plain `instanceof` - // would return false, as no `_writableState` property is attached. - - // Trying to use the custom `instanceof` for Writable here will also break the - // Node.js LazyTransform implementation, which has a non-trivial getter for - // `_writableState` that would lead to infinite recursion. - if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { - return new Writable(options); - } + // Writable ctor is applied to Duplexes, though they're not + // instanceof Writable, they're instanceof Readable. + if (!(this instanceof Writable) && !(this instanceof Duplex)) return new Writable(options); this._writableState = new WritableState(options, this); @@ -14790,16 +14765,20 @@ function writeAfterEnd(stream, cb) { processNextTick(cb, er); } -// Checks that a user-supplied chunk is valid, especially for the particular -// mode the stream is in. Currently this means that `null` is never accepted -// and undefined/non-string values are only allowed in object mode. +// If we get something that is not a buffer, string, null, or undefined, +// and we're not in objectMode, then that's an error. +// Otherwise stream chunks are all considered to be of length=1, and the +// watermarks determine how many objects to keep in the buffer, rather than +// how many bytes or characters. function validChunk(stream, state, chunk, cb) { var valid = true; var er = false; - + // Always throw error if a null is written + // if we are not in object mode then throw + // if it is not a buffer, string, or undefined. if (chunk === null) { er = new TypeError('May not write null values to stream'); - } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + } else if (!Buffer.isBuffer(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { er = new TypeError('Invalid non-string/buffer chunk'); } if (er) { @@ -14813,20 +14792,19 @@ function validChunk(stream, state, chunk, cb) { Writable.prototype.write = function (chunk, encoding, cb) { var state = this._writableState; var ret = false; - var isBuf = Buffer.isBuffer(chunk); if (typeof encoding === 'function') { cb = encoding; encoding = null; } - if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + if (Buffer.isBuffer(chunk)) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; if (typeof cb !== 'function') cb = nop; - if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + if (state.ended) writeAfterEnd(this, cb);else if (validChunk(this, state, chunk, cb)) { state.pendingcb++; - ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + ret = writeOrBuffer(this, state, chunk, encoding, cb); } return ret; @@ -14866,11 +14844,10 @@ function decodeChunk(state, chunk, encoding) { // if we're already writing something, then just put this // in the queue, and wait our turn. Otherwise, call _write // If we return false, then we need a drain event, so set that flag. -function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { - if (!isBuf) { - chunk = decodeChunk(state, chunk, encoding); - if (Buffer.isBuffer(chunk)) encoding = 'buffer'; - } +function writeOrBuffer(stream, state, chunk, encoding, cb) { + chunk = decodeChunk(state, chunk, encoding); + + if (Buffer.isBuffer(chunk)) encoding = 'buffer'; var len = state.objectMode ? 1 : chunk.length; state.length += len; @@ -14939,8 +14916,8 @@ function onwrite(stream, er) { asyncWrite(afterWrite, stream, state, finished, cb); /**/ } else { - afterWrite(stream, state, finished, cb); - } + afterWrite(stream, state, finished, cb); + } } } @@ -15020,7 +14997,7 @@ function clearBuffer(stream, state) { } Writable.prototype._write = function (chunk, encoding, cb) { - cb(new Error('_write() is not implemented')); + cb(new Error('not implemented')); }; Writable.prototype._writev = null; @@ -15091,6 +15068,7 @@ function CorkedRequest(state) { this.next = null; this.entry = null; + this.finish = function (err) { var entry = _this.entry; _this.entry = null; @@ -15108,7 +15086,7 @@ function CorkedRequest(state) { }; } }).call(this,require('_process')) -},{"./_stream_duplex":84,"./internal/streams/stream":90,"_process":82,"buffer":44,"buffer-shims":43,"core-util-is":45,"inherits":66,"process-nextick-args":81,"util-deprecate":97}],89:[function(require,module,exports){ +},{"./_stream_duplex":84,"_process":82,"buffer":44,"buffer-shims":43,"core-util-is":45,"events":63,"inherits":66,"process-nextick-args":81,"util-deprecate":96}],89:[function(require,module,exports){ 'use strict'; var Buffer = require('buffer').Buffer; @@ -15174,27 +15152,35 @@ BufferList.prototype.concat = function (n) { return ret; }; },{"buffer":44,"buffer-shims":43}],90:[function(require,module,exports){ -module.exports = require('events').EventEmitter; - -},{"events":63}],91:[function(require,module,exports){ -module.exports = require('./readable').PassThrough +module.exports = require("./lib/_stream_passthrough.js") -},{"./readable":92}],92:[function(require,module,exports){ +},{"./lib/_stream_passthrough.js":85}],91:[function(require,module,exports){ +(function (process){ +var Stream = (function (){ + try { + return require('st' + 'ream'); // hack to fix a circular dependency issue when used with browserify + } catch(_){} +}()); exports = module.exports = require('./lib/_stream_readable.js'); -exports.Stream = exports; +exports.Stream = Stream || exports; exports.Readable = exports; exports.Writable = require('./lib/_stream_writable.js'); exports.Duplex = require('./lib/_stream_duplex.js'); exports.Transform = require('./lib/_stream_transform.js'); exports.PassThrough = require('./lib/_stream_passthrough.js'); -},{"./lib/_stream_duplex.js":84,"./lib/_stream_passthrough.js":85,"./lib/_stream_readable.js":86,"./lib/_stream_transform.js":87,"./lib/_stream_writable.js":88}],93:[function(require,module,exports){ -module.exports = require('./readable').Transform +if (!process.browser && process.env.READABLE_STREAM === 'disable' && Stream) { + module.exports = Stream; +} + +}).call(this,require('_process')) +},{"./lib/_stream_duplex.js":84,"./lib/_stream_passthrough.js":85,"./lib/_stream_readable.js":86,"./lib/_stream_transform.js":87,"./lib/_stream_writable.js":88,"_process":82}],92:[function(require,module,exports){ +module.exports = require("./lib/_stream_transform.js") -},{"./readable":92}],94:[function(require,module,exports){ -module.exports = require('./lib/_stream_writable.js'); +},{"./lib/_stream_transform.js":87}],93:[function(require,module,exports){ +module.exports = require("./lib/_stream_writable.js") -},{"./lib/_stream_writable.js":88}],95:[function(require,module,exports){ +},{"./lib/_stream_writable.js":88}],94:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -15323,281 +15309,230 @@ Stream.prototype.pipe = function(dest, options) { return dest; }; -},{"events":63,"inherits":66,"readable-stream/duplex.js":83,"readable-stream/passthrough.js":91,"readable-stream/readable.js":92,"readable-stream/transform.js":93,"readable-stream/writable.js":94}],96:[function(require,module,exports){ -'use strict'; +},{"events":63,"inherits":66,"readable-stream/duplex.js":83,"readable-stream/passthrough.js":90,"readable-stream/readable.js":91,"readable-stream/transform.js":92,"readable-stream/writable.js":93}],95:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var Buffer = require('buffer').Buffer; -var bufferShim = require('buffer-shims'); -var isEncoding = Buffer.isEncoding || function (encoding) { - encoding = '' + encoding; - switch (encoding && encoding.toLowerCase()) { - case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': - return true; - default: - return false; - } -}; +var isBufferEncoding = Buffer.isEncoding + || function(encoding) { + switch (encoding && encoding.toLowerCase()) { + case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true; + default: return false; + } + } -function _normalizeEncoding(enc) { - if (!enc) return 'utf8'; - var retried; - while (true) { - switch (enc) { - case 'utf8': - case 'utf-8': - return 'utf8'; - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return 'utf16le'; - case 'latin1': - case 'binary': - return 'latin1'; - case 'base64': - case 'ascii': - case 'hex': - return enc; - default: - if (retried) return; // undefined - enc = ('' + enc).toLowerCase(); - retried = true; - } - } -}; -// Do not cache `Buffer.isEncoding` when checking encoding names as some -// modules monkey-patch it to support additional encodings -function normalizeEncoding(enc) { - var nenc = _normalizeEncoding(enc); - if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); - return nenc || enc; +function assertEncoding(encoding) { + if (encoding && !isBufferEncoding(encoding)) { + throw new Error('Unknown encoding: ' + encoding); + } } // StringDecoder provides an interface for efficiently splitting a series of // buffers into a series of JS strings without breaking apart multi-byte -// characters. -exports.StringDecoder = StringDecoder; -function StringDecoder(encoding) { - this.encoding = normalizeEncoding(encoding); - var nb; +// characters. CESU-8 is handled as part of the UTF-8 encoding. +// +// @TODO Handling all encodings inside a single object makes it very difficult +// to reason about this code, so it should be split up in the future. +// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code +// points as used by CESU-8. +var StringDecoder = exports.StringDecoder = function(encoding) { + this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, ''); + assertEncoding(encoding); switch (this.encoding) { - case 'utf16le': - this.text = utf16Text; - this.end = utf16End; - nb = 4; - break; case 'utf8': - this.fillLast = utf8FillLast; - nb = 4; + // CESU-8 represents each of Surrogate Pair by 3-bytes + this.surrogateSize = 3; + break; + case 'ucs2': + case 'utf16le': + // UTF-16 represents each of Surrogate Pair by 2-bytes + this.surrogateSize = 2; + this.detectIncompleteChar = utf16DetectIncompleteChar; break; case 'base64': - this.text = base64Text; - this.end = base64End; - nb = 3; + // Base-64 stores 3 bytes in 4 chars, and pads the remainder. + this.surrogateSize = 3; + this.detectIncompleteChar = base64DetectIncompleteChar; break; default: - this.write = simpleWrite; - this.end = simpleEnd; + this.write = passThroughWrite; return; } - this.lastNeed = 0; - this.lastTotal = 0; - this.lastChar = bufferShim.allocUnsafe(nb); -} -StringDecoder.prototype.write = function (buf) { - if (buf.length === 0) return ''; - var r; - var i; - if (this.lastNeed) { - r = this.fillLast(buf); - if (r === undefined) return ''; - i = this.lastNeed; - this.lastNeed = 0; - } else { - i = 0; - } - if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); - return r || ''; + // Enough space to store all bytes of a single character. UTF-8 needs 4 + // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate). + this.charBuffer = new Buffer(6); + // Number of bytes received for the current incomplete multi-byte character. + this.charReceived = 0; + // Number of bytes expected for the current incomplete multi-byte character. + this.charLength = 0; }; -StringDecoder.prototype.end = utf8End; -// Returns only complete characters in a Buffer -StringDecoder.prototype.text = utf8Text; +// write decodes the given buffer and returns it as JS string that is +// guaranteed to not contain any partial multi-byte characters. Any partial +// character found at the end of the buffer is buffered up, and will be +// returned when calling write again with the remaining bytes. +// +// Note: Converting a Buffer containing an orphan surrogate to a String +// currently works, but converting a String to a Buffer (via `new Buffer`, or +// Buffer#write) will replace incomplete surrogates with the unicode +// replacement character. See https://codereview.chromium.org/121173009/ . +StringDecoder.prototype.write = function(buffer) { + var charStr = ''; + // if our last write ended with an incomplete multibyte character + while (this.charLength) { + // determine how many remaining bytes this buffer has to offer for this char + var available = (buffer.length >= this.charLength - this.charReceived) ? + this.charLength - this.charReceived : + buffer.length; + + // add the new bytes to the char buffer + buffer.copy(this.charBuffer, this.charReceived, 0, available); + this.charReceived += available; + + if (this.charReceived < this.charLength) { + // still not enough chars in this buffer? wait for more ... + return ''; + } + + // remove bytes belonging to the current character from the buffer + buffer = buffer.slice(available, buffer.length); + + // get the character that was split + charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding); + + // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character + var charCode = charStr.charCodeAt(charStr.length - 1); + if (charCode >= 0xD800 && charCode <= 0xDBFF) { + this.charLength += this.surrogateSize; + charStr = ''; + continue; + } + this.charReceived = this.charLength = 0; -// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer -StringDecoder.prototype.fillLast = function (buf) { - if (this.lastNeed <= buf.length) { - buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); - return this.lastChar.toString(this.encoding, 0, this.lastTotal); + // if there are no more bytes in this buffer, just emit our char + if (buffer.length === 0) { + return charStr; + } + break; } - buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); - this.lastNeed -= buf.length; -}; -// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a -// continuation byte. -function utf8CheckByte(byte) { - if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; - return -1; -} + // determine and set charLength / charReceived + this.detectIncompleteChar(buffer); -// Checks at most 3 bytes at the end of a Buffer in order to detect an -// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) -// needed to complete the UTF-8 character (if applicable) are returned. -function utf8CheckIncomplete(self, buf, i) { - var j = buf.length - 1; - if (j < i) return 0; - var nb = utf8CheckByte(buf[j]); - if (nb >= 0) { - if (nb > 0) self.lastNeed = nb - 1; - return nb; + var end = buffer.length; + if (this.charLength) { + // buffer the incomplete character bytes we got + buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end); + end -= this.charReceived; } - if (--j < i) return 0; - nb = utf8CheckByte(buf[j]); - if (nb >= 0) { - if (nb > 0) self.lastNeed = nb - 2; - return nb; + + charStr += buffer.toString(this.encoding, 0, end); + + var end = charStr.length - 1; + var charCode = charStr.charCodeAt(end); + // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character + if (charCode >= 0xD800 && charCode <= 0xDBFF) { + var size = this.surrogateSize; + this.charLength += size; + this.charReceived += size; + this.charBuffer.copy(this.charBuffer, size, 0, size); + buffer.copy(this.charBuffer, 0, 0, size); + return charStr.substring(0, end); } - if (--j < i) return 0; - nb = utf8CheckByte(buf[j]); - if (nb >= 0) { - if (nb > 0) { - if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + + // or just emit the charStr + return charStr; +}; + +// detectIncompleteChar determines if there is an incomplete UTF-8 character at +// the end of the given buffer. If so, it sets this.charLength to the byte +// length that character, and sets this.charReceived to the number of bytes +// that are available for this character. +StringDecoder.prototype.detectIncompleteChar = function(buffer) { + // determine how many bytes we have to check at the end of this buffer + var i = (buffer.length >= 3) ? 3 : buffer.length; + + // Figure out if one of the last i bytes of our buffer announces an + // incomplete char. + for (; i > 0; i--) { + var c = buffer[buffer.length - i]; + + // See http://en.wikipedia.org/wiki/UTF-8#Description + + // 110XXXXX + if (i == 1 && c >> 5 == 0x06) { + this.charLength = 2; + break; } - return nb; - } - return 0; -} -// Validates as many continuation bytes for a multi-byte UTF-8 character as -// needed or are available. If we see a non-continuation byte where we expect -// one, we "replace" the validated continuation bytes we've seen so far with -// UTF-8 replacement characters ('\ufffd'), to match v8's UTF-8 decoding -// behavior. The continuation byte check is included three times in the case -// where all of the continuation bytes for a character exist in the same buffer. -// It is also done this way as a slight performance increase instead of using a -// loop. -function utf8CheckExtraBytes(self, buf, p) { - if ((buf[0] & 0xC0) !== 0x80) { - self.lastNeed = 0; - return '\ufffd'.repeat(p); - } - if (self.lastNeed > 1 && buf.length > 1) { - if ((buf[1] & 0xC0) !== 0x80) { - self.lastNeed = 1; - return '\ufffd'.repeat(p + 1); - } - if (self.lastNeed > 2 && buf.length > 2) { - if ((buf[2] & 0xC0) !== 0x80) { - self.lastNeed = 2; - return '\ufffd'.repeat(p + 2); - } + // 1110XXXX + if (i <= 2 && c >> 4 == 0x0E) { + this.charLength = 3; + break; } - } -} -// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. -function utf8FillLast(buf) { - var p = this.lastTotal - this.lastNeed; - var r = utf8CheckExtraBytes(this, buf, p); - if (r !== undefined) return r; - if (this.lastNeed <= buf.length) { - buf.copy(this.lastChar, p, 0, this.lastNeed); - return this.lastChar.toString(this.encoding, 0, this.lastTotal); - } - buf.copy(this.lastChar, p, 0, buf.length); - this.lastNeed -= buf.length; -} - -// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a -// partial character, the character's bytes are buffered until the required -// number of bytes are available. -function utf8Text(buf, i) { - var total = utf8CheckIncomplete(this, buf, i); - if (!this.lastNeed) return buf.toString('utf8', i); - this.lastTotal = total; - var end = buf.length - (total - this.lastNeed); - buf.copy(this.lastChar, 0, end); - return buf.toString('utf8', i, end); -} - -// For UTF-8, a replacement character for each buffered byte of a (partial) -// character needs to be added to the output. -function utf8End(buf) { - var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) return r + '\ufffd'.repeat(this.lastTotal - this.lastNeed); - return r; -} - -// UTF-16LE typically needs two bytes per character, but even if we have an even -// number of bytes available, we need to check if we end on a leading/high -// surrogate. In that case, we need to wait for the next two bytes in order to -// decode the last character properly. -function utf16Text(buf, i) { - if ((buf.length - i) % 2 === 0) { - var r = buf.toString('utf16le', i); - if (r) { - var c = r.charCodeAt(r.length - 1); - if (c >= 0xD800 && c <= 0xDBFF) { - this.lastNeed = 2; - this.lastTotal = 4; - this.lastChar[0] = buf[buf.length - 2]; - this.lastChar[1] = buf[buf.length - 1]; - return r.slice(0, -1); - } + // 11110XXX + if (i <= 3 && c >> 3 == 0x1E) { + this.charLength = 4; + break; } - return r; } - this.lastNeed = 1; - this.lastTotal = 2; - this.lastChar[0] = buf[buf.length - 1]; - return buf.toString('utf16le', i, buf.length - 1); -} + this.charReceived = i; +}; -// For UTF-16LE we do not explicitly append special replacement characters if we -// end on a partial character, we simply let v8 handle that. -function utf16End(buf) { - var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) { - var end = this.lastTotal - this.lastNeed; - return r + this.lastChar.toString('utf16le', 0, end); - } - return r; -} +StringDecoder.prototype.end = function(buffer) { + var res = ''; + if (buffer && buffer.length) + res = this.write(buffer); -function base64Text(buf, i) { - var n = (buf.length - i) % 3; - if (n === 0) return buf.toString('base64', i); - this.lastNeed = 3 - n; - this.lastTotal = 3; - if (n === 1) { - this.lastChar[0] = buf[buf.length - 1]; - } else { - this.lastChar[0] = buf[buf.length - 2]; - this.lastChar[1] = buf[buf.length - 1]; + if (this.charReceived) { + var cr = this.charReceived; + var buf = this.charBuffer; + var enc = this.encoding; + res += buf.slice(0, cr).toString(enc); } - return buf.toString('base64', i, buf.length - n); -} -function base64End(buf) { - var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); - return r; + return res; +}; + +function passThroughWrite(buffer) { + return buffer.toString(this.encoding); } -// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) -function simpleWrite(buf) { - return buf.toString(this.encoding); +function utf16DetectIncompleteChar(buffer) { + this.charReceived = buffer.length % 2; + this.charLength = this.charReceived ? 2 : 0; } -function simpleEnd(buf) { - return buf && buf.length ? this.write(buf) : ''; +function base64DetectIncompleteChar(buffer) { + this.charReceived = buffer.length % 3; + this.charLength = this.charReceived ? 3 : 0; } -},{"buffer":44,"buffer-shims":43}],97:[function(require,module,exports){ + +},{"buffer":44}],96:[function(require,module,exports){ (function (global){ /** @@ -15668,16 +15603,16 @@ function config (name) { } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],98:[function(require,module,exports){ +},{}],97:[function(require,module,exports){ arguments[4][66][0].apply(exports,arguments) -},{"dup":66}],99:[function(require,module,exports){ +},{"dup":66}],98:[function(require,module,exports){ module.exports = function isBuffer(arg) { return arg && typeof arg === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function'; } -},{}],100:[function(require,module,exports){ +},{}],99:[function(require,module,exports){ (function (process,global){ // Copyright Joyent, Inc. and other Node contributors. // @@ -16267,4 +16202,4 @@ function hasOwnProperty(obj, prop) { } }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./support/isBuffer":99,"_process":82,"inherits":98}]},{},[1]); +},{"./support/isBuffer":98,"_process":82,"inherits":97}]},{},[1]); diff --git a/package.json b/package.json index a02ab9e803..185eced0f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mocha", - "version": "3.4.1", + "version": "3.4.2", "description": "simple, flexible, fun test framework", "keywords": [ "mocha",