From 0dc6f0387395f3760f50699dbc1f8d311b8cd1fc Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 1 Oct 2018 22:12:47 -0700 Subject: [PATCH] module: support multi-dot file extension Support multi-dot file extensions like '.coffee.md' in Module.load. PR-URL: https://github.com/nodejs/node/pull/23416 Reviewed-By: Anna Henningsen Reviewed-By: John-David Dalton --- lib/internal/modules/cjs/loader.js | 19 +++- .../test-module-deleted-extensions.js | 18 ---- test/parallel/test-module-multi-extensions.js | 94 +++++++++++++++++++ 3 files changed, 111 insertions(+), 20 deletions(-) delete mode 100644 test/known_issues/test-module-deleted-extensions.js create mode 100644 test/parallel/test-module-multi-extensions.js diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 4c3c05bcfb25c3..305a11b322012d 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -217,6 +217,22 @@ function tryExtensions(p, exts, isMain) { return false; } +// find the longest (possibly multi-dot) extension registered in +// Module._extensions +function findLongestRegisteredExtension(filename) { + const name = path.basename(filename); + let currentExtension; + let index; + let startIndex = 0; + while ((index = name.indexOf('.', startIndex)) !== -1) { + startIndex = index + 1; + if (index === 0) continue; // Skip dotfiles like .gitignore + currentExtension = name.slice(index); + if (Module._extensions[currentExtension]) return currentExtension; + } + return '.js'; +} + var warned = false; Module._findPath = function(request, paths, isMain) { if (path.isAbsolute(request)) { @@ -595,8 +611,7 @@ Module.prototype.load = function(filename) { this.filename = filename; this.paths = Module._nodeModulePaths(path.dirname(filename)); - var extension = path.extname(filename) || '.js'; - if (!Module._extensions[extension]) extension = '.js'; + var extension = findLongestRegisteredExtension(filename); Module._extensions[extension](this, filename); this.loaded = true; diff --git a/test/known_issues/test-module-deleted-extensions.js b/test/known_issues/test-module-deleted-extensions.js deleted file mode 100644 index 3a51e8725eec60..00000000000000 --- a/test/known_issues/test-module-deleted-extensions.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; -// Refs: https://github.com/nodejs/node/issues/4778 -const common = require('../common'); -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const tmpdir = require('../common/tmpdir'); -const file = path.join(tmpdir.path, 'test-extensions.foo.bar'); - -tmpdir.refresh(); -fs.writeFileSync(file, '', 'utf8'); -require.extensions['.foo.bar'] = (module, path) => {}; -delete require.extensions['.foo.bar']; -require.extensions['.bar'] = common.mustCall((module, path) => { - assert.strictEqual(module.id, file); - assert.strictEqual(path, file); -}); -require(path.join(tmpdir.path, 'test-extensions')); diff --git a/test/parallel/test-module-multi-extensions.js b/test/parallel/test-module-multi-extensions.js new file mode 100644 index 00000000000000..be17bc5d025508 --- /dev/null +++ b/test/parallel/test-module-multi-extensions.js @@ -0,0 +1,94 @@ +'use strict'; + +// Refs: https://github.com/nodejs/node/issues/4778 + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const Module = require('module'); +const tmpdir = require('../common/tmpdir'); +const file = path.join(tmpdir.path, 'test-extensions.foo.bar'); +const dotfile = path.join(tmpdir.path, '.bar'); +const dotfileWithExtension = path.join(tmpdir.path, '.foo.bar'); + +tmpdir.refresh(); +fs.writeFileSync(file, 'console.log(__filename);', 'utf8'); +fs.writeFileSync(dotfile, 'console.log(__filename);', 'utf8'); +fs.writeFileSync(dotfileWithExtension, 'console.log(__filename);', 'utf8'); + +{ + require.extensions['.bar'] = common.mustNotCall(); + require.extensions['.foo.bar'] = common.mustCall(); + const modulePath = path.join(tmpdir.path, 'test-extensions'); + require(modulePath); + require(file); + delete require.cache[file]; + delete require.extensions['.bar']; + delete require.extensions['.foo.bar']; + Module._pathCache = Object.create(null); +} + +{ + require.extensions['.foo.bar'] = common.mustCall(); + const modulePath = path.join(tmpdir.path, 'test-extensions'); + require(modulePath); + assert.throws( + () => require(`${modulePath}.foo`), + new Error(`Cannot find module '${modulePath}.foo'`) + ); + require(`${modulePath}.foo.bar`); + delete require.cache[file]; + delete require.extensions['.foo.bar']; + Module._pathCache = Object.create(null); +} + +{ + const modulePath = path.join(tmpdir.path, 'test-extensions'); + assert.throws( + () => require(modulePath), + new Error(`Cannot find module '${modulePath}'`) + ); + delete require.cache[file]; + Module._pathCache = Object.create(null); +} + +{ + require.extensions['.bar'] = common.mustNotCall(); + require.extensions['.foo.bar'] = common.mustCall(); + const modulePath = path.join(tmpdir.path, 'test-extensions.foo'); + require(modulePath); + delete require.cache[file]; + delete require.extensions['.bar']; + delete require.extensions['.foo.bar']; + Module._pathCache = Object.create(null); +} + +{ + require.extensions['.foo.bar'] = common.mustNotCall(); + const modulePath = path.join(tmpdir.path, 'test-extensions.foo'); + assert.throws( + () => require(modulePath), + new Error(`Cannot find module '${modulePath}'`) + ); + delete require.extensions['.foo.bar']; + Module._pathCache = Object.create(null); +} + +{ + require.extensions['.bar'] = common.mustNotCall(); + require(dotfile); + delete require.cache[dotfile]; + delete require.extensions['.bar']; + Module._pathCache = Object.create(null); +} + +{ + require.extensions['.bar'] = common.mustCall(); + require.extensions['.foo.bar'] = common.mustNotCall(); + require(dotfileWithExtension); + delete require.cache[dotfileWithExtension]; + delete require.extensions['.bar']; + delete require.extensions['.foo.bar']; + Module._pathCache = Object.create(null); +}