src/eric7/Utilities/ClassBrowsers/jsclbr.py

branch
eric7
changeset 9955
aa02420279fe
parent 9954
7c5fa3eef082
child 9956
5b138f996a1e
equal deleted inserted replaced
9954:7c5fa3eef082 9955:aa02420279fe
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2023 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 from eric7 import Utilities
16 from eric7.Utilities import ClassBrowsers
17
18 from . import ClbrBaseClasses
19
20 SUPPORTED_TYPES = [ClassBrowsers.JS_SOURCE]
21
22
23 class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase):
24 """
25 Mixin class implementing the notion of visibility.
26 """
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
45 def __init__(self, module, name, file, lineno, signature="", 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__(
57 self, module, name, file, lineno, signature, separator
58 )
59 VisibilityMixin.__init__(self)
60
61
62 class Attribute(ClbrBaseClasses.Attribute, VisibilityMixin):
63 """
64 Class to represent a class attribute.
65 """
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:
81 """
82 Class implementing a visitor going through the parsed tree.
83 """
84
85 def __init__(self, src, module, filename):
86 """
87 Constructor
88
89 @param src source to be parsed (string)
90 @param module name of the module (string)
91 @param filename file name (string)
92 """
93 self.__dict = {}
94 self.__dict_counts = {}
95 self.__root = None
96 self.__stack = []
97
98 self.__module = module
99 self.__file = filename
100 self.__source = src
101
102 # normalize line endings
103 self.__source = self.__source.replace("\r\n", "\n").replace("\r", "\n")
104
105 # ensure source ends with an eol
106 if bool(self.__source) and self.__source[-1] != "\n":
107 self.__source = self.__source + "\n"
108
109 def parse(self):
110 """
111 Public method to parse the source.
112
113 @return dictionary containing the parsed information
114 """
115 try:
116 self.__root = jsParser.parse(self.__source, self.__file)
117 self.__visit(self.__root)
118 except jsParser.SyntaxError:
119 # ignore syntax errors of the parser
120 pass
121 except jsTokenizer.ParseError:
122 # ignore syntax errors of the tokenizer
123 pass
124
125 return self.__dict
126
127 def __visit(self, root):
128 """
129 Private method implementing the visit logic delegating to interesting
130 methods.
131
132 @param root root node to visit
133 """
134
135 def call(n):
136 getattr(self, "visit_{0}".format(n.type), self.visit_noop)(n)
137
138 call(root)
139 for node in root:
140 self.__visit(node)
141
142 def visit_noop(self, node):
143 """
144 Public method to ignore the given node.
145
146 @param node reference to the node (jasy.script.parse.Node.Node)
147 """
148 pass
149
150 def visit_function(self, node):
151 """
152 Public method to treat a function node.
153
154 @param node reference to the node (jasy.script.parse.Node.Node)
155 """
156 if (
157 node.type == "function"
158 and getattr(node, "name", None)
159 and node.functionForm == "declared_form"
160 ):
161 if self.__stack and self.__stack[-1].endlineno < node.line:
162 del self.__stack[-1]
163 endline = node.line + self.__source.count("\n", node.start, node.end)
164 if getattr(node, "params", None):
165 func_sig = ", ".join([p.value for p in node.params])
166 else:
167 func_sig = ""
168 if self.__stack:
169 # it's a nested function
170 cur_func = self.__stack[-1]
171 f = Function(None, node.name, self.__file, node.line, func_sig)
172 f.setEndLine(endline)
173 cur_func._addmethod(node.name, f)
174 else:
175 f = Function(self.__module, node.name, 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 )
183 else:
184 self.__dict_counts[func_name] = 0
185 self.__dict[func_name] = f
186 self.__stack.append(f)
187
188 def visit_property_init(self, node):
189 """
190 Public method to treat a property_init node.
191
192 @param node reference to the node (jasy.script.parse.Node.Node)
193 """
194 if node.type == "property_init" and node[1].type == "function":
195 if self.__stack and self.__stack[-1].endlineno < node[0].line:
196 del self.__stack[-1]
197 endline = node[0].line + self.__source.count("\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, self.__file, node[0].line, func_sig)
206 f.setEndLine(endline)
207 cur_func._addmethod(node[0].value, f)
208 else:
209 f = Function(
210 self.__module, node[0].value, self.__file, node[0].line, func_sig
211 )
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 )
219 else:
220 self.__dict_counts[func_name] = 0
221 self.__dict[func_name] = f
222 self.__stack.append(f)
223
224 def visit_var(self, node):
225 """
226 Public method to treat a variable node.
227
228 @param node reference to the node (jasy.script.parse.Node.Node)
229 """
230 if (
231 node.type == "var"
232 and node.parent.type == "script"
233 and node.getChildrenLength()
234 ):
235 if self.__stack and self.__stack[-1].endlineno < node[0].line:
236 del self.__stack[-1]
237 if self.__stack:
238 # function variables
239 for var in node:
240 attr = Attribute(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 )
248 for var in node:
249 self.__dict["@@Globals@@"]._addglobal(
250 Attribute(self.__module, var.name, self.__file, var.line)
251 )
252
253 def visit_const(self, node):
254 """
255 Public method to treat a constant node.
256
257 @param node reference to the node (jasy.script.parse.Node.Node)
258 """
259 if (
260 node.type == "const"
261 and node.parent.type == "script"
262 and node.getChildrenLength()
263 ):
264 if self.__stack and self.__stack[-1].endlineno < node[0].line:
265 del self.__stack[-1]
266 if self.__stack:
267 # function variables
268 for var in node:
269 attr = Attribute(
270 self.__module, "const " + var.name, self.__file, var.line
271 )
272 self.__stack[-1]._addattribute(attr)
273 else:
274 # global variable
275 if "@@Globals@@" not in self.__dict:
276 self.__dict["@@Globals@@"] = ClbrBaseClasses.ClbrBase(
277 self.__module, "Globals", self.__file, 0
278 )
279 for var in node:
280 self.__dict["@@Globals@@"]._addglobal(
281 Attribute(
282 self.__module, "const " + var.name, self.__file, var.line
283 )
284 )
285
286
287 def readmodule_ex(module, path=None, isTypeFile=False):
288 """
289 Read a JavaScript file and return a dictionary of functions and variables.
290
291 @param module name of the JavaScript file
292 @type str
293 @param path path the file should be searched in
294 @type list of str
295 @param isTypeFile flag indicating a file of this type
296 @type bool
297 @return the resulting dictionary
298 @rtype dict
299 """
300 # search the path for the file
301 f = None
302 fullpath = [] if path is None else path[:]
303 f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath)
304 if f:
305 f.close()
306 if type not in SUPPORTED_TYPES:
307 # not Javascript source, can't do anything with this module
308 return {}
309
310 try:
311 src = Utilities.readEncodedFile(file)[0]
312 except (UnicodeError, OSError):
313 # can't do anything with this module
314 return {}
315
316 return scan(src, file, module)
317
318
319 def scan(src, file, module):
320 """
321 Public method to scan the given source text.
322
323 @param src source text to be scanned
324 @type str
325 @param file file name associated with the source text
326 @type str
327 @param module module name associated with the source text
328 @type str
329 @return dictionary containing the extracted data
330 @rtype dict
331 """
332 # convert eol markers the Python style
333 src = src.replace("\r\n", "\n").replace("\r", "\n")
334
335 dictionary = {}
336
337 visitor = Visitor(src, module, file)
338 dictionary = visitor.parse()
339 return dictionary

eric ide

mercurial