Wed, 30 Dec 2020 11:00:05 +0100
Updated copyright for 2021.
# -*- coding: utf-8 -*- # Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Parse a JavaScript file and retrieve variables and functions. It uses the JavaScript parser contained in the jasy web framework. """ import jasy.script.parse.Parser as jsParser import jasy.script.tokenize.Tokenizer as jsTokenizer import Utilities import Utilities.ClassBrowsers as ClassBrowsers from . import ClbrBaseClasses SUPPORTED_TYPES = [ClassBrowsers.JS_SOURCE] _modules = {} # cache of modules we've seen class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase): """ Mixin class implementing the notion of visibility. """ def __init__(self): """ Constructor """ if self.name.startswith('__'): self.setPrivate() elif self.name.startswith('_'): self.setProtected() else: self.setPublic() class Function(ClbrBaseClasses.Function, VisibilityMixin): """ Class to represent a Python function. """ def __init__(self, module, name, file, lineno, signature='', separator=','): """ Constructor @param module name of the module containing this function @param name name of this function @param file filename containing this class @param lineno linenumber of the class definition @param signature parameterlist of the method @param separator string separating the parameters """ ClbrBaseClasses.Function.__init__(self, module, name, file, lineno, signature, separator) VisibilityMixin.__init__(self) class Attribute(ClbrBaseClasses.Attribute, VisibilityMixin): """ Class to represent a class attribute. """ def __init__(self, module, name, file, lineno): """ Constructor @param module name of the module containing this class @param name name of this class @param file filename containing this attribute @param lineno linenumber of the class definition """ ClbrBaseClasses.Attribute.__init__(self, module, name, file, lineno) VisibilityMixin.__init__(self) class Visitor(object): """ Class implementing a visitor going through the parsed tree. """ def __init__(self, src, module, filename): """ Constructor @param src source to be parsed (string) @param module name of the module (string) @param filename file name (string) """ self.__dict = {} self.__dict_counts = {} self.__root = None self.__stack = [] self.__module = module self.__file = filename self.__source = src # normalize line endings self.__source = self.__source.replace("\r\n", "\n").replace("\r", "\n") # ensure source ends with an eol if bool(self.__source) and self.__source[-1] != '\n': self.__source = self.__source + '\n' def parse(self): """ Public method to parse the source. @return dictionary containing the parsed information """ try: self.__root = jsParser.parse(self.__source, self.__file) self.__visit(self.__root) except jsParser.SyntaxError: # ignore syntax errors of the parser pass except jsTokenizer.ParseError: # ignore syntax errors of the tokenizer pass return self.__dict def __visit(self, root): """ Private method implementing the visit logic delegating to interesting methods. @param root root node to visit """ def call(n): getattr(self, "visit_{0}".format(n.type), self.visit_noop)(n) call(root) for node in root: self.__visit(node) def visit_noop(self, node): """ Public method to ignore the given node. @param node reference to the node (jasy.script.parse.Node.Node) """ pass def visit_function(self, node): """ Public method to treat a function node. @param node reference to the node (jasy.script.parse.Node.Node) """ if ( node.type == "function" and getattr(node, "name", None) and node.functionForm == "declared_form" ): if self.__stack and self.__stack[-1].endlineno < node.line: del self.__stack[-1] endline = node.line + self.__source.count( '\n', node.start, node.end) if getattr(node, "params", None): func_sig = ", ".join([p.value for p in node.params]) else: func_sig = "" if self.__stack: # it's a nested function cur_func = self.__stack[-1] f = Function(None, node.name, self.__file, node.line, func_sig) f.setEndLine(endline) cur_func._addmethod(node.name, f) else: f = Function(self.__module, node.name, self.__file, node.line, func_sig) f.setEndLine(endline) func_name = node.name if func_name in self.__dict_counts: self.__dict_counts[func_name] += 1 func_name = "{0}_{1:d}".format( func_name, self.__dict_counts[func_name]) else: self.__dict_counts[func_name] = 0 self.__dict[func_name] = f self.__stack.append(f) def visit_property_init(self, node): """ Public method to treat a property_init node. @param node reference to the node (jasy.script.parse.Node.Node) """ if node.type == "property_init" and node[1].type == "function": if self.__stack and self.__stack[-1].endlineno < node[0].line: del self.__stack[-1] endline = node[0].line + self.__source.count( '\n', node.start, node[1].end) if getattr(node[1], "params", None): func_sig = ", ".join([p.value for p in node[1].params]) else: func_sig = "" if self.__stack: # it's a nested function cur_func = self.__stack[-1] f = Function(None, node[0].value, self.__file, node[0].line, func_sig) f.setEndLine(endline) cur_func._addmethod(node[0].value, f) else: f = Function(self.__module, node[0].value, self.__file, node[0].line, func_sig) f.setEndLine(endline) func_name = node[0].value if func_name in self.__dict_counts: self.__dict_counts[func_name] += 1 func_name = "{0}_{1:d}".format( func_name, self.__dict_counts[func_name]) else: self.__dict_counts[func_name] = 0 self.__dict[func_name] = f self.__stack.append(f) def visit_var(self, node): """ Public method to treat a variable node. @param node reference to the node (jasy.script.parse.Node.Node) """ if ( node.type == "var" and node.parent.type == "script" and node.getChildrenLength() ): if self.__stack and self.__stack[-1].endlineno < node[0].line: del self.__stack[-1] if self.__stack: # function variables for var in node: attr = Attribute( self.__module, var.name, self.__file, var.line) self.__stack[-1]._addattribute(attr) else: # global variable if "@@Globals@@" not in self.__dict: self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase( self.__module, "Globals", self.__file, 0) for var in node: self.__dict["@@Globals@@"]._addglobal(Attribute( self.__module, var.name, self.__file, var.line)) def visit_const(self, node): """ Public method to treat a constant node. @param node reference to the node (jasy.script.parse.Node.Node) """ if ( node.type == "const" and node.parent.type == "script" and node.getChildrenLength() ): if self.__stack and self.__stack[-1].endlineno < node[0].line: del self.__stack[-1] if self.__stack: # function variables for var in node: attr = Attribute(self.__module, "const " + var.name, self.__file, var.line) self.__stack[-1]._addattribute(attr) else: # global variable if "@@Globals@@" not in self.__dict: self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase( self.__module, "Globals", self.__file, 0) for var in node: self.__dict["@@Globals@@"]._addglobal( Attribute(self.__module, "const " + var.name, self.__file, var.line)) def readmodule_ex(module, path=None): """ Read a JavaScript file and return a dictionary of functions and variables. @param module name of the JavaScript file (string) @param path path the file should be searched in (list of strings) @return the resulting dictionary """ global _modules if module in _modules: # we've seen this file before... return _modules[module] # search the path for the file f = None fullpath = [] if path is None else path[:] f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath) if f: f.close() if type not in SUPPORTED_TYPES: # not CORBA IDL source, can't do anything with this module _modules[module] = {} return {} try: src = Utilities.readEncodedFile(file)[0] except (UnicodeError, OSError): # can't do anything with this module _modules[module] = {} return {} _modules[module] = scan(src, file, module) return _modules[module] def scan(src, file, module): """ Public method to scan the given source text. @param src source text to be scanned @type str @param file file name associated with the source text @type str @param module module name associated with the source text @type str @return dictionary containing the extracted data @rtype dict """ # convert eol markers the Python style src = src.replace("\r\n", "\n").replace("\r", "\n") dictionary = {} visitor = Visitor(src, module, file) dictionary = visitor.parse() _modules[module] = dictionary return dictionary