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