ThirdParty/Jasy/jasy/script/util/__init__.py

changeset 6650
1dd52aa8897c
parent 6645
ad476851d7e0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ThirdParty/Jasy/jasy/script/util/__init__.py	Sat Jan 12 12:11:42 2019 +0100
@@ -0,0 +1,416 @@
+#
+# Jasy - Web Tooling Framework
+# Copyright 2010-2012 Zynga Inc.
+# Copyright 2013-2014 Sebastian Werner
+#
+
+from __future__ import unicode_literals
+
+from jasy.script.output.Compressor import Compressor
+
+# Shared instance
+compressor = Compressor()
+
+pseudoTypes = set(["any", "var", "undefined", "null", "true", "false", "this", "arguments"])
+builtinTypes = set(["Object", "String", "Number", "Boolean", "Array", "Function", "RegExp", "Date"])
+
+# Basic user friendly node type to human type
+nodeTypeToDocType = {
+
+    # Primitives
+    "string": "String",
+    "number": "Number",
+    "not": "Boolean",
+    "true": "Boolean",
+    "false": "Boolean",
+
+    # Literals
+    "function": "Function",
+    "regexp": "RegExp",
+    "object_init": "Map",
+    "array_init": "Array",
+
+    # We could figure out the real class automatically - at least that's the case quite often
+    "new": "Object",
+    "new_with_args": "Object",
+
+    # Comparisons
+    "eq" : "Boolean",
+    "ne" : "Boolean",
+    "strict_eq" : "Boolean",
+    "strict_ne" : "Boolean",
+    "lt" : "Boolean",
+    "le" : "Boolean",
+    "gt" : "Boolean",
+    "ge" : "Boolean",
+    "in" : "Boolean",
+    "instanceof" : "Boolean",
+
+    # Numbers
+    "lsh": "Number",
+    "rsh": "Number",
+    "ursh": "Number",
+    "minus": "Number",
+    "mul": "Number",
+    "div": "Number",
+    "mod": "Number",
+    "bitwise_and": "Number",
+    "bitwise_xor": "Number",
+    "bitwise_or": "Number",
+    "bitwise_not": "Number",
+    "increment": "Number",
+    "decrement": "Number",
+    "unary_minus": "Number",
+    "unary_plus": "Number",
+
+    # This is not 100% correct, but I don't like to introduce a BooleanLike type.
+    # If the author likes something different he is still able to override it via API docs
+    "and": "Boolean",
+    "or": "Boolean",
+
+    # Operators/Built-ins
+    "void": "undefined",
+    "null": "null",
+    "typeof": "String",
+    "delete": "Boolean",
+    "this": "This",
+
+    # These are not real types, we try to figure out the real value behind automatically
+    "call": "Call",
+    "hook": "Hook",
+    "assign": "Assign",
+    "plus": "Plus",
+    "identifier" : "Identifier",
+    "dot": "Object",
+    "index": "var"
+}
+
+
+def getVisibility(name):
+    """
+    Returns the visibility of the given name by convention
+    """
+
+    if name.startswith("__"):
+        return "private"
+    elif name.startswith("_"):
+        return "internal"
+    else:
+        return "public"
+
+
+def requiresDocumentation(name):
+    """
+    Whether the given name suggests that documentation is required
+    """
+
+    return not name.startswith("_")
+
+
+def getKeyValue(dict, key):
+    """
+    Returns the value node of the given key inside the given object initializer.
+    """
+
+    for propertyInit in dict:
+        if propertyInit[0].value == key:
+            return propertyInit[1]
+
+
+def findAssignments(name, node):
+    """
+    Returns a list of assignments which might have impact on the value used in the given node.
+    """
+
+    # Looking for all script blocks
+    scripts = []
+    parent = node
+    while parent:
+        if parent.type == "script":
+            scope = getattr(parent, "scope", None)
+            if scope and name in scope.modified:
+                scripts.append(parent)
+
+        parent = getattr(parent, "parent", None)
+
+    def assignMatcher(node):
+        if node.type == "assign" and node[0].type == "identifier" and node[0].value == name:
+            return True
+
+        if node.type == "declaration" and node.name == name and getattr(node, "initializer", None):
+            return True
+
+        if node.type == "function" and node.functionForm == "declared_form" and node.name == name:
+            return True
+
+        return False
+
+    # Query all relevant script nodes
+    assignments = []
+    for script in scripts:
+        queryResult = queryAll(script, assignMatcher, False)
+        assignments.extend(queryResult)
+
+    # Collect assigned values
+    values = []
+    for assignment in assignments:
+        if assignment.type == "function":
+            values.append(assignment)
+        elif assignment.type == "assign":
+            values.append(assignment[1])
+        else:
+            values.append(assignment.initializer)
+
+    return assignments, values
+
+
+def findFunction(node):
+    """
+    Returns the first function inside the given node
+    """
+
+    return query(node, lambda node: node.type == "function")
+
+
+def findCommentNode(node):
+    """
+    Finds the first doc comment node inside the given node
+    """
+
+    def matcher(node):
+        comments = getattr(node, "comments", None)
+        if comments:
+            for comment in comments:
+                if comment.variant == "doc":
+                    return True
+
+    return query(node, matcher)
+
+
+def getDocComment(node):
+    """
+    Returns the first doc comment of the given node.
+    """
+
+    comments = getattr(node, "comments", None)
+    if comments:
+        for comment in comments:
+            if comment.variant == "doc":
+                return comment
+
+    return None
+
+
+def findReturn(node):
+    """
+    Finds the first return inside the given node
+    """
+
+    return query(node, lambda node: node.type == "return", True)
+
+
+
+def valueToString(node):
+    """
+    Converts the value of the given node into something human friendly
+    """
+
+    if node.type in ("number", "string", "false", "true", "regexp", "null"):
+        return compressor.compress(node)
+    elif node.type in nodeTypeToDocType:
+        if node.type == "plus":
+            return detectPlusType(node)
+        elif node.type in ("new", "new_with_args", "dot"):
+            return detectObjectType(node)
+        else:
+            return nodeTypeToDocType[node.type]
+    else:
+        return "Other"
+
+
+
+def queryAll(node, matcher, deep=True, inner=False, result=None):
+    """
+    Recurses the tree starting with the given node and returns a list of nodes
+    matched by the given matcher method
+
+    - node: any node
+    - matcher: function which should return a truish value when node matches
+    - deep: whether inner scopes should be scanned, too
+    - inner: used internally to differentiate between current and inner nodes
+    - result: can be used to extend an existing list, otherwise a new list is created and returned
+    """
+
+    if result == None:
+        result = []
+
+    # Don't do in closure functions
+    if inner and node.type == "script" and not deep:
+        return None
+
+    if matcher(node):
+        result.append(node)
+
+    for child in node:
+        queryAll(child, matcher, deep, True, result)
+
+    return result
+
+
+
+def query(node, matcher, deep=True, inner=False):
+    """
+    Recurses the tree starting with the given node and returns the first node
+    which is matched by the given matcher method.
+
+    - node: any node
+    - matcher: function which should return a truish value when node matches
+    - deep: whether inner scopes should be scanned, too
+    - inner: used internally to differentiate between current and inner nodes
+    """
+
+    # Don't do in closure functions
+    if inner and node.type == "script" and not deep:
+        return None
+
+    if matcher(node):
+        return node
+
+    for child in node:
+        result = query(child, matcher, deep, True)
+        if result is not None:
+            return result
+
+    return None
+
+
+def findCall(node, methodName):
+    """
+    Recurses the tree starting with the given node and returns the first node
+    which calls the given method name (supports namespaces, too)
+    """
+
+    if type(methodName) is str:
+        methodName = set([methodName])
+
+    def matcher(node):
+        call = getCallName(node)
+        if call and call in methodName:
+            return call
+
+    return query(node, matcher)
+
+
+def getCallName(node):
+    if node.type == "call":
+        if node[0].type == "dot":
+            return assembleDot(node[0])
+        elif node[0].type == "identifier":
+            return node[0].value
+
+    return None
+
+
+def getParameterFromCall(call, index=0):
+    """
+    Returns a parameter node by index on the call node
+    """
+
+    try:
+        return call[1][index]
+    except:
+        return None
+
+
+def getParamNamesFromFunction(func):
+    """
+    Returns a human readable list of parameter names (sorted by their order in the given function)
+    """
+
+    params = getattr(func, "params", None)
+    if params:
+        return [identifier.value for identifier in params]
+    else:
+        return None
+
+
+def detectPlusType(plusNode):
+    """
+    Analyses the given "plus" node and tries to figure out if a "string" or "number" result is produced.
+    """
+
+    if plusNode[0].type == "string" or plusNode[1].type == "string":
+        return "String"
+    elif plusNode[0].type == "number" and plusNode[1].type == "number":
+        return "Number"
+    elif plusNode[0].type == "plus" and detectPlusType(plusNode[0]) == "String":
+        return "String"
+    else:
+        return "var"
+
+
+def detectObjectType(objectNode):
+    """
+    Returns a human readable type information of the given node
+    """
+
+    if objectNode.type in ("new", "new_with_args"):
+        construct = objectNode[0]
+    else:
+        construct = objectNode
+
+    # Only support built-in top level constructs
+    if construct.type == "identifier" and construct.value in ("Array", "Boolean", "Date", "Function", "Number", "Object", "String", "RegExp"):
+        return construct.value
+
+    # And namespaced custom classes
+    elif construct.type == "dot":
+        assembled = assembleDot(construct)
+        if assembled:
+            return assembled
+
+    return "Object"
+
+
+
+def resolveIdentifierNode(identifierNode):
+    assignNodes, assignValues = findAssignments(identifierNode.value, identifierNode)
+    if assignNodes:
+
+        assignCommentNode = None
+
+        # Find first relevant assignment with comment! Otherwise just first one.
+        for assign in assignNodes:
+
+            # The parent is the relevant doc comment container
+            # It's either a "var" (declaration) or "semicolon" (assignment)
+            if getDocComment(assign):
+                assignCommentNode = assign
+                break
+            elif getDocComment(assign.parent):
+                assignCommentNode = assign.parent
+                break
+
+        return assignValues[0], assignCommentNode or assignValues[0]
+
+    return None, None
+
+
+
+def assembleDot(node, result=None):
+    """
+    Joins a dot node (cascaded supported, too) into a single string like "foo.bar.Baz"
+    """
+
+    if result == None:
+        result = []
+
+    for child in node:
+        if child.type == "identifier":
+            result.append(child.value)
+        elif child.type == "dot":
+            assembleDot(child, result)
+        else:
+            return None
+
+    return ".".join(result)

eric ide

mercurial