You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

405 lines
10 KiB

/*
* Module identifier resolution
*/
/*
* Global identifiers start with a term other than '.' or '..':
*
* require('foo') // absolute identifier
* require('foo/bar') // absolute identifier
* require('foo/./bar') // absolute identifier, resolves to 'foo/bar'
*
* Relative identifers are resolved relative to the current module, i.e.
* the module whose require() function is called. For instance, when
* the current module is 'foo/bar':
*
* require('baz') // absolute identifier, 'baz'
* require('./baz') // relative identifier, resolves to 'foo/baz'
* require('../baz') // relative identifier, resolves to 'baz'
* require('../../baz') // relative identifier, resolution fails
*
* For this to work, a separate require() function is given to every
* module, with require.id tracking the resolved identifier of the
* current module.
*
* The internal implementation for relative paths is simply to concatenate
* the module's (resolved) absolute identifier with the requested relative
* path, separated by a slash. After this, resolution proceeds like global
* require() resolution.
*/
/*
* NOTE: be careful with module caching; if module modSearch() is successful,
* Duktape won't modSearch() the module twice. Different names need to be used
* in different tests to avoid this. Alternatively, Duktape.modLoaded could
* be emptied.
*/
/*===
basic resolution
Duktape.modSearch foo/mod1
global require: foo/mod1 -> foo/mod1
global require: foo//mod1 -> foo/mod1
global require: foo/./mod1 -> foo/mod1
global require: foo//.//mod1 -> foo/mod1
global require: ./foo/./mod1 -> foo/mod1
global require: ./foo/././/.///./////////////mod1 -> foo/mod1
global require: ./foo/../foo/mod1 -> foo/mod1
Duktape.modSearch bar/a
global require: bar/a -> bar/a
global require: bar/a/ -> TypeError
Duktape.modSearch bar/a/b
global require: bar/a/b -> bar/a/b
global require: ../bar -> TypeError
global require: foo/../../bar -> TypeError
global require: .. -> TypeError
global require: /foo/mod1 -> TypeError
global require: foo/mod1/ -> TypeError
global require: foo/.bar -> TypeError
global require: foo/.../bar -> TypeError
global require: foo/mod1/. -> TypeError
global require: foo/mod1/.. -> TypeError
Duktape.modSearch baz
Duktape.modSearch xxx
Duktape.modSearch xxy
Duktape.modSearch xxx/yyy
Duktape.modSearch quux/foo
Duktape.modSearch xxz
Duktape.modSearch quux/xxw
Duktape.modSearch quux/xxw/yyy
Duktape.modSearch zzz
Duktape.modSearch www
===*/
function basicResolutionTest() {
function globalTest(id) {
var mod;
try {
mod = require(id);
print('global require: ' + id + ' -> ' + mod.name);
} catch (e) {
print('global require: ' + id + ' -> ' + e.name);
}
}
var moduleSources = {
"foo/mod1": "exports.name='foo/mod1';",
"foo/mod2": "exports.name='foo/mod2';",
"bar/mod1": "exports.name='bar/mod1';",
"bar/a": "exports.name='bar/a';",
"bar/a/b": "exports.name='bar/a/b';",
"quux": "exports.name='quux';",
};
Duktape.modSearch = function (id) {
var ret;
// The identifier given to modSearch() is a resolved absolute identifier
print('Duktape.modSearch', id);
ret = moduleSources[id];
if (ret) { return ret; }
throw new Error('cannot find module: ' + id);
}
/*
* Global require() tests - because of the internal implementation
* this also covers most of the relative module resolution cases.
*/
// all of these resolve to 'foo/mod1'
globalTest('foo/mod1');
globalTest('foo//mod1');
globalTest('foo/./mod1');
globalTest('foo//.//mod1');
globalTest('./foo/./mod1');
globalTest('./foo/././/.///./////////////mod1');
globalTest('./foo/../foo/mod1');
// 'a' is both a module name and a "directory"
globalTest('bar/a');
globalTest('bar/a/'); // error: trailing slash
globalTest('bar/a/b');
// error when '..' cannot backtrack terms
globalTest('../bar');
globalTest('foo/../../bar');
globalTest('..');
// error when id begins with a slash (empty initial term)
globalTest('/foo/mod1');
// error when id ends with a slash (empty final term)
globalTest('foo/mod1/');
// error when a term begins with a period (this is Duktape specific
// but CommonJS modules is even more strict)
globalTest('foo/.bar');
globalTest('foo/.../bar');
// error when an ID ends with a '.' or '..' term
globalTest('foo/mod1/.');
globalTest('foo/mod1/..');
/*
* Require from inside a module, both relative and absolute paths.
*/
Duktape.modSearch = function (id) {
print('Duktape.modSearch', id);
if (id === 'baz') {
return 'require("xxx");\n' + // absolute
'require("./xxy");\n' + // relative
'require("./xxx/yyy");\n' // relative
;
}
return ''; // return a fake empty module
};
void require('baz');
/*
* Require from inside a module with a few more path components.
*/
Duktape.modSearch = function (id) {
print('Duktape.modSearch', id);
if (id === 'quux/foo') {
return 'require("xxz");\n' + // absolute
'require("./xxw");\n' + // relative
'require("./xxw/yyy");\n' + // relative
'require("../zzz");\n' + // relative
'require("././../www");\n'
;
}
return ''; // return a fake empty module
};
void require('quux/foo');
}
print('basic resolution');
try {
basicResolutionTest();
} catch (e) {
print(e);
}
/*===
non-ascii
Duktape.modSearch: "foo"
Duktape.modSearch: "foo\u1234"
Duktape.modSearch: "foo\u1234\x01/\udead\ubeef"
Duktape.modSearch: "foo\u1234\x01/\u1234"
Duktape.modSearch: "foo\u1234\x01/\u1234"
===*/
/* Non-ASCII characters are allowed in terms; Duktape places no requirements
* now except that terms must be non-empty, cannot begin with a period, and
* cannot contain slashes.
*
* U+0000 is treated as an end-of-string in the current implementation. This
* is not desirable but also not worth fixing.
*/
function nonAsciiTest() {
Duktape.modSearch = function (id) {
print('Duktape.modSearch:', Duktape.enc('jx', id));
throw Error('module not found');
};
function test(id) {
try {
void require(id);
print('never here');
} catch (e) {
;
}
}
// a few basics
test('foo');
test('foo\u1234');
test('foo\u1234\u0001/\udead\ubeef');
// check that '..' works over non-ascii
test('foo\u1234\u0001/\udead\ubeef/../\u1234');
// document the fact that U+0000 terminates resolution
test('foo\u1234\u0001/\udead\ubeef/../\u1234\u0000neverseen');
}
print('non-ascii');
try {
nonAsciiTest();
} catch (e) {
print(e);
}
/*===
length
Duktape.modSearch foo/bar
230: foo/bar
231: foo/bar
232: foo/bar
233: foo/bar
234: foo/bar
235: foo/bar
236: foo/bar
237: foo/bar
238: foo/bar
239: foo/bar
240: foo/bar
241: foo/bar
242: foo/bar
243: foo/bar
244: foo/bar
245: foo/bar
246: foo/bar
247: foo/bar
248: foo/bar
249: foo/bar
250: foo/bar
251: foo/bar
252: foo/bar
253: foo/bar
254: foo/bar
255: TypeError
256: TypeError
257: TypeError
258: TypeError
259: TypeError
260: TypeError
261: TypeError
262: TypeError
263: TypeError
264: TypeError
265: TypeError
266: TypeError
267: TypeError
268: TypeError
269: TypeError
270: TypeError
230: bar
231: bar
232: bar
233: bar
234: bar
235: TypeError
236: TypeError
237: TypeError
238: TypeError
239: TypeError
240: TypeError
241: TypeError
242: TypeError
243: TypeError
244: TypeError
245: TypeError
246: TypeError
247: TypeError
248: TypeError
249: TypeError
250: TypeError
251: TypeError
252: TypeError
253: TypeError
254: TypeError
255: TypeError
256: TypeError
257: TypeError
258: TypeError
259: TypeError
260: TypeError
261: TypeError
262: TypeError
263: TypeError
264: TypeError
265: TypeError
266: TypeError
267: TypeError
268: TypeError
269: TypeError
270: TypeError
===*/
/* Test the current implementation limit for ID lengths. This also
* does some boundary value testing for ID length.
*/
function lengthTest() {
var i;
var mod;
function buildFooBarId(n) {
var tmp = '';
while (tmp.length < n - 6) {
tmp += '/';
}
return 'foo' + tmp + 'bar';
}
function buildNumberedId(n, num) {
var tmp = 'foo/num-' + num + '-';
while (tmp.length < n) {
tmp += 'x';
}
return tmp; // foo/num-123-xxxxxx... to 'n' chars
}
/*
* Test the limit for the current global require() id
*/
Duktape.modSearch = function (id) {
print('Duktape.modSearch', id);
return 'exports.name="' + id + '"';
}
for (i = 230; i <= 270; i++) {
try {
mod = require(buildFooBarId(i));
print(i + ':', mod.name);
} catch (e) {
print(i + ':', e.name);
}
}
/*
* Test the limit for a relative require() id from inside a
* module; length restriction is applied against an intermediate
* identifier constructed by joining the module's require.id
* with the requested relative ID with a slash.
*/
Duktape.modSearch = function (id) {
// Disable to avoid spam; each id is dynamic and long
//print('Duktape.modSearch', id);
if (id == 'bar') {
return 'exports.name = "' + id + '";';
} else {
/* submodule will request '../bar' relative to its own path */
return 'var mod = require(".././././././bar");\n' +
'exports.name = "' + id + '";\n' +
'exports.mod_name = mod.name;\n';
}
}
for (i = 230; i <= 270; i++) {
try {
mod = require(buildNumberedId(i, i));
print(i + ':', mod.mod_name);
} catch (e) {
print(i + ':', e.name);
}
}
}
print('length');
try {
lengthTest();
} catch (e) {
print(e);
}