Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,10 @@ explicitly via error event handlers set on the domain instead.
<a id="DEP0013"></a>
### DEP0013: fs asynchronous function without callback

Type: Runtime
Type: End-of-Life

Calling an asynchronous function without a callback is deprecated.
Calling an asynchronous function without a callback throws a `TypeError`
REPLACEME onwards. Refer: [PR 12562](https://github.com/nodejs/node/pull/12562)

<a id="DEP0014"></a>
### DEP0014: fs.read legacy String interface
Expand Down
180 changes: 150 additions & 30 deletions doc/api/fs.md

Large diffs are not rendered by default.

58 changes: 7 additions & 51 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ const { kMaxLength } = require('buffer');

const isWindows = process.platform === 'win32';

const DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG);
const errnoException = errors.errnoException;

let truncateWarn = true;
Expand Down Expand Up @@ -106,46 +105,17 @@ function handleErrorFromBinding(ctx) {
}
}

// TODO(joyeecheung): explore how the deprecation could be solved via linting
// rules. See https://github.com/nodejs/node/pull/12976
function rethrow() {
process.emitWarning(
'Calling an asynchronous function without callback is deprecated.',
'DeprecationWarning', 'DEP0013', rethrow
);

// Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and
// is fairly slow to generate.
if (DEBUG) {
var backtrace = new Error();
return function(err) {
if (err) {
backtrace.stack = `${err.name}: ${err.message}` +
backtrace.stack.substr(backtrace.name.length);
throw backtrace;
}
};
}

return function(err) {
if (err) {
throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs
}
};
}

function maybeCallback(cb) {
return typeof cb === 'function' ? cb : rethrow();
if (typeof cb === 'function')
return cb;

throw new errors.TypeError('ERR_INVALID_CALLBACK');
}

