mirror of https://github.com/svaarala/duktape.git
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
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);
|
|
}
|
|
|