toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
255 lines (241 loc) • 7.55 kB
JavaScript
/**
* On ne documente que les exports.
*
*/
var TreeWalker = require("./tree-walker");
var UglifyJS = require("uglify-js");
var Markdown = require("./tfw-md");
var JSON = require("./tolojson");
var declarations = {};
var tw = new TreeWalker();
var lastComments = undefined;
var rxTag = /^[ \t]*@([a-zA-Z]+)[ \t]*/;
var rxParam = /^(\{[^\}]*\})?[ \t]*([\w$_][\w$_0-9\.]*)[ \t-]*/;
function removeCommentStart(line) {
var n = line.length;
var i = 0;
while (i < n && line.charAt(i) <= ' ') i++;
if (i >= n) return "";
while (i < n && line.charAt(i) == '*') i++;
if (i >= n) return "";
if (line.charAt(i) == ' ') i++;
return line.substr(i);
}
function comments(node) {
var com = node.start.comments_before;
if (!Array.isArray(com) || com.length == 0) return undefined;
com = com[com.length - 1].value.trim();
var lines = com.split("\n");
var tags = {$summary: "", $full: ""};
var tag = "$summary";
var firstLine = true;
lines.forEach(
function(line) {
line = removeCommentStart(line);
if (firstLine) {
if (line.length == 0) return;
line = line.trim();
}
firstLine = false;
var m = line.match(rxTag);
var item;
if (m) {
line = line.substr(m[0].length);
tag = "$" + m[1].toLowerCase();
if (typeof tags[tag] === 'undefined') {
tags[tag] = [];
}
if (tag == '$param') {
m = line.match(rxParam);
item = {content: ""};
if (m) {
if (typeof m[1] === 'string') {
item.type = m[1].substr(1, m[1].length - 2).trim();
}
item.name = m[2].trim();
line = line.substr(m[0].length);
}
tags[tag].push(item);
} else {
tags[tag].push("");
}
}
if (tag == '$summary' && line.trim() == '') {
// Fin du summary, on passe en full description.
tags.$full = tags.$summary;
tag = '$full';
} else {
line += "\n";
item = tags[tag];
if (typeof item === 'string') {
tags[tag] += line;
}
else if (Array.isArray(item)) {
var arr = item;
item = item[arr.length - 1];
if (typeof item === 'string') {
arr[arr.length - 1] += line;
}
else if (typeof item.content === 'string') {
item.content += line;
}
}
}
}
);
if (Array.isArray(tags.$example)) {
// Préparer les exemples pour un highlight Javascript.
var i, example;
for (i = 0 ; i < tags.$example.length ; i++) {
example = tags.$example[i].trim();
tags.$example[i] = "```js\n" + example + "\n```";
}
}
var key, val;
for (key in tags) {
val = tags[key];
if (typeof val === 'string') {
tags[key] = Markdown.toHTML(val.trim());
}
else if (Array.isArray(val)) {
val.forEach(
function(itm, idx) {
if (typeof itm === 'string') {
val[idx] = Markdown.toHTML(itm.trim());
}
else if (typeof itm.content === 'string') {
itm.content = Markdown.toHTML(itm.content.trim());
}
}
);
}
}
return tags;
}
function getArgs(node) {
var args = [];
var argnames = node.argnames;
if (Array.isArray(argnames)) {
argnames.forEach(
function(arg) {
args.push(arg.name);
}
);
}
return args;
}
function getFunction(node) {
var name = node.name;
var com = comments(node);
if (typeof com === 'undefined' || com.length == 0) {
com = lastComments;
}
return {
TYPE: "Function",
name: name,
comments: com,
args: getArgs(node)
};
}
function parseFunction(node) {
var obj = getFunction(node.value);
declarations[node.name.name] = obj;
}
function parseMethod(node) {
var name = node.body.left.expression.expression.name;
var dec = declarations[name];
if (dec) {
dec.TYPE = "Class";
if (typeof dec.methods !== 'object') {
dec.methods = {};
}
var method = node.body.left.property;
dec.methods[method] = {
comments: comments(node),
args: getArgs(node.body.right)
};
}
}
function parseVar(tree) {
var actions = {
"[VarDef]value/[Function]": parseFunction
};
tree.definitions.forEach(
function(node) {
tw.action(node, actions);
}
);
}
function parseModuleExports(node) {
var exports = {
comments: comments(node) || lastComments
};
declarations.exports = exports;
var right = node.body.right;
if (right.TYPE == "SymbolRef") {
exports.value = declarations[right.name];
return;
}
}
function parseExportsAtt(node) {
if (typeof declarations.exports !== 'object'
|| declarations.exports.TYPE != 'Object')
{
declarations.exports = {
TYPE: "Object",
attributes: {}
};
}
var name = node.body.left.property;
var exports = {
comments: comments(node),
value: undefined
};
declarations.exports.attributes[name] = exports;
var right = node.body.right;
if (right.TYPE == "SymbolRef") {
exports.value = declarations[right.name];
return;
}
if (right.TYPE == "Function") {
exports.value = {
TYPE: "Function",
args: getArgs(right)
};
}
}
function parseAttribute(node) {
var objName = node.body.left.expression.name;
var attName = node.body.left.property;
var f = node.body.right;
if (f.TYPE == 'Function') {
var dec = declarations[objName];
if (typeof dec.statics !== 'object') {
dec.statics = {};
}
dec.statics[attName] = getFunction(f);
if (typeof dec.statics[attName].comments !== 'object') {
var com = comments(node) || lastComments;
dec.statics[attName].comments = com;
}
}
}
module.exports = function(code) {
declarations = {};
var tree = UglifyJS.parse(code);
var items = tree.body;
var actions = {
"[Var]definitions": parseVar,
"[SimpleStatement]body/[Assign]left/[Dot]expression/[Dot][property=prototype]": parseMethod,
"[SimpleStatement]body/[Assign][operator==]left/[Dot][property=exports]expression/[SymbolRef][name=module]": parseModuleExports,
"[SimpleStatement]body/[Assign][operator==]left/[Dot]expression/[name=exports]": parseExportsAtt,
"[SimpleStatement]body/[Assign][operator==]left/[Dot]expression/[SymbolRef]": parseAttribute
};
items.forEach( function(node) {
lastComments = comments(node);
tw.action(node, actions);
} );
return {
exports: declarations.exports
};
};