// Ensure that callbacks run in the global context. Only use this function
// for callbacks that are passed to the binding layer, callbacks that are
// invoked from JS already run in the proper scope.
function makeCallback(cb) {
if (cb === undefined) {
return rethrow();
}

if (typeof cb !== 'function') {
throw new errors.TypeError('ERR_INVALID_CALLBACK');
}
Expand All @@ -159,10 +129,6 @@ function makeCallback(cb) {
// an optimization, since the data passed back to the callback needs to be
// transformed anyway.
function makeStatsCallback(cb) {
if (cb === undefined) {
return rethrow();
}

if (typeof cb !== 'function') {
throw new errors.TypeError('ERR_INVALID_CALLBACK');
}
Expand Down Expand Up @@ -191,8 +157,6 @@ fs.access = function(path, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = fs.F_OK;
} else if (typeof callback !== 'function') {
throw new errors.TypeError('ERR_INVALID_CALLBACK');
}

path = getPathFromURL(path);
Expand All @@ -218,16 +182,8 @@ fs.accessSync = function(path, mode) {
handleErrorFromBinding(ctx);
};

// fs.exists never throws even when the arguments are invalid - if there is
// a callback it would invoke it with false, otherwise it emits a warning
// (see the comments of rethrow()).
// This is to bring it inline with fs.existsSync, which never throws.
// TODO(joyeecheung): deprecate the never-throw-on-invalid-arguments behavior
fs.exists = function(path, callback) {
if (typeof callback !== 'function') {
rethrow();
return;
}
maybeCallback(callback);

function suppressedCallback(err) {
callback(err ? false : true);
Expand Down Expand Up @@ -781,7 +737,7 @@ fs.ftruncateSync = function(fd, len = 0) {
};

fs.rmdir = function(path, callback) {
callback = maybeCallback(callback);
callback = makeCallback(callback);
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
Expand Down Expand Up @@ -1828,7 +1784,7 @@ fs.realpath = function realpath(p, options, callback) {


fs.realpath.native = function(path, options, callback) {
callback = maybeCallback(callback || options);
callback = makeCallback(callback || options);
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/test-fs-readfile-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

require('fs').readFile('/'); // throws EISDIR
require('fs').readFileSync('/'); // throws EISDIR
3 changes: 2 additions & 1 deletion test/parallel/test-fs-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ fs.access(readOnlyFile, fs.F_OK | fs.R_OK, common.mustCall((err) => {
assert.ifError(err);
}));

fs.access(readOnlyFile, fs.W_OK, common.mustCall((err) => {
fs.access(readOnlyFile, fs.W_OK, common.mustCall(function(err) {
assert.strictEqual(this, undefined);
if (hasWriteAccessForReadonlyFile) {
assert.ifError(err);
} else {
Expand Down
9 changes: 3 additions & 6 deletions test/parallel/test-fs-append-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,9 @@ fs.open(filename5, 'a+', function(e, fd) {
});
});

// test that a missing callback emits a warning, even if the last argument is a
// function.
const filename6 = join(tmpdir.path, 'append6.txt');
const warn = 'Calling an asynchronous function without callback is deprecated.';
common.expectWarning('DeprecationWarning', warn);
fs.appendFile(filename6, console.log);
assert.throws(
() => fs.appendFile(join(tmpdir.path, 'append6.txt'), console.log),
{ code: 'ERR_INVALID_CALLBACK' });

process.on('exit', function() {
assert.strictEqual(12, ncallbacks);
Expand Down
7 changes: 3 additions & 4 deletions test/parallel/test-fs-exists.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ const fs = require('fs');
const { URL } = require('url');
const f = __filename;

// Only warnings are emitted when the callback is invalid
fs.exists(f);
fs.exists();
fs.exists(f, {});
assert.throws(() => fs.exists(f), { code: 'ERR_INVALID_CALLBACK' });
assert.throws(() => fs.exists(), { code: 'ERR_INVALID_CALLBACK' });
assert.throws(() => fs.exists(f, {}), { code: 'ERR_INVALID_CALLBACK' });

fs.exists(f, common.mustCall(function(y) {
assert.strictEqual(y, true);
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-fs-fchmod.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const fs = require('fs');

// Check for mode values range
const modeUpperBoundaryValue = 0o777;
fs.fchmod(1, modeUpperBoundaryValue);
fs.fchmod(1, modeUpperBoundaryValue, common.mustCall());
fs.fchmodSync(1, modeUpperBoundaryValue);

// umask of 0o777 is equal to 775
Expand Down
6 changes: 0 additions & 6 deletions test/parallel/test-fs-make-callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const fs = require('fs');
const callbackThrowValues = [null, true, false, 0, 1, 'foo', /foo/, [], {}];

const { sep } = require('path');
const warn = 'Calling an asynchronous function without callback is deprecated.';

const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
Expand All @@ -16,11 +15,6 @@ function testMakeCallback(cb) {
};
}

common.expectWarning('DeprecationWarning', warn);

// Passing undefined/nothing calls rethrow() internally, which emits a warning
testMakeCallback()();

function invalidCallbackThrowsTests() {
callbackThrowValues.forEach((value) => {
common.expectsError(testMakeCallback(value), {
Expand Down
6 changes: 0 additions & 6 deletions test/parallel/test-fs-makeStatsCallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
const common = require('../common');
const fs = require('fs');
const callbackThrowValues = [null, true, false, 0, 1, 'foo', /foo/, [], {}];
const warn = 'Calling an asynchronous function without callback is deprecated.';

function testMakeStatsCallback(cb) {
return function() {
Expand All @@ -11,14 +10,9 @@ function testMakeStatsCallback(cb) {
};
}

common.expectWarning('DeprecationWarning', warn);

// Verify the case where a callback function is provided
testMakeStatsCallback(common.mustCall())();

// Passing undefined/nothing calls rethrow() internally, which emits a warning
testMakeStatsCallback()();

function invalidCallbackThrowsTests() {
callbackThrowValues.forEach((value) => {
common.expectsError(testMakeStatsCallback(value), {
Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-fs-mkdir-rmdir.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fs.mkdir(d, 0o666, common.mustCall(function(err) {
assert.ifError(err);

fs.mkdir(d, 0o666, common.mustCall(function(err) {
assert.strictEqual(this, undefined);
assert.ok(err, 'got no error');
assert.ok(/^EEXIST/.test(err.message), 'got no EEXIST message');
assert.strictEqual(err.code, 'EEXIST', 'got no EEXIST code');
Expand Down
5 changes: 0 additions & 5 deletions test/parallel/test-fs-mkdtemp.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,3 @@ fs.mkdtemp(path.join(tmpdir.path, 'bar.'), common.mustCall(handler));
// Same test as above, but making sure that passing an options object doesn't
// affect the way the callback function is handled.
fs.mkdtemp(path.join(tmpdir.path, 'bar.'), {}, common.mustCall(handler));

// Making sure that not passing a callback doesn't crash, as a default function
// is passed internally.
fs.mkdtemp(path.join(tmpdir.path, 'bar-'));
fs.mkdtemp(path.join(tmpdir.path, 'bar-'), {});
4 changes: 2 additions & 2 deletions test/parallel/test-fs-readfile-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function test(env, cb) {

test({ NODE_DEBUG: '' }, common.mustCall((data) => {
assert(/EISDIR/.test(data));
assert(!/test-fs-readfile-error/.test(data));
assert(/test-fs-readfile-error/.test(data));
}));

test({ NODE_DEBUG: 'fs' }, common.mustCall((data) => {
Expand All @@ -57,7 +57,7 @@ test({ NODE_DEBUG: 'fs' }, common.mustCall((data) => {
}));

common.expectsError(
() => { fs.readFile(() => {}); },
() => { fs.readFile(() => {}, common.mustNotCall()); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "path" argument must be one of type string, Buffer, or URL',
Expand Down
3 changes: 2 additions & 1 deletion test/parallel/test-fs-realpath-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const fs = require('fs');
if (!common.isOSX) common.skip('MacOS-only test.');

assert.strictEqual(fs.realpathSync.native('/users'), '/Users');
fs.realpath.native('/users', common.mustCall((err, res) => {
fs.realpath.native('/users', common.mustCall(function(err, res) {
assert.ifError(err);
assert.strictEqual(res, '/Users');
assert.strictEqual(this, undefined);
}));
6 changes: 3 additions & 3 deletions test/parallel/test-fs-write-no-fd.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';
require('../common');
const common = require('../common');
const fs = require('fs');
const assert = require('assert');

assert.throws(function() {
fs.write(null, Buffer.allocUnsafe(1), 0, 1);
fs.write(null, Buffer.allocUnsafe(1), 0, 1, common.mustNotCall());
}, /TypeError/);

assert.throws(function() {
fs.write(null, '1', 0, 1);
fs.write(null, '1', 0, 1, common.mustNotCall());
}, /TypeError/);