eric7/Utilities/ClassBrowsers/jsclbr.py

branch
eric7
changeset 8312
800c432b34c8
parent 8207
d359172d11be
child 8881
54e42bc2437a
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2021 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 import jasy.script.parse.Parser as jsParser
13 import jasy.script.tokenize.Tokenizer as jsTokenizer
14
15 import Utilities
16 import Utilities.ClassBrowsers as ClassBrowsers
17 from . import ClbrBaseClasses
18
19 SUPPORTED_TYPES = [ClassBrowsers.JS_SOURCE]
20
21 _modules = {} # cache of modules we've seen
22
23
24 class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase):
25 """
26 Mixin class implementing the notion of visibility.
27 """
28 def __init__(self):
29 """
30 Constructor
31 """
32 if self.name.startswith('__'):
33 self.setPrivate()
34 elif self.name.startswith('_'):
35 self.setProtected()
36 else:
37 self.setPublic()
38
39
40 class Function(ClbrBaseClasses.Function, VisibilityMixin):
41 """
42 Class to represent a Python function.
43 """
44 def __init__(self, module, name, file, lineno, signature='',
45 separator=','):
46 """
47 Constructor
48
49 @param module name of the module containing this function
50 @param name name of this function
51 @param file filename containing this class
52 @param lineno linenumber of the class definition
53 @param signature parameterlist of the method
54 @param separator string separating the parameters
55 """
56 ClbrBaseClasses.Function.__init__(self, module, name, file, lineno,
57 signature, separator)
58 VisibilityMixin.__init__(self)
59
60
61 class Attribute(ClbrBaseClasses.Attribute, VisibilityMixin):
62 """
63 Class to represent a class attribute.
64 """
65 def __init__(self, module, name, file, lineno):
66 """
67 Constructor
68
69 @param module name of the module containing this class
70 @param name name of this class
71 @param file filename containing this attribute
72 @param lineno linenumber of the class definition
73 """
74 ClbrBaseClasses.Attribute.__init__(self, module, name, file, lineno)
75 VisibilityMixin.__init__(self)
76
77
78 class Visitor:
79 """
80 Class implementing a visitor going through the parsed tree.
81 """
82 def __init__(self, src, module, filename):
83 """
84 Constructor
85
86 @param src source to be parsed (string)
87 @param module name of the module (string)
88 @param filename file name (string)
89 """
90 self.__dict = {}
91 self.__dict_counts = {}
92 self.__root = None
93 self.__stack = []
94
95 self.__module = module
96 self.__file = filename
97 self.__source = src
98
99 # normalize line endings
100 self.__source = self.__source.replace("\r\n", "\n").replace("\r", "\n")
101
102 # ensure source ends with an eol
103 if bool(self.__source) and self.__source[-1] != '\n':
104 self.__source = self.__source + '\n'
105
106 def parse(self):
107 """
108 Public method to parse the source.
109
110 @return dictionary containing the parsed information
111 """
112 try:
113 self.__root = jsParser.parse(self.__source, self.__file)
114 self.__visit(self.__root)
115 except jsParser.SyntaxError:
116 # ignore syntax errors of the parser
117 pass
118 except jsTokenizer.ParseError:
119 # ignore syntax errors of the tokenizer
120 pass
121
122 return self.__dict
123
124 def __visit(self, root):
125 """
126 Private method implementing the visit logic delegating to interesting
127 methods.
128
129 @param root root node to visit
130 """
131 def call(n):
132 getattr(self, "visit_{0}".format(n.type),
133 self.visit_noop)(n)
134
135 call(root)
136 for node in root:
137 self.__visit(node)
138
139 def visit_noop(self, node):
140 """
141 Public method to ignore the given node.
142
143 @param node reference to the node (jasy.script.parse.Node.Node)
144 """
145 pass
146
147 def visit_function(self, node):
148 """
149 Public method to treat a function node.
150
151 @param node reference to the node (jasy.script.parse.Node.Node)
152 """
153 if (
154 node.type == "function" and
155 getattr(node, "name", None) and
156 node.functionForm == "declared_form"
157 ):
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 (
230 node.type == "var" and
231 node.parent.type == "script" and
232 node.getChildrenLength()
233 ):
234 if self.__stack and self.__stack[-1].endlineno < node[0].line:
235 del self.__stack[-1]
236 if self.__stack:
237 # function variables
238 for var in node:
239 attr = Attribute(
240 self.__module, var.name, self.__file, var.line)
241 self.__stack[-1]._addattribute(attr)
242 else:
243 # global variable
244 if "@@Globals@@" not in self.__dict:
245 self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase(
246 self.__module, "Globals", self.__file, 0)
247 for var in node:
248 self.__dict["@@Globals@@"]._addglobal(Attribute(
249 self.__module, var.name, self.__file, var.line))
250
251 def visit_const(self, node):
252 """
253 Public method to treat a constant node.
254
255 @param node reference to the node (jasy.script.parse.Node.Node)
256 """
257 if (
258 node.type == "const" and
259 node.parent.type == "script" and
260 node.getChildrenLength()
261 ):
262 if self.__stack and self.__stack[-1].endlineno < node[0].line:
263 del self.__stack[-1]
264 if self.__stack:
265 # function variables
266 for var in node:
267 attr = Attribute(self.__module, "const " + var.name,
268 self.__file, var.line)
269 self.__stack[-1]._addattribute(attr)
270 else:
271 # global variable
272 if "@@Globals@@" not in self.__dict:
273 self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase(
274 self.__module, "Globals", self.__file, 0)
275 for var in node:
276 self.__dict["@@Globals@@"]._addglobal(
277 Attribute(self.__module, "const " + var.name,
278 self.__file, var.line))
279
280
281 def readmodule_ex(module, path=None):
282 """
283 Read a JavaScript file and return a dictionary of functions and variables.
284
285 @param module name of the JavaScript file (string)
286 @param path path the file should be searched in (list of strings)
287 @return the resulting dictionary
288 """
289 global _modules
290
291 if module in _modules:
292 # we've seen this file before...
293 return _modules[module]
294
295 # search the path for the file
296 f = None
297 fullpath = [] if path is None else path[:]
298 f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath)
299 if f:
300 f.close()
301 if type not in SUPPORTED_TYPES:
302 # not CORBA IDL source, can't do anything with this module
303 _modules[module] = {}
304 return {}
305
306 try:
307 src = Utilities.readEncodedFile(file)[0]
308 except (UnicodeError, OSError):
309 # can't do anything with this module
310 _modules[module] = {}
311 return {}
312
313 _modules[module] = scan(src, file, module)
314 return _modules[module]
315
316
317 def scan(src, file, module):
318 """
319 Public method to scan the given source text.
320
321 @param src source text to be scanned
322 @type str
323 @param file file name associated with the source text
324 @type str
325 @param module module name associated with the source text
326 @type str
327 @return dictionary containing the extracted data
328 @rtype dict
329 """
330 # convert eol markers the Python style
331 src = src.replace("\r\n", "\n").replace("\r", "\n")
332
333 dictionary = {}
334
335 visitor = Visitor(src, module, file)
336 dictionary = visitor.parse()
337 _modules[module] = dictionary
338 return dictionary

eric ide

mercurial