Source code for jasy.script.api.Data

#
# Jasy - Web Tooling Framework
# Copyright 2010-2012 Zynga Inc.
# Copyright 2013-2014 Sebastian Werner
#

import jasy.script.api.Text as Text

from jasy.script.util import *
import jasy.core.Console as Console
from jasy import UserError


[docs]class ApiData(): """ Container for all relevant API data. Automatically generated, filled and cached by jasy.item.Script.getApiDocs(). """ __slots__ = [ "main", "construct", "statics", "properties", "events", "members", "id", "package", "basename", "errors", "size", "assets", "permutations", "content", "isEmpty", "uses", "usedBy", "includes", "includedBy", "implements", "implementedBy", "highlight" ] def __init__(self, id, highlight=True): self.id = id self.highlight = highlight splits = id.split(".") self.basename = splits.pop() self.package = ".".join(splits) self.isEmpty = False self.uses = set() self.main = { "type" : "Unsupported", "name" : id, "line" : 1 }
[docs] def addSize(self, size): """Adds the statistics on different size aspects.""" self.size = size
[docs] def addAssets(self, assets): """Adds the info about used assets.""" self.assets = assets
[docs] def addUses(self, uses): self.uses.add(uses)
[docs] def removeUses(self, uses): self.uses.remove(uses)
[docs] def addFields(self, permutations): self.permutations = permutations
[docs] def scanTree(self, tree): self.uses.update(tree.scope.shared) for package in tree.scope.packages: splits = package.split(".") current = splits[0] for split in splits[1:]: current = "%s.%s" % (current, split) self.uses.add(current) try: if not self.__processTree(tree): self.main["errornous"] = True except UserError as myError: raise myError except Exception as error: self.main["errors"] = ({ "line": 1, "message": "%s" % error }) self.main["errornous"] = True self.warn("Error during processing file: %s" % error, 1)
def __processTree(self, tree): success = False callNode = findCall(tree, ("core.Module", "core.Interface", "core.Class", "core.Main.declareNamespace")) if callNode: callName = getCallName(callNode) # # core.Module # if callName == "core.Module": self.setMain(callName, callNode.parent, self.id) staticsMap = getParameterFromCall(callNode, 1) if staticsMap: success = True self.statics = {} for staticsEntry in staticsMap: self.addEntry(staticsEntry[0].value, staticsEntry[1], staticsEntry, self.statics) else: self.warn("Invalid core.Module()", callNode.line) # # core.Interface # elif callName == "core.Interface": self.setMain(callName, callNode.parent, self.id) configMap = getParameterFromCall(callNode, 1) if configMap: success = True for propertyInit in configMap: sectionName = propertyInit[0].value sectionValue = propertyInit[1] if sectionName == "properties": self.properties = {} for propertyEntry in sectionValue: self.addProperty(propertyEntry[0].value, propertyEntry[1], propertyEntry, self.properties) elif sectionName == "events": self.events = {} for eventEntry in sectionValue: self.addEvent(eventEntry[0].value, eventEntry[1], eventEntry, self.events) elif sectionName == "members": self.members = {} for memberEntry in sectionValue: self.addEntry(memberEntry[0].value, memberEntry[1], memberEntry, self.members) else: self.warn('Invalid core.Interface section "%s"' % sectionName, propertyInit.line) else: self.warn("Invalid core.Interface()", callNode.line) # # core.Class # elif callName == "core.Class": self.setMain(callName, callNode.parent, self.id) configMap = getParameterFromCall(callNode, 1) if configMap: success = True for propertyInit in configMap: sectionName = propertyInit[0].value sectionValue = propertyInit[1] if sectionName == "construct": self.addConstructor(sectionValue, propertyInit) elif sectionName == "properties": self.properties = {} for propertyEntry in sectionValue: self.addProperty(propertyEntry[0].value, propertyEntry[1], propertyEntry, self.properties) elif sectionName == "events": self.events = {} for eventEntry in sectionValue: self.addEvent(eventEntry[0].value, eventEntry[1], eventEntry, self.events) elif sectionName == "members": self.members = {} for memberEntry in sectionValue: self.addEntry(memberEntry[0].value, memberEntry[1], memberEntry, self.members) elif sectionName == "include": self.includes = [valueToString(entry) for entry in sectionValue] elif sectionName == "implement": self.implements = [valueToString(entry) for entry in sectionValue] elif sectionName == "pooling": # TODO pass else: self.warn('Invalid core.Class section "%s"' % sectionName, propertyInit.line) else: self.warn("Invalid core.Class()", callNode.line) # # core.Main.declareNamespace # elif callName == "core.Main.declareNamespace": target = getParameterFromCall(callNode, 0) assigned = getParameterFromCall(callNode, 1) if target: success = True if assigned and assigned.type == "function": # Use callNode call for constructor, find first doc comment for main documentation self.setMain("core.Main", findCommentNode(tree), target.value) self.addConstructor(assigned, callNode.parent) else: self.setMain("core.Main", callNode.parent, target.value) if assigned and assigned.type == "object_init": self.statics = {} for staticsEntry in assigned: self.addEntry(staticsEntry[0].value, staticsEntry[1], staticsEntry, self.statics) # # Handle plain JS namespace -> object assignments # else: def assignMatcher(node): if node.type == "assign" and node[0].type == "dot": if node[1].type == "object_init": doc = getDocComment(node.parent) if not doc is None: return True elif node[1].type == "function": doc = getDocComment(node.parent) if not doc is None: return True return False result = query(tree, assignMatcher) if not result is None: name = assembleDot(result[0]) self.setMain("Native", result.parent, name) success = True if result[1].type == "object_init": # Ingore empty objects and do not produce namespaces for them # # e.g. some.namespace.foo = {}; if len(result[1]) == 0: success = False self.isEmpty = True self.statics = {} for prop in result[1]: self.addEntry(prop[0].value, prop[1], prop, self.statics) elif result[1].type == "function": self.addConstructor(result[1], result.parent) def memberMatcher(node): if node is not result and node.type == "assign" and node[0].type == "dot": assignName = assembleDot(node[0]) if assignName is not None and assignName != name and assignName.startswith(name) and len(assignName) > len(name): localName = assignName[len(name):] if localName.startswith("."): localName = localName[1:] # Support for MyClass.prototype.memberFoo = function() {} if "." in localName: splittedLocalName = localName.split(".") if len(splittedLocalName) == 2 and splittedLocalName[0] == "prototype": if not hasattr(self, "members"): self.members = {} self.addEntry(splittedLocalName[1], node[1], node.parent, self.members) # Support for MyClass.staticFoo = function() {} elif localName != "prototype": if not hasattr(self, "statics"): self.statics = {} self.addEntry(localName, node[1], node.parent, self.statics) else: if not hasattr(self, "members"): self.members = {} # Support for MyClass.prototype = {}; if node[1].type == "object_init": membersMap = node[1] for membersEntry in membersMap: self.addEntry(membersEntry[0].value, membersEntry[1], membersEntry, self.members) # Support for MyClass.prototype = new BaseClass; elif node[1].type == "new" or node[1].type == "new_with_args": self.includes = [valueToString(node[1][0])] queryAll(tree, memberMatcher) # # core.Main.addStatics # # addStatics = findCall(tree, "core.Main.addStatics") # if addStatics: # target = getParameterFromCall(addStatics, 0) # staticsMap = getParameterFromCall(addStatics, 1) # # if target and staticsMap and target.type == "string" and staticsMap.type == "object_init": # # if self.main["type"] == "Unsupported": # self.setMain("core.Main", addStatics.parent, target.value) # # success = True # if not hasattr(self, "statics"): # self.statics = {} # # for staticsEntry in staticsMap: # self.addEntry(staticsEntry[0].value, staticsEntry[1], staticsEntry, self.statics) # # else: # self.warn("Invalid core.Main.addStatics()") # # core.Main.addMembers # # addMembers = findCall(tree, "core.Main.addMembers") # if addMembers: # target = getParameterFromCall(addMembers, 0) # membersMap = getParameterFromCall(addMembers, 1) # # if target and membersMap and target.type == "string" and membersMap.type == "object_init": # # if self.main["type"] == "Unsupported": # self.setMain("core.Main", addMembers.parent, target.value) # # success = True # if not hasattr(self, "members"): # self.members = {} # # for membersEntry in membersMap: # self.addEntry(membersEntry[0].value, membersEntry[1], membersEntry, self.members) # # else: # self.warn("Invalid core.Main.addMembers()") # return success
[docs] def export(self): ret = {} for name in self.__slots__: if hasattr(self, name): ret[name] = getattr(self, name) return ret
[docs] def warn(self, message, line): Console.warn("%s at line %s in %s" % (message, line, self.id))
[docs] def setMain(self, mainType, mainNode, exportName): callComment = getDocComment(mainNode) entry = self.main = { "type" : mainType, "name" : exportName, "line" : mainNode.line } if callComment: if callComment.text: html = callComment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if hasattr(callComment, "tags"): entry["tags"] = callComment.tags if callComment is None or not callComment.text: entry["errornous"] = True self.warn('Missing comment on "%s" namespace' % exportName, mainNode.line)
[docs] def addProperty(self, name, valueNode, commentNode, collection): entry = collection[name] = { "line": (commentNode or valueNode).line } comment = getDocComment(commentNode) if comment is None or not comment.text: entry["errornous"] = True self.warn('Missing or empty comment on property "%s"' % name, valueNode.line) else: html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if comment and comment.tags: entry["tags"] = comment.tags # Copy over value ptype = getKeyValue(valueNode, "type") if ptype and ptype.type == "string": entry["type"] = ptype.value pfire = getKeyValue(valueNode, "fire") if pfire and pfire.type == "string": entry["fire"] = pfire.value # Produce nice output for init value pinit = getKeyValue(valueNode, "init") if pinit: entry["init"] = valueToString(pinit) # Handle nullable, default value is true when an init value is there. Otherwise false. pnullable = getKeyValue(valueNode, "nullable") if pnullable: entry["nullable"] = pnullable.type == "true" elif pinit is not None and pinit.type != "null": entry["nullable"] = False else: entry["nullable"] = True # Just store whether an apply routine was defined papply = getKeyValue(valueNode, "apply") if papply and papply.type == "function": entry["apply"] = True # Multi Properties pthemeable = getKeyValue(valueNode, "themeable") if pthemeable and pthemeable.type == "true": entry["themeable"] = True pinheritable = getKeyValue(valueNode, "inheritable") if pinheritable and pinheritable.type == "true": entry["inheritable"] = True pgroup = getKeyValue(valueNode, "group") if pgroup and len(pgroup) > 0: entry["group"] = [child.value for child in pgroup] pshorthand = getKeyValue(valueNode, "shorthand") if pshorthand and pshorthand.type == "true": entry["shorthand"] = True
[docs] def addConstructor(self, valueNode, commentNode=None): entry = self.construct = { "line" : (commentNode or valueNode).line } if commentNode is None: commentNode = valueNode # Root doc comment is optional for constructors comment = getDocComment(commentNode) if comment and comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) if comment and comment.tags: entry["tags"] = comment.tags entry["init"] = self.main["name"] funcParams = getParamNamesFromFunction(valueNode) if funcParams: entry["params"] = {} for paramPos, paramName in enumerate(funcParams): entry["params"][paramName] = { "position" : paramPos } # Use comment for enrich existing data comment = getDocComment(commentNode) if comment: if not comment.params: self.warn("Documentation for parameters of constructor are missing", valueNode.line) for paramName in funcParams: entry["params"][paramName]["errornous"] = True else: for paramName in funcParams: if paramName in comment.params: entry["params"][paramName].update(comment.params[paramName]) else: entry["params"][paramName]["errornous"] = True self.warn("Missing documentation for parameter %s in constructor" % paramName, valueNode.line) else: entry["errornous"] = True
[docs] def addEvent(self, name, valueNode, commentNode, collection): entry = collection[name] = { "line" : (commentNode or valueNode).line } if valueNode.type == "dot": entry["type"] = assembleDot(valueNode) elif valueNode.type == "identifier": entry["type"] = valueNode.value # Try to resolve identifier with local variable assignment assignments, values = findAssignments(valueNode.value, valueNode) if assignments: # We prefer the same comment node as before as in these # szenarios a reference might be used for different event types if not findCommentNode(commentNode): commentNode = assignments[0] self.addEvent(name, values[0], commentNode, collection) return comment = getDocComment(commentNode) if comment: if comment.tags: entry["tags"] = comment.tags # Prefer type but fall back to returns (if the developer has made an error here) if comment.type: entry["type"] = comment.type elif comment.returns: entry["type"] = comment.returns[0] if comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) else: self.warn("Comment contains invalid HTML", commentNode.line) entry["errornous"] = True else: self.warn("Invalid doc comment", commentNode.line) entry["errornous"] = True
[docs] def addEntry(self, name, valueNode, commentNode, collection): # # Use already existing type or get type from node info # if name in collection: entry = collection[name] else: entry = collection[name] = { "type" : nodeTypeToDocType[valueNode.type] } # # Store generic data like line number and visibility # entry["line"] = valueNode.line entry["visibility"] = getVisibility(name) if name.upper() == name: entry["constant"] = True # # Complex structured types are processed in two steps # if entry["type"] == "Call" or entry["type"] == "Hook": commentNode = findCommentNode(commentNode) if commentNode: comment = getDocComment(commentNode) if comment: # Static type definition if comment.type: entry["type"] = comment.type self.addEntry(name, valueNode, commentNode, collection) return else: # Maybe type function: We need to ignore returns etc. which are often # the parent of the comment. funcValueNode = findFunction(commentNode) if funcValueNode: # Switch to function type for re-analysis entry["type"] = "Function" self.addEntry(name, funcValueNode, commentNode, collection) return if entry["type"] == "Call": callFunction = None if valueNode[0].type == "function": callFunction = valueNode[0] elif valueNode[0].type == "identifier": assignNodes, assignValues = findAssignments(valueNode[0].value, valueNode[0]) if assignNodes: callFunction = assignValues[0] if callFunction: # We try to analyze what the first return node returns returnNode = findReturn(callFunction) if returnNode and len(returnNode) > 0: returnValue = returnNode[0] entry["type"] = nodeTypeToDocType[returnValue.type] self.addEntry(name, returnValue, returnValue, collection) elif entry["type"] == "Hook": thenEntry = valueNode[1] thenType = nodeTypeToDocType[thenEntry.type] if not thenType in ("void", "null"): entry["type"] = thenType self.addEntry(name, thenEntry, thenEntry, collection) # Try second item for better data then null/void else: elseEntry = valueNode[2] elseType = nodeTypeToDocType[elseEntry.type] entry["type"] = elseType self.addEntry(name, elseEntry, elseEntry, collection) return # # Try to resolve identifiers # if entry["type"] == "Identifier": assignTypeNode, assignCommentNode = resolveIdentifierNode(valueNode) if assignTypeNode is not None: entry["type"] = nodeTypeToDocType[assignTypeNode.type] # Prefer comment from assignment, not from value if available self.addEntry(name, assignTypeNode, assignCommentNode, collection) return # # Processes special types: # # - Plus: Whether a string or number is created # - Object: Figures out the class instance which is created # if entry["type"] == "Plus": entry["type"] = detectPlusType(valueNode) elif entry["type"] == "Object": entry["type"] = detectObjectType(valueNode) # # Add human readable value # valueNodeHumanValue = valueToString(valueNode) if valueNodeHumanValue != entry["type"] and not valueNodeHumanValue in ("Other", "Call"): entry["value"] = valueNodeHumanValue # # Read data from comment and add documentation # comment = getDocComment(commentNode) if comment: if comment.tags: entry["tags"] = comment.tags if comment.type: entry["type"] = comment.type if comment.hasContent(): html = comment.getHtml(self.highlight) entry["doc"] = html entry["summary"] = Text.extractSummary(html) else: entry["errornous"] = True if comment.tags: entry["tags"] = comment.tags else: entry["errornous"] = True # # Add additional data for function types (params, returns) # if entry["type"] == "Function": # Add basic param data funcParams = getParamNamesFromFunction(valueNode) if funcParams: entry["params"] = {} for paramPos, paramName in enumerate(funcParams): entry["params"][paramName] = { "position" : paramPos } # Detect return type automatically returnNode = findReturn(valueNode) if returnNode and len(returnNode) > 0: autoReturnType = nodeTypeToDocType[returnNode[0].type] if autoReturnType == "Plus": autoReturnType = detectPlusType(returnNode[0]) elif autoReturnType in ("Call", "Object"): autoReturnType = "var" autoReturnEntry = { "name" : autoReturnType, "auto" : True } if autoReturnType in builtinTypes: autoReturnEntry["builtin"] = True if autoReturnType in pseudoTypes: autoReturnEntry["pseudo"] = True entry["returns"] = [autoReturnEntry] # Use comment for enrich existing data if comment: if comment.returns: entry["returns"] = comment.returns if funcParams: if not comment.params: for paramName in funcParams: entry["params"][paramName]["errornous"] = True else: for paramName in funcParams: if paramName in comment.params: entry["params"][paramName].update(comment.params[paramName]) else: entry["params"][paramName]["errornous"] = True