Ox.doc: change test object from {statement, result} to {statement, expected}, plus some refactoring; Ox.test: allow source, file, files, docObject or docObjects as first argument, make it testable and add a test, plus some refactoring; Ox.tokenize: add error type, continue lexing on error

This commit is contained in:
rolux 2012-05-29 16:18:05 +02:00
parent 38016aa06b
commit 5dc654ad6d

View file

@ -2,7 +2,7 @@
/*@ /*@
Ox.doc <f> Generates documentation for annotated JavaScript Ox.doc <f> Generates documentation for annotated JavaScript
(source) -> <[o]> Array of doc objects (source[, callback]) -> <[o]> Array of doc objects
(file, callback) -> <u> undefined (file, callback) -> <u> undefined
(files, callback) -> <u> undefined (files, callback) -> <u> undefined
source <s> JavaScript source code source <s> JavaScript source code
@ -35,6 +35,8 @@ Ox.doc <f> Generates documentation for annotated JavaScript
Present if the <code>type</code> of the item is Present if the <code>type</code> of the item is
<code>"function"</code>. <code>"function"</code>.
tests <[o]> Tests (array of test objects) tests <[o]> Tests (array of test objects)
expected <s> Expected result
statement <s> Statement
type <s> Type of the item type <s> Type of the item
<script> <script>
Ox.test.doc = Ox.doc( Ox.test.doc = Ox.doc(
@ -46,10 +48,14 @@ Ox.doc <f> Generates documentation for annotated JavaScript
' Bar per baz is a good indicator of an item\'s foo-ness.\n' + ' Bar per baz is a good indicator of an item\'s foo-ness.\n' +
' (item) -> <n> Bar per baz, or NaN\n' + ' (item) -> <n> Bar per baz, or NaN\n' +
' item <o> Any item\n' + ' item <o> Any item\n' +
' > My.foo({bar: 1, baz: 10})\n' +
' 0.1\n' +
' > My.foo({})\n' +
' NaN\n' +
'@*' + '/\n' + '@*' + '/\n' +
'My.foo = function(item) {\n' + 'My.foo = function(item) {\n' +
' return item.bar / item.baz;\n' + ' return item.bar / item.baz;\n' +
'}' '};'
); );
</script> </script>
> Ox.test.doc[0].name > Ox.test.doc[0].name
@ -64,6 +70,8 @@ Ox.doc <f> Generates documentation for annotated JavaScript
['number'] ['number']
> Ox.test.doc[1].usage[0].summary > Ox.test.doc[1].usage[0].summary
'Bar per baz, or NaN' 'Bar per baz, or NaN'
> Ox.test.doc[1].tests[1]
{expected: 'NaN', statement: 'My.foo({})'}
@*/ @*/
Ox.doc = (function() { Ox.doc = (function() {
var re = { var re = {
@ -127,12 +135,12 @@ Ox.doc = (function() {
item.usage.push(parseNode(node)); item.usage.push(parseNode(node));
} else if (subitem.types[0] == 'event') { } else if (subitem.types[0] == 'event') {
item.events = item.events || []; item.events = item.events || [];
item.events.push(parseNode(node)); item.events.push(parseNode(node));
} else { } else {
key = item.types[0] == 'function' key = item.types[0] == 'function'
? 'arguments' : 'properties' ? 'arguments' : 'properties'
item[key] = item[key] || []; item[key] = item[key] || [];
item[key].push(parseNode(node)); item[key].push(parseNode(node));
} }
} else { } else {
item.description = item.description item.description = item.description
@ -203,14 +211,14 @@ Ox.doc = (function() {
section = tree.line.split(' ')[0] section = tree.line.split(' ')[0]
} }
}); });
return items; return items;
} }
function parseTest(string) { function parseTest(string) {
// fixme: we cannot properly handle tests where a string contains '\n ' // fixme: we cannot properly handle tests where a string contains '\n '
var lines = decodeLinebreaks(string).split('\n '); var lines = decodeLinebreaks(string).split('\n ');
return { return {
statement: lines[0].slice(2), statement: lines[0].slice(2),
result: lines[1].trim() expected: lines[1].trim()
}; };
} }
function parseTokens(tokens, includeLeadingLinebreaks) { function parseTokens(tokens, includeLeadingLinebreaks) {
@ -311,18 +319,22 @@ Ox.doc = (function() {
} }
return ret; return ret;
} }
return function(/* source | file, callback | files, callback*/) { return function(argument, callback) {
var source = arguments.length == 1 ? arguments[0] : void 0, var counter = 0, items = [], ret;
files = arguments.length == 2 ? Ox.makeArray(arguments[0]) : void 0, if (arguments.length == 1) {
callback = arguments[1], ret = parseSource(argument);
counter = 0, items = []; } else {
files && files.forEach(function(file) { argument = Ox.makeArray(argument);
Ox.get(file, function(source) { argument.forEach(function(file) {
items = items.concat(parseSource(source, file.split('?')[0])); Ox.get(file, function(source) {
++counter == files.length && callback(items); items = items.concat(
}); parseSource(source, file.split('?')[0])
}); );
return source ? parseSource(source) : void 0; ++counter == argument.length && callback(items);
});
})
}
return ret;
} }
}()); }());
@ -567,91 +579,128 @@ Ox.minify = function() {
/*@ /*@
Ox.test <f> Takes JavaScript, runs inline tests, returns results Ox.test <f> Takes JavaScript, runs inline tests, returns results
(source, callback) -> <[o]> Array of results
(file, callback) -> <u> undefined (file, callback) -> <u> undefined
(files, callback) -> <u> undefines (files, callback) -> <u> undefined
file <s> Path to JavaScript file (doc, callback) -> <u> undefined
files <[s]> List of paths to J (docs, callback) -> <u> undefined
source <s> JavaScript source
file <s> JavaScript file
files <[s]> Array of JavaScript files
doc <o> Documentation object (as returned by Ox.doc)
docs <[o]> Array of documentation objects (as returned by Ox.doc)
callback <f> Callback function callback <f> Callback function
results [<o>] Array of results results <[o]> Array of results
actual <s> Actual result actual <s> Actual result
expected <s> Expected result expected <s> Expected result
name <s> Item name name <s> Item name
section <s> Section in the file section <s|u> Section in the file
statement <s> Test statement statement <s> Test statement
passed <b> True if actual result and expected result are equal passed <b> True if actual result and expected result are equal
<script>
Ox.test.foo = function(item) {
return item.bar / item.baz;
};
Ox.test.source =
'/*@\n'+
'Ox.test.foo <f> Returns an items\'s bar per baz\n' +
' Bar per baz is a good indicator of an item\'s foo-ness.\n' +
' (item) -> <n> Bar per baz, or NaN\n' +
' item <o> Any item\n' +
' > Ox.test.foo({bar: 1, baz: 10})\n' +
' 0.1\n' +
' > Ox.test.foo({})\n' +
' NaN\n' +
'@*' + '/\n' +
'Ox.test.foo = function(item) {\n' +
' return item.bar / item.baz;\n' +
'};';
</script>
> Ox.test(Ox.test.source, function(r) { Ox.test(r[0].passed, true); })
undefined
@*/ @*/
Ox.test = function(file, callback) { Ox.test = function(argument, callback) {
var regexp = /(Ox\.test\()/; Ox.print('Ox.test', argument, '----------------------------------')
if (arguments.length == 2) { function runTests(items) {
Ox.doc(file, function(items) { var id = Ox.uid(), regexp = /(.+Ox\.test\()/, results = [];
var results = []; Ox.test.data[id] = {
file = file.split('?')[0]; callback: callback,
Ox.test.data[file] = { done: false,
callback: callback, results: results,
done: false, tests: {}
results: [], };
tests: {} items.forEach(function(item) {
}; item.tests && item.tests.some(function(test) {
items.forEach(function(item) { return test.expected;
item.tests && item.tests.some(function(test) { }) && item.tests.forEach(function(test) {
return test.result; var actual, isAsync = regexp.test(test.statement);
}) && item.tests.forEach(function(test) { if (isAsync) {
var actual, isAsync = regexp.test(test.statement); Ox.test.data[id].tests[test.statement] = {
if (isAsync) { name: item.name,
Ox.test.data[file].tests[item.name] = { section: item.section
section: item.section, };
statement: test.statement Ox.Log('TEST', 'XXX', test.statement);
}; test.statement = test.statement.replace(
test.statement = test.statement.replace( regexp,
regexp, '$1\'' + item.name + '\', ' "$1'" + test.statement.replace(/'/g, "\\'") + "', "
); );
} }
if (test.result || test.statement.match(/Ox\.test/)) { if (test.expected || test.statement.match(/Ox\.test\./)) {
// don't eval script tags without assignment to Ox.test.foo // don't eval script tags without assignment to Ox.test.foo
actual = eval(test.statement); Ox.Log('TEST', test.statement);
Ox.Log('TEST', test.statement); actual = eval(test.statement);
} }
if (!isAsync && test.result) { if (!isAsync && test.expected) {
Ox.test.data[file].results.push({ Ox.test.data[id].results.push({
actual: JSON.stringify(actual), actual: JSON.stringify(actual),
expected: test.result, expected: test.expected,
name: item.name, name: item.name,
section: item.section, section: item.section,
statement: test.statement, statement: test.statement,
passed: Ox.isEqual(eval( passed: Ox.isEqual(
'(' + test.result + ')' actual, eval('(' + test.expected + ')')
), actual) )
}); });
} }
});
}); });
Ox.test.data[file].done = true;
if (Ox.isEmpty(Ox.test.data[file].tests)) {
callback(Ox.test.data[file].results);
}
}); });
Ox.test.data[id].done = true;
if (Ox.isEmpty(Ox.test.data[id].tests)) {
callback(Ox.test.data[id].results);
}
}
if (arguments.length == 2) {
if (Ox.typeOf(argument) == 'string' && Ox.contains(argument, '\n')) {
runTests(Ox.doc(argument))
} else {
argument = Ox.makeArray(argument);
if (Ox.typeOf(argument[0]) == 'string') {
Ox.doc(argument, runTests);
} else {
runTests(argument);
}
}
} else { } else {
var name = arguments[0], var statement = arguments[0],
result = arguments[1], result = arguments[1],
expected = arguments[2]; expected = arguments[2],
file = null; id, test;
Ox.forEach(Ox.test.data, function(v, k) { Ox.forEach(Ox.test.data, function(v, k) {
if (v.tests[name]) { if (v.tests[statement]) {
file = k; id = k;
test = v.tests[statement];
Ox.Break(); Ox.Break();
} }
}); });
Ox.test.data[file].results.push({ Ox.test.data[id].results.push(Ox.extend(test, {
actual: result, actual: result,
expected: expected, expected: expected,
name: name, statement: statement,
section: Ox.test.data[file].tests[name].section,
statement: Ox.test.data[file].tests[name].statement,
passed: Ox.isEqual(result, expected) passed: Ox.isEqual(result, expected)
}); }));
delete Ox.test.data[file].tests[name]; delete Ox.test.data[id].tests[statement];
if (Ox.test.data[file].done && Ox.isEmpty(Ox.test.data[file].tests)) { if (Ox.test.data[id].done && Ox.isEmpty(Ox.test.data[id].tests)) {
Ox.test.data[file].callback(Ox.test.data[file].results); Ox.test.data[id].callback(Ox.test.data[id].results);
} }
} }
}; };
@ -663,10 +712,11 @@ Ox.tokenize <f> Tokenizes JavaScript
column <n> Column of the token column <n> Column of the token
line <n> Line of the token line <n> Line of the token
type <s> Type of the token type <s> Type of the token
Type can be <code>"comment"</code>, <code>"identifier"</code>, Type can be <code>"comment"</code>, <code>"error"</code>,
<code>"linebreak"</code>, <code>"number"</code>, <code>"identifier"</code>, <code>"linebreak"</code>,
<code>"operator"</code>, <code>"regexp"</code>, <code>"number"</code>, <code>"operator"</code>,
<code>"string"</code> or <code>"whitespace"</code> <code>"regexp"</code>, <code>"string"</code> or
<code>"whitespace"</code>
value <s> Value of the token value <s> Value of the token
source <s> JavaScript source code source <s> JavaScript source code
> Ox.tokenize('// comment\nvar foo = bar / baz;').length > Ox.tokenize('// comment\nvar foo = bar / baz;').length
@ -790,10 +840,20 @@ Ox.tokenize = (function() {
type = 'whitespace'; type = 'whitespace';
while (whitespace.indexOf(source[++cursor]) > -1) {} while (whitespace.indexOf(source[++cursor]) > -1) {}
} else { } else {
break; type = 'error';
++cursor;
} }
value = source.slice(start, cursor); value = source.slice(start, cursor);
tokens.push({column: column, line: line, type: type, value: value}); if (
type == 'error' && tokens.length
&& tokens[tokens.length - 1].type == 'error'
) {
tokens[tokens.length - 1].value += value;
} else {
tokens.push(
{column: column, line: line, type: type, value: value}
);
}
if (type == 'comment') { if (type == 'comment') {
lines = value.split('\n'); lines = value.split('\n');
column = lines[lines.length - 1].length; column = lines[lines.length - 1].length;