raptor
Version:
RaptorJS provides an AMD module loader that works in Node, Rhino and the web browser. It also includes various sub-modules to support building optimized web applications.
621 lines (492 loc) • 22.4 kB
JavaScript
/**
*
*
*/
define(
'raptor/jsdoc/ASTWalker',
['raptor'],
function(raptor, require, exports, module) {
"use strict";
var logger = module.logger(),
arrays = require('raptor/arrays'),
Type = require("raptor/jsdoc/Type");
var toStrings = {
Literal: function() {
return JSON.stringify(this.value);
},
Identifier: function() {
return this.name;
},
MemberExpression: function() {
if (this.computed) {
return nodeToString(this.object) + "[" + nodeToString(this.property) + "]";
}
else {
return nodeToString(this.object) + "." + nodeToString(this.property);
}
},
FunctionExpression: function() {
return "function(" + argsToString(this.params) + ") " + nodeToString(this.body);
},
CallExpression: function() {
return nodeToString(this.callee) + "(" + argsToString(this['arguments']) + ")";
},
BlockStatement: function() {
return "{" + this.body.map(function(statement) {
return nodeToString(statement);
}).join(" ") + "}";
},
ReturnStatement: function() {
return "return " + nodeToString(this.argument) + ";";
},
ExpressionStatement: function() {
return nodeToString(this.expression) + ";";
},
ObjectExpression: function() {
return "{" + this.properties.map(function(prop) {
return nodeToString(prop);
}).join(", ") + "}";
},
AssignmentExpression: function() {
return nodeToString(this.left) + "=" + nodeToString(this.right);
},
Property: function() {
return nodeToString(this.key) + ": " + nodeToString(this.value);
},
ThisExpression: function() {
return "this";
},
VariableDeclaration: function() {
return "var " + this.declarations.map(function(declarator) {
return nodeToString(declarator);
}).join(",") + ";";
},
VariableDeclarator: function() {
return nodeToString(this.id) + (this.init ? "=" + nodeToString(this.init) : "");
}
},
nodeToString = function(node) {
if (!node) {
return "[null node]";
}
var func = toStrings[node.type];
return func ? func.call(node) : "[" + node.type + "]";
},
nodeInstanceToString = function() {
return nodeToString(this);
},
argsToString = function(args) {
return args.map(function(arg) {
return nodeToString(arg);
}).join(", ");
};
var ASTWalker = function(env) {
this.env = env;
this.symbols = env.getSymbols();
this.scopeStack = [this.env.getGlobal()];
this.stack = [];
this.indent = "";
};
ASTWalker.prototype = {
resolveMemberExpressionPropertyName: function(node) {
var propertyNode = node.property,
propName = null;
if (node.computed === true) {
this.walk(node.property);
propName = node.property.resolvedType && node.property.resolvedType.value;
}
else {
if (propertyNode.type === 'Literal') {
propName = propertyNode.value;
}
else if (propertyNode.type === 'Identifier') {
propName = propertyNode.name;
}
else {
throw raptor.createError(new Error("Unexpected type for property node: " + propertyNode.type));
}
}
return propName;
},
resolveAssignmentObject: function(node) {
if (node.type === 'Identifier') {
var existingVar = this.currentScope().resolveVar(node.name);
if (existingVar) {
node.resolvedType = existingVar;
}
else {
var newGlobal = new Type();
this.getGlobal().setProperty({
name: node.name,
type: newGlobal
});
node.resolvedType = newGlobal;
}
this.publishAfterWalk(node);
}
else if (node.type === 'MemberExpression') {
var objectType = this.resolveAssignmentObject(node.object);
if (objectType) {
var propertyNode = node.property,
name;
name = this.resolveMemberExpressionPropertyName(node);
if (name != null) {
var existingProperty = objectType.getPropertyType(name);
if (existingProperty) {
node.resolvedType = existingProperty;
}
else {
var newProperty = new Type();
objectType.setProperty({
name: name,
type: newProperty
});
node.resolvedType = newProperty;
}
}
}
this.publishAfterWalk(node);
}
else {
this.walk(node);
}
return node.resolvedType;
},
getSymbols: function() {
return this.symbols;
},
currentScope: function() {
return this.scopeStack[this.scopeStack.length-1];
},
resolveVar: function(name) {
return this.currentScope().resolveVar(name);
},
invokeFunctionExpression: function(node, args) {
if (node.type !== 'FunctionExpression') {
throw raptor.createError(new Error("invokeFunctionExpressionNode() Invalid node type: " + node.type));
}
node.args = args;
this.walk(node);
return node.scope;
},
resolveType: function(node) {
this.walk(node);
return node.resolvedType;
},
getGlobal: function() {
return this.env.getGlobal();
},
argsToString: function(args) {
return argsToString(args);
},
walk: function(node) {
var type = node.type;
node.toString = nodeInstanceToString;
var walkerFunc = this["walk_" + type];
if (walkerFunc) {
if (logger.isDebugEnabled()) {
console.log(this.indent + "walk_" + type + ": " + nodeToString(node), node.loc ? "Line: " + node.loc.start.line : "");
}
this.indent += " ";
this.stack.push(node);
walkerFunc.call(this, node);
this.publishAfterWalk(node);
arrays.pop(this.stack);
this.indent = this.indent.substring(2);
}
else {
if (logger.isDebugEnabled()) {
console.log(this.indent + 'walk(): Unrecognized node of type "' + node.type + '" [' + (node.loc ? node.loc.start.line + ':' + node.loc.start.column : "(unknown location)") + ']: ' + nodeToString(node));
}
}
},
publishAfterWalk: function(node) {
this.env.publish(node.type, {
scope: this.currentScope(),
type: node.resolvedType,
comment: node.comment,
symbols: this.symbols,
node: node,
walker: this
});
},
walk_Program: function(node) {
if (node.body) {
node.body.forEach(function(bodyStatement) {
this.walk(bodyStatement);
}, this);
}
if (node.comments) {
node.comments.forEach(function(comment) {
this.walk(comment);
}, this);
}
},
walk_ExpressionStatement: function(node) {
if (node.comment) {
/*
* Pass along the comment from the expression statement to the actual expression "CallExpression" node
*/
node.expression.comment = node.comment;
}
this.walk(node.expression);
this.resolvedType = node.expression.resolvedType;
},
walk_CallExpression: function(node) {
//See if there is a static type resolver for this function
this.walk(node.callee);
if (node.callee.resolvedType && node.callee.resolvedType.resolver) {
/*
* Rely on the custom resolver to walk to invoke the function
*/
node.resolvedType = node.callee.resolvedType.resolver(node, this);
}
else {
/*
* If no custom resolver is provided then just walk the arguments
*/
if (node['arguments']) {
node['arguments'].forEach(function(arg) {
this.walk(arg);
}, this);
}
}
},
walk_FunctionExpression: function(node) {
var scope = new Type();
scope.functionExpressionNode = node;
scope.functionType = node.resolvedType = new Type("function");
scope.functionType.functionScope = scope;
node.params.forEach(function(paramNode) {
node.resolvedType.addFunctionParam({
name: paramNode.name,
comment: paramNode.comment
});
}, this);
scope.setParentScope(arrays.peek(this.scopeStack));
this.scopeStack.push(scope);
try
{
if (node.args) {
raptor.forEachEntry(node.args, function(name, type) {
scope.setProperty({
name: name,
type: type
}); //Add the parameters to the scope
});
}
this.walk(node.body);
node.scope = scope;
}
finally {
arrays.pop(this.scopeStack);
}
},
walk_AssignmentExpression: function(node) {
var leftNode = node.left,
rightNode = node.right;
this.walk(rightNode);
var parentType = null,
targetPropName = null,
resolvedType = null,
existingType = null;
if (leftNode.type === 'MemberExpression') {
/*
* The l-value is a member expression.
*
* We will split it into two parts so that
* we can add the new property
*/
var objectNode = leftNode.object;
var objectType = this.resolveAssignmentObject(objectNode);
if (objectType) {
var propName = this.resolveMemberExpressionPropertyName(leftNode);
targetPropName = propName;
parentType = objectType;
existingType = objectType && propName ? objectType.getPropertyType(propName) : null;
}
}
else if (leftNode.type === 'Identifier') {
//Simple variable assignment: a = "Hello";
existingType = this.currentScope().resolveVar(leftNode.name);
targetPropName = leftNode.name;
parentType = this.getGlobal(); //Creating a new global
}
resolvedType = rightNode.resolvedType;
if (existingType) {
existingType.addType(resolvedType);
}
else if (parentType && targetPropName) {
parentType.setProperty({
name: targetPropName,
type: resolvedType,
comment: node.comment
});
}
this.env.publish("assignment", {
type: resolvedType,
scope: this.currentScope(),
comment: node.comment,
symbols: this.symbols,
node: node,
walker: this,
setType: function(type) {
resolvedType = type;
}
});
node.resolvedType = leftNode.resolvedType = resolvedType;
this.publishAfterWalk(leftNode);
},
walk_BlockStatement: function(node) {
node.body.forEach(function(bodyStatement) {
this.walk(bodyStatement);
}, this);
},
walk_ReturnStatement: function(node) {
var type = node.argument ? this.resolveType(node.argument) : null;
var scope = this.currentScope();
if (scope.returnType) {
if (type) {
scope.returnType.addType(type);
}
}
else {
scope.returnType = type;
}
node.resolvedReturnType = scope.returnType;
},
walk_VariableDeclaration: function(node) {
if (node.comment && node.declarations.length) {
//Pass along the comment to the first variable declarator
node.declarations[0].comment = node.comment;
}
node.declarations.forEach(function(declarator) {
this.walk(declarator);
}, this);
},
walk_VariableDeclarator: function(node) {
var scope = this.currentScope();
var varName = node.id.name,
type = null;
if (node.init) {
type = this.resolveType(node.init);
}
if (node.comment && type) {
type.setComment(node.comment);
}
node.resolvedType = type;
this.env.publish("assignment", {
type: type,
scope: this.currentScope(),
comment: node.comment,
symbols: this.symbols,
node: node,
walker: this,
setType: function(type) {
node.resolvedType = type;
}
});
this.env.publish("var", {
type: type,
scope: this.currentScope(),
comment: node.comment,
symbols: this.symbols,
node: node,
varName: varName,
walker: this,
setType: function(type) {
node.resolvedType = type;
}
});
scope.addLocalVariable(varName, node.resolvedType, node.comment);
},
walk_IfStatement: function(node) {
this.walk(node.test);
this.walk(node.consequent);
if (node.alternate) {
this.walk(node.alternate);
}
},
walk_UnaryExpression: function(node) {
this.walk(node.argument);
},
walk_WhileStatement: function(node) {
this.walk(node.test);
this.walk(node.body);
},
walk_DoWhileStatement: function(node) {
this.walk(node.body);
this.walk(node.test);
},
walk_MemberExpression: function(node) {
this.walk(node.object);
var propertyNode = node.property;
var propName = this.resolveMemberExpressionPropertyName(node);
var objectType = node.object.resolvedType;
if (objectType && propName) { //Check if the property is a static value
node.resolvedType = objectType.getPropertyType(propName);
}
},
walk_ObjectExpression: function(node) {
var objectType = new Type("object");
node.properties.forEach(function(prop) {
var keyNode = prop.key,
key;
this.walk(prop);
if (keyNode.type === 'Identifier') {
key = keyNode.name;
}
else if (keyNode.type === 'Literal') {
key = keyNode.value;
}
else {
throw raptor.createError(new Error('Invalid type: ' + keyNode.type));
}
objectType.setProperty({
name: key,
type: prop.resolvedType || prop.value.resolvedType,
comment: prop.comment
});
}, this);
node.resolvedType = objectType;
},
walk_Property: function(node) {
this.walk(node.value);
this.env.publish("property", {
type: node.value.resolvedType,
scope: this.currentScope(),
comment: node.comment,
symbols: this.symbols,
node: node,
walker: this,
setType: function(type) {
node.resolvedType = type;
}
});
},
walk_ThisExpression: function(node) {
var scope = this.currentScope();
if (scope.functionType) {
node.resolvedType = scope.functionType.getInstanceType();
}
},
walk_Literal: function(node) {
node.resolvedType = new Type(typeof node.value);
node.resolvedType.value = node.value;
},
walk_Identifier: function(node) {
node.resolvedType = this.currentScope().resolveVar(node.name);
//
// if (node.name === 'isArray') {
// console.error('isArray: currrentScope=', this.currentScope().parentScope.getPropertyType("isArray"), ' resolvedType=', node.resolvedType);
// }
},
walk_JSDocComment: function(node) {
},
walk_BlockComment: function(node) {
},
walk_LineComment: function(node) {
}
};
return ASTWalker;
});