eric6/Utilities/ClassBrowsers/jsclbr.py

changeset 6942
2602857055c5
parent 6650
1dd52aa8897c
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Parse a JavaScript file and retrieve variables and functions.
8
9 It uses the JavaScript parser contained in the jasy web framework.
10 """
11
12 from __future__ import unicode_literals
13
14 import jasy.script.parse.Parser as jsParser
15 import jasy.script.tokenize.Tokenizer as jsTokenizer
16
17 import Utilities
18 import Utilities.ClassBrowsers as ClassBrowsers
19 from . import ClbrBaseClasses
20
21 SUPPORTED_TYPES = [ClassBrowsers.JS_SOURCE]
22
23 _modules = {} # cache of modules we've seen
24
25
26 class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase):
27 """
28 Mixin class implementing the notion of visibility.
29 """
30 def __init__(self):
31 """
32 Constructor
33 """
34 if self.name.startswith('__'):
35 self.setPrivate()
36 elif self.name.startswith('_'):
37 self.setProtected()
38 else:
39 self.setPublic()
40
41
42 class Function(ClbrBaseClasses.Function, VisibilityMixin):
43 """
44 Class to represent a Python function.
45 """
46 def __init__(self, module, name, file, lineno, signature='',
47 separator=','):
48 """
49 Constructor
50
51 @param module name of the module containing this function
52 @param name name of this function
53 @param file filename containing this class
54 @param lineno linenumber of the class definition
55 @param signature parameterlist of the method
56 @param separator string separating the parameters
57 """
58 ClbrBaseClasses.Function.__init__(self, module, name, file, lineno,
59 signature, separator)
60 VisibilityMixin.__init__(self)
61
62
63 class Attribute(ClbrBaseClasses.Attribute, VisibilityMixin):
64 """
65 Class to represent a class attribute.
66 """
67 def __init__(self, module, name, file, lineno):
68 """
69 Constructor
70
71 @param module name of the module containing this class
72 @param name name of this class
73 @param file filename containing this attribute
74 @param lineno linenumber of the class definition
75 """
76 ClbrBaseClasses.Attribute.__init__(self, module, name, file, lineno)
77 VisibilityMixin.__init__(self)
78
79
80 class Visitor(object):
81 """
82 Class implementing a visitor going through the parsed tree.
83 """
84 def __init__(self, src, module, filename):
85 """
86 Constructor
87
88 @param src source to be parsed (string)
89 @param module name of the module (string)
90 @param filename file name (string)
91 """
92 self.__dict = {}
93 self.__dict_counts = {}
94 self.__root = None
95 self.__stack = []
96
97 self.__module = module
98 self.__file = filename
99 self.__source = src
100
101 # normalize line endings
102 self.__source = self.__source.replace("\r\n", "\n").replace("\r", "\n")
103
104 # ensure source ends with an eol
105 if bool(self.__source) and self.__source[-1] != '\n':
106 self.__source = self.__source + '\n'
107
108 def parse(self):
109 """
110 Public method to parse the source.
111
112 @return dictionary containing the parsed information
113 """
114 try:
115 self.__root = jsParser.parse(self.__source, self.__file)
116 self.__visit(self.__root)
117 except jsParser.SyntaxError:
118 # ignore syntax errors of the parser
119 pass
120 except jsTokenizer.ParseError:
121 # ignore syntax errors of the tokenizer
122 pass
123
124 return self.__dict
125
126 def __visit(self, root):
127 """
128 Private method implementing the visit logic delegating to interesting
129 methods.
130
131 @param root root node to visit
132 """
133 def call(n):
134 getattr(self, "visit_{0}".format(n.type),
135 self.visit_noop)(n)
136
137 call(root)
138 for node in root:
139 self.__visit(node)
140
141 def visit_noop(self, node):
142 """
143 Public method to ignore the given node.
144
145 @param node reference to the node (jasy.script.parse.Node.Node)
146 """
147 pass
148
149 def visit_function(self, node):
150 """
151 Public method to treat a function node.
152
153 @param node reference to the node (jasy.script.parse.Node.Node)
154 """
155 if node.type == "function" and \
156 getattr(node, "name", None) and \
157 node.functionForm == "declared_form":
158 if self.__stack and self.__stack[-1].endlineno < node.line:
159 del self.__stack[-1]
160 endline = node.line + self.__source.count(
161 '\n', node.start, node.end)
162 if getattr(node, "params", None):
163 func_sig = ", ".join([p.value for p in node.params])
164 else:
165 func_sig = ""
166 if self.__stack:
167 # it's a nested function
168 cur_func = self.__stack[-1]
169 f = Function(None, node.name,
170 self.__file, node.line, func_sig)
171 f.setEndLine(endline)
172 cur_func._addmethod(node.name, f)
173 else:
174 f = Function(self.__module, node.name,
175 self.__file, node.line, func_sig)
176 f.setEndLine(endline)
177 func_name = node.name
178 if func_name in self.__dict_counts:
179 self.__dict_counts[func_name] += 1
180 func_name = "{0}_{1:d}".format(
181 func_name, self.__dict_counts[func_name])
182 else:
183 self.__dict_counts[func_name] = 0
184 self.__dict[func_name] = f
185 self.__stack.append(f)
186
187 def visit_property_init(self, node):
188 """
189 Public method to treat a property_init node.
190
191 @param node reference to the node (jasy.script.parse.Node.Node)
192 """
193 if node.type == "property_init" and node[1].type == "function":
194 if self.__stack and self.__stack[-1].endlineno < node[0].line:
195 del self.__stack[-1]
196 endline = node[0].line + self.__source.count(
197 '\n', node.start, node[1].end)
198 if getattr(node[1], "params", None):
199 func_sig = ", ".join([p.value for p in node[1].params])
200 else:
201 func_sig = ""
202 if self.__stack:
203 # it's a nested function
204 cur_func = self.__stack[-1]
205 f = Function(None, node[0].value,
206 self.__file, node[0].line, func_sig)
207 f.setEndLine(endline)
208 cur_func._addmethod(node[0].value, f)
209 else:
210 f = Function(self.__module, node[0].value,
211 self.__file, node[0].line, func_sig)
212 f.setEndLine(endline)
213 func_name = node[0].value
214 if func_name in self.__dict_counts:
215 self.__dict_counts[func_name] += 1
216 func_name = "{0}_{1:d}".format(
217 func_name, self.__dict_counts[func_name])
218 else:
219 self.__dict_counts[func_name] = 0
220 self.__dict[func_name] = f
221 self.__stack.append(f)
222
223 def visit_var(self, node):
224 """
225 Public method to treat a variable node.
226
227 @param node reference to the node (jasy.script.parse.Node.Node)
228 """
229 if node.type == "var" and \
230 node.parent.type == "script" and \
231 node.getChildrenLength():
232 if self.__stack and self.__stack[-1].endlineno < node[0].line:
233 del self.__stack[-1]
234 if self.__stack:
235 # function variables
236 for var in node:
237 attr = Attribute(
238 self.__module, var.name, self.__file, var.line)
239 self.__stack[-1]._addattribute(attr)
240 else:
241 # global variable
242 if "@@Globals@@" not in self.__dict:
243 self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase(
244 self.__module, "Globals", self.__file, 0)
245 for var in node:
246 self.__dict["@@Globals@@"]._addglobal(Attribute(
247 self.__module, var.name, self.__file, var.line))
248
249 def visit_const(self, node):
250 """
251 Public method to treat a constant node.
252
253 @param node reference to the node (jasy.script.parse.Node.Node)
254 """
255 if node.type == "const" and \
256 node.parent.type == "script" and \
257 node.getChildrenLength():
258 if self.__stack and self.__stack[-1].endlineno < node[0].line:
259 del self.__stack[-1]
260 if self.__stack:
261 # function variables
262 for var in node:
263 attr = Attribute(self.__module, "const " + var.name,
264 self.__file, var.line)
265 self.__stack[-1]._addattribute(attr)
266 else:
267 # global variable
268 if "@@Globals@@" not in self.__dict:
269 self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase(
270 self.__module, "Globals", self.__file, 0)
271 for var in node:
272 self.__dict["@@Globals@@"]._addglobal(
273 Attribute(self.__module, "const " + var.name,
274 self.__file, var.line))
275
276
277 def readmodule_ex(module, path=None):
278 """
279 Read a JavaScript file and return a dictionary of functions and variables.
280
281 @param module name of the JavaScript file (string)
282 @param path path the file should be searched in (list of strings)
283 @return the resulting dictionary
284 """
285 global _modules
286
287 dictionary = {}
288
289 if module in _modules:
290 # we've seen this file before...
291 return _modules[module]
292
293 # search the path for the file
294 f = None
295 fullpath = [] if path is None else path[:]
296 f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath)
297 if f:
298 f.close()
299 if type not in SUPPORTED_TYPES:
300 # not CORBA IDL source, can't do anything with this module
301 _modules[module] = dictionary
302 return dictionary
303
304 _modules[module] = dictionary
305 try:
306 src = Utilities.readEncodedFile(file)[0]
307 except (UnicodeError, IOError):
308 # can't do anything with this module
309 _modules[module] = dictionary
310 return dictionary
311
312 visitor = Visitor(src, module, file)
313 dictionary = visitor.parse()
314 _modules[module] = dictionary
315 return dictionary

eric ide

mercurial