mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
11 years ago
1 changed files with 383 additions and 0 deletions
@ -0,0 +1,383 @@ |
|||||
|
/* |
||||
|
* 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 find() is successful, |
||||
|
* Duktape won't find() the module twice. Different names need to be used |
||||
|
* in different tests to avoid this. Alternatively, Duktape.loaded could |
||||
|
* be emptied. |
||||
|
*/ |
||||
|
|
||||
|
/*=== |
||||
|
basic resolution |
||||
|
Duktape.find 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.find bar/a |
||||
|
global require: bar/a -> bar/a |
||||
|
global require: bar/a/ -> TypeError |
||||
|
Duktape.find 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.find baz |
||||
|
Duktape.find xxx |
||||
|
Duktape.find baz/xxx |
||||
|
Duktape.find baz/xxx/yyy |
||||
|
Duktape.find zzz |
||||
|
Duktape.find 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.find = function (id) { |
||||
|
var ret; |
||||
|
|
||||
|
// The identifier given to find() is a resolved absolute identifier
|
||||
|
print('Duktape.find', 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.find = function (id) { |
||||
|
print('Duktape.find', id); |
||||
|
if (id === 'baz') { |
||||
|
return 'require("xxx");\n' + // absolute
|
||||
|
'require("./xxx");\n' + // relative
|
||||
|
'require("./xxx/yyy");\n' + // relative
|
||||
|
'require("../zzz");\n' + // relative
|
||||
|
'require("././../www");\n' |
||||
|
; |
||||
|
} |
||||
|
return ''; // return a fake empty module
|
||||
|
}; |
||||
|
|
||||
|
void require('baz'); |
||||
|
} |
||||
|
|
||||
|
print('basic resolution'); |
||||
|
|
||||
|
try { |
||||
|
basicResolutionTest(); |
||||
|
} catch (e) { |
||||
|
print(e); |
||||
|
} |
||||
|
|
||||
|
/*=== |
||||
|
non-ascii |
||||
|
Duktape.find: "foo" |
||||
|
Duktape.find: "foo\u1234" |
||||
|
Duktape.find: "foo\u1234\x01/\udead\ubeef" |
||||
|
Duktape.find: "foo\u1234\x01/\u1234" |
||||
|
Duktape.find: "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.find = function (id) { |
||||
|
print('Duktape.find:', Duktape.enc('jx', id)); |
||||
|
}; |
||||
|
|
||||
|
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.find 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: foo/bar |
||||
|
231: foo/bar |
||||
|
232: foo/bar |
||||
|
233: foo/bar |
||||
|
234: foo/bar |
||||
|
235: foo/bar |
||||
|
236: foo/bar |
||||
|
237: foo/bar |
||||
|
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.find = function (id) { |
||||
|
print('Duktape.find', 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.find = function (id) { |
||||
|
// Disable to avoid spam; each id is dynamic and long
|
||||
|
//print('Duktape.find', 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); |
||||
|
} |
Loading…
Reference in new issue