ThirdParty/Jasy/jasy/script/util/__init__.py

changeset 6650
1dd52aa8897c
parent 6645
ad476851d7e0
equal deleted inserted replaced
6649:f1b3a73831c9 6650:1dd52aa8897c
1 #
2 # Jasy - Web Tooling Framework
3 # Copyright 2010-2012 Zynga Inc.
4 # Copyright 2013-2014 Sebastian Werner
5 #
6
7 from __future__ import unicode_literals
8
9 from jasy.script.output.Compressor import Compressor
10
11 # Shared instance
12 compressor = Compressor()
13
14 pseudoTypes = set(["any", "var", "undefined", "null", "true", "false", "this", "arguments"])
15 builtinTypes = set(["Object", "String", "Number", "Boolean", "Array", "Function", "RegExp", "Date"])
16
17 # Basic user friendly node type to human type
18 nodeTypeToDocType = {
19
20 # Primitives
21 "string": "String",
22 "number": "Number",
23 "not": "Boolean",
24 "true": "Boolean",
25 "false": "Boolean",
26
27 # Literals
28 "function": "Function",
29 "regexp": "RegExp",
30 "object_init": "Map",
31 "array_init": "Array",
32
33 # We could figure out the real class automatically - at least that's the case quite often
34 "new": "Object",
35 "new_with_args": "Object",
36
37 # Comparisons
38 "eq" : "Boolean",
39 "ne" : "Boolean",
40 "strict_eq" : "Boolean",
41 "strict_ne" : "Boolean",
42 "lt" : "Boolean",
43 "le" : "Boolean",
44 "gt" : "Boolean",
45 "ge" : "Boolean",
46 "in" : "Boolean",
47 "instanceof" : "Boolean",
48
49 # Numbers
50 "lsh": "Number",
51 "rsh": "Number",
52 "ursh": "Number",
53 "minus": "Number",
54 "mul": "Number",
55 "div": "Number",
56 "mod": "Number",
57 "bitwise_and": "Number",
58 "bitwise_xor": "Number",
59 "bitwise_or": "Number",
60 "bitwise_not": "Number",
61 "increment": "Number",
62 "decrement": "Number",
63 "unary_minus": "Number",
64 "unary_plus": "Number",
65
66 # This is not 100% correct, but I don't like to introduce a BooleanLike type.
67 # If the author likes something different he is still able to override it via API docs
68 "and": "Boolean",
69 "or": "Boolean",
70
71 # Operators/Built-ins
72 "void": "undefined",
73 "null": "null",
74 "typeof": "String",
75 "delete": "Boolean",
76 "this": "This",
77
78 # These are not real types, we try to figure out the real value behind automatically
79 "call": "Call",
80 "hook": "Hook",
81 "assign": "Assign",
82 "plus": "Plus",
83 "identifier" : "Identifier",
84 "dot": "Object",
85 "index": "var"
86 }
87
88
89 def getVisibility(name):
90 """
91 Returns the visibility of the given name by convention
92 """
93
94 if name.startswith("__"):
95 return "private"
96 elif name.startswith("_"):
97 return "internal"
98 else:
99 return "public"
100
101
102 def requiresDocumentation(name):
103 """
104 Whether the given name suggests that documentation is required
105 """
106
107 return not name.startswith("_")
108
109
110 def getKeyValue(dict, key):
111 """
112 Returns the value node of the given key inside the given object initializer.
113 """
114
115 for propertyInit in dict:
116 if propertyInit[0].value == key:
117 return propertyInit[1]
118
119
120 def findAssignments(name, node):
121 """
122 Returns a list of assignments which might have impact on the value used in the given node.
123 """
124
125 # Looking for all script blocks
126 scripts = []
127 parent = node
128 while parent:
129 if parent.type == "script":
130 scope = getattr(parent, "scope", None)
131 if scope and name in scope.modified:
132 scripts.append(parent)
133
134 parent = getattr(parent, "parent", None)
135
136 def assignMatcher(node):
137 if node.type == "assign" and node[0].type == "identifier" and node[0].value == name:
138 return True
139
140 if node.type == "declaration" and node.name == name and getattr(node, "initializer", None):
141 return True
142
143 if node.type == "function" and node.functionForm == "declared_form" and node.name == name:
144 return True
145
146 return False
147
148 # Query all relevant script nodes
149 assignments = []
150 for script in scripts:
151 queryResult = queryAll(script, assignMatcher, False)
152 assignments.extend(queryResult)
153
154 # Collect assigned values
155 values = []
156 for assignment in assignments:
157 if assignment.type == "function":
158 values.append(assignment)
159 elif assignment.type == "assign":
160 values.append(assignment[1])
161 else:
162 values.append(assignment.initializer)
163
164 return assignments, values
165
166
167 def findFunction(node):
168 """
169 Returns the first function inside the given node
170 """
171
172 return query(node, lambda node: node.type == "function")
173
174
175 def findCommentNode(node):
176 """
177 Finds the first doc comment node inside the given node
178 """
179
180 def matcher(node):
181 comments = getattr(node, "comments", None)
182 if comments:
183 for comment in comments:
184 if comment.variant == "doc":
185 return True
186
187 return query(node, matcher)
188
189
190 def getDocComment(node):
191 """
192 Returns the first doc comment of the given node.
193 """
194
195 comments = getattr(node, "comments", None)
196 if comments:
197 for comment in comments:
198 if comment.variant == "doc":
199 return comment
200
201 return None
202
203
204 def findReturn(node):
205 """
206 Finds the first return inside the given node
207 """
208
209 return query(node, lambda node: node.type == "return", True)
210
211
212
213 def valueToString(node):
214 """
215 Converts the value of the given node into something human friendly
216 """
217
218 if node.type in ("number", "string", "false", "true", "regexp", "null"):
219 return compressor.compress(node)
220 elif node.type in nodeTypeToDocType:
221 if node.type == "plus":
222 return detectPlusType(node)
223 elif node.type in ("new", "new_with_args", "dot"):
224 return detectObjectType(node)
225 else:
226 return nodeTypeToDocType[node.type]
227 else:
228 return "Other"
229
230
231
232 def queryAll(node, matcher, deep=True, inner=False, result=None):
233 """
234 Recurses the tree starting with the given node and returns a list of nodes
235 matched by the given matcher method
236
237 - node: any node
238 - matcher: function which should return a truish value when node matches
239 - deep: whether inner scopes should be scanned, too
240 - inner: used internally to differentiate between current and inner nodes
241 - result: can be used to extend an existing list, otherwise a new list is created and returned
242 """
243
244 if result == None:
245 result = []
246
247 # Don't do in closure functions
248 if inner and node.type == "script" and not deep:
249 return None
250
251 if matcher(node):
252 result.append(node)
253
254 for child in node:
255 queryAll(child, matcher, deep, True, result)
256
257 return result
258
259
260
261 def query(node, matcher, deep=True, inner=False):
262 """
263 Recurses the tree starting with the given node and returns the first node
264 which is matched by the given matcher method.
265
266 - node: any node
267 - matcher: function which should return a truish value when node matches
268 - deep: whether inner scopes should be scanned, too
269 - inner: used internally to differentiate between current and inner nodes
270 """
271
272 # Don't do in closure functions
273 if inner and node.type == "script" and not deep:
274 return None
275
276 if matcher(node):
277 return node
278
279 for child in node:
280 result = query(child, matcher, deep, True)
281 if result is not None:
282 return result
283
284 return None
285
286
287 def findCall(node, methodName):
288 """
289 Recurses the tree starting with the given node and returns the first node
290 which calls the given method name (supports namespaces, too)
291 """
292
293 if type(methodName) is str:
294 methodName = set([methodName])
295
296 def matcher(node):
297 call = getCallName(node)
298 if call and call in methodName:
299 return call
300
301 return query(node, matcher)
302
303
304 def getCallName(node):
305 if node.type == "call":
306 if node[0].type == "dot":
307 return assembleDot(node[0])
308 elif node[0].type == "identifier":
309 return node[0].value
310
311 return None
312
313
314 def getParameterFromCall(call, index=0):
315 """
316 Returns a parameter node by index on the call node
317 """
318
319 try:
320 return call[1][index]
321 except:
322 return None
323
324
325 def getParamNamesFromFunction(func):
326 """
327 Returns a human readable list of parameter names (sorted by their order in the given function)
328 """
329
330 params = getattr(func, "params", None)
331 if params:
332 return [identifier.value for identifier in params]
333 else:
334 return None
335
336
337 def detectPlusType(plusNode):
338 """
339 Analyses the given "plus" node and tries to figure out if a "string" or "number" result is produced.
340 """
341
342 if plusNode[0].type == "string" or plusNode[1].type == "string":
343 return "String"
344 elif plusNode[0].type == "number" and plusNode[1].type == "number":
345 return "Number"
346 elif plusNode[0].type == "plus" and detectPlusType(plusNode[0]) == "String":
347 return "String"
348 else:
349 return "var"
350
351
352 def detectObjectType(objectNode):
353 """
354 Returns a human readable type information of the given node
355 """
356
357 if objectNode.type in ("new", "new_with_args"):
358 construct = objectNode[0]
359 else:
360 construct = objectNode
361
362 # Only support built-in top level constructs
363 if construct.type == "identifier" and construct.value in ("Array", "Boolean", "Date", "Function", "Number", "Object", "String", "RegExp"):
364 return construct.value
365
366 # And namespaced custom classes
367 elif construct.type == "dot":
368 assembled = assembleDot(construct)
369 if assembled:
370 return assembled
371
372 return "Object"
373
374
375
376 def resolveIdentifierNode(identifierNode):
377 assignNodes, assignValues = findAssignments(identifierNode.value, identifierNode)
378 if assignNodes:
379
380 assignCommentNode = None
381
382 # Find first relevant assignment with comment! Otherwise just first one.
383 for assign in assignNodes:
384
385 # The parent is the relevant doc comment container
386 # It's either a "var" (declaration) or "semicolon" (assignment)
387 if getDocComment(assign):
388 assignCommentNode = assign
389 break
390 elif getDocComment(assign.parent):
391 assignCommentNode = assign.parent
392 break
393
394 return assignValues[0], assignCommentNode or assignValues[0]
395
396 return None, None
397
398
399
400 def assembleDot(node, result=None):
401 """
402 Joins a dot node (cascaded supported, too) into a single string like "foo.bar.Baz"
403 """
404
405 if result == None:
406 result = []
407
408 for child in node:
409 if child.type == "identifier":
410 result.append(child.value)
411 elif child.type == "dot":
412 assembleDot(child, result)
413 else:
414 return None
415
416 return ".".join(result)

eric ide

mercurial