--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Utilities/ClassBrowsers/jsclbr.py Sun Apr 14 15:09:21 2019 +0200 @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 - 2019 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. +""" + +from __future__ import unicode_literals + +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 + + dictionary = {} + + 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] = dictionary + return dictionary + + _modules[module] = dictionary + try: + src = Utilities.readEncodedFile(file)[0] + except (UnicodeError, IOError): + # can't do anything with this module + _modules[module] = dictionary + return dictionary + + visitor = Visitor(src, module, file) + dictionary = visitor.parse() + _modules[module] = dictionary + return dictionary