Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] fs: feature detection for recursive mkdir[Sync] #22302

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 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
25 changes: 25 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,14 @@ fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
});
```

The [`util.features`][] symbol can be used to feature detect if
recursion is available.

```js
fs.mkdir[util.features].recursive;
// true
```

See also: mkdir(2).

## fs.mkdirSync(path[, options])
Expand All @@ -2106,6 +2114,14 @@ changes:
Synchronously creates a directory. Returns `undefined`.
This is the synchronous version of [`fs.mkdir()`][].

The [`util.features`][] symbol can be used to feature detect if
recursion is available.

```js
fs.mkdirSync[util.features].recursive;
// true
```

See also: mkdir(2).

## fs.mkdtemp(prefix[, options], callback)
Expand Down Expand Up @@ -4012,6 +4028,14 @@ The optional `options` argument can be an integer specifying mode (permission
and sticky bits), or an object with a `mode` property and a `recursive`
property indicating whether parent folders should be created.

The [`util.features`][] symbol can be used to feature detect if
recursion is available.

```js
fs.mkdir[util.features].recursive;
// true
```

### fsPromises.mkdtemp(prefix[, options])
<!-- YAML
added: v10.0.0
Expand Down Expand Up @@ -4666,6 +4690,7 @@ the file contents.
[`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
[`net.Socket`]: net.html#net_class_net_socket
[`stat()`]: fs.html#fs_fs_stat_path_options_callback
[`util.features`]: util.html#util_util_features
[`util.promisify()`]: util.html#util_util_promisify_original
[Caveats]: #fs_caveats
[Common System Errors]: errors.html#errors_common_system_errors
Expand Down
7 changes: 7 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ The `--throw-deprecation` command line flag and `process.throwDeprecation`
property take precedence over `--trace-deprecation` and
`process.traceDeprecation`.

## util.features
<!-- YAML
added: REPLACEME
-->

* {symbol} that can be used to do feature detection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍😻😍

## util.format(format[, ...args])
<!-- YAML
added: v0.5.3
Expand Down
10 changes: 9 additions & 1 deletion lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const {

const { FSReqCallback, statValues } = binding;
const internalFS = require('internal/fs/utils');
const { getPathFromURL } = require('internal/url');
const { addFeatureDetection, getPathFromURL } = require('internal/url');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addFeatureDetection is part of internal/util not internal/url.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bah... I BROKE IT

const internalUtil = require('internal/util');
const {
copyObject,
Expand Down Expand Up @@ -745,6 +745,10 @@ function mkdir(path, options, callback) {
validateMode(mode, 'mode', 0o777), recursive, req);
}

addFeatureDetection(mkdir, {
recursive: true
});

function mkdirSync(path, options) {
if (typeof options === 'number' || typeof options === 'string') {
options = { mode: options };
Expand All @@ -766,6 +770,10 @@ function mkdirSync(path, options) {
handleErrorFromBinding(ctx);
}

addFeatureDetection(mkdirSync, {
recursive: true
});

function readdir(path, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options, {});
Expand Down
6 changes: 5 additions & 1 deletion lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const {
ERR_INVALID_ARG_VALUE,
ERR_METHOD_NOT_IMPLEMENTED
} = require('internal/errors').codes;
const { getPathFromURL } = require('internal/url');
const { addFeatureDetection, getPathFromURL } = require('internal/url');
const { isUint8Array } = require('internal/util/types');
const {
copyObject,
Expand Down Expand Up @@ -308,6 +308,10 @@ async function mkdir(path, options) {
kUsePromises);
}

addFeatureDetection(mkdir, {
recursive: true
});

async function readdir(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
Expand Down
15 changes: 15 additions & 0 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,20 @@ function isInsideNodeModules() {
return false;
}

const kCustomFeatureDetectionSymbol = Symbol('node.util.features');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a Symbol.for('node.util.features'). It makes things a lot simpler for people maintaining isomorphic modules.


function addFeatureDetection(obj, value) {
Object.defineProperty(obj, kCustomFeatureDetectionSymbol, {
value,
writable: false,
enumerable: false,
configurable: true
});
}


module.exports = {
addFeatureDetection,
assertCrypto,
cachedResult,
convertToValidSignal,
Expand All @@ -394,6 +406,9 @@ module.exports = {
// alternative to using 'inspect'
customInspectSymbol: Symbol('util.inspect.custom'),

// Symbol used to do feature detection
featureDetectionSymbol: kCustomFeatureDetectionSymbol,

// Used by the buffer module to capture an internal reference to the
// default isEncoding implementation, just in case userland overrides it.
kIsEncodingSymbol: Symbol('kIsEncodingSymbol'),
Expand Down
2 changes: 2 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const {
customInspectSymbol,
deprecate,
getSystemErrorName: internalErrorName,
featureDetectionSymbol,
isError,
promisify,
join,
Expand Down Expand Up @@ -1472,6 +1473,7 @@ module.exports = exports = {
callbackify,
debuglog,
deprecate,
features: featureDetectionSymbol,
format,
formatWithOptions,
getSystemErrorName,
Expand Down
9 changes: 9 additions & 0 deletions test/parallel/test-fs-mkdir.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { features } = require('util');

const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
Expand Down Expand Up @@ -172,6 +173,14 @@ if (common.isMainThread && (common.isLinux || common.isOSX)) {
});
}

// mkdirp and mkdirSyncp feature detection
{
assert.strictEqual(fs.mkdir[features].recursive, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add a test for the undefined case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which undefined case? where features is undefined?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think it would be good to demonstrate that this will work fine for feature detection when util.features is undefined.

!!mkdir[require('util').propretyThatDoesntExist] === false

assert.strictEqual(fs.mkdir[features].fhwdgads, undefined);
assert.strictEqual(fs.mkdirSync[features].recursive, true);
assert.strictEqual(fs.mkdirSync[features].fhwdgads, undefined);
}

// Keep the event loop alive so the async mkdir() requests
// have a chance to run (since they don't ref the event loop).
process.nextTick(() => {});
7 changes: 7 additions & 0 deletions test/parallel/test-fs-promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const path = require('path');
const fs = require('fs');
const { features } = require('util');
const fsPromises = fs.promises;
const {
access,
Expand Down Expand Up @@ -250,6 +251,12 @@ function verifyStatObject(stat) {
assert(stats.isDirectory());
}

// mkdirp and mkdirSyncp feature detection
{
assert.strictEqual(mkdir[features].recursive, true);
assert.strictEqual(mkdir[features].fhwdgads, undefined);
}

await mkdtemp(path.resolve(tmpDir, 'FOO'));
assert.rejects(
// mkdtemp() expects to get a string prefix.
Expand Down
20 changes: 20 additions & 0 deletions test/parallel/test-internal-util-add-feature-detection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Flags: --expose-internals
'use strict';

require('../common');
const assert = require('assert');
const { addFeatureDetection } = require('internal/util');
const { features } = require('util');

function myFunction() {
return true;
}

addFeatureDetection(myFunction, {
works: true,
});

assert.strictEqual(typeof myFunction, 'function');
assert.strictEqual(myFunction(), true);
assert.strictEqual(myFunction[features].works, true);
assert.strictEqual(myFunction[features].doesNotWork, undefined);