16 |
16 |
17 class InvalidModulePath(Exception): |
17 class InvalidModulePath(Exception): |
18 """ |
18 """ |
19 Class defining an exception for invalid module paths. |
19 Class defining an exception for invalid module paths. |
20 """ |
20 """ |
|
21 |
21 pass |
22 pass |
22 |
23 |
23 |
24 |
24 def getModuleQualnameFromPath(path): |
25 def getModuleQualnameFromPath(path): |
25 """ |
26 """ |
26 Function to get the module's qualified name by analysis of the |
27 Function to get the module's qualified name by analysis of the |
27 path. |
28 path. |
28 |
29 |
29 Resolve the absolute pathname and eliminate symlinks. This could result |
30 Resolve the absolute pathname and eliminate symlinks. This could result |
30 in an incorrect name if symlinks are used to restructure the python lib |
31 in an incorrect name if symlinks are used to restructure the python lib |
31 directory. |
32 directory. |
32 |
33 |
33 Starting from the right-most directory component look for __init__.py |
34 Starting from the right-most directory component look for __init__.py |
34 in the directory component. If it exists then the directory name is |
35 in the directory component. If it exists then the directory name is |
35 part of the module name. Move left to the subsequent directory |
36 part of the module name. Move left to the subsequent directory |
36 components until a directory is found without __init__.py. |
37 components until a directory is found without __init__.py. |
37 |
38 |
38 @param path path of the module to be analyzed |
39 @param path path of the module to be analyzed |
39 @type str |
40 @type str |
40 @return qualified name of the module |
41 @return qualified name of the module |
41 @rtype str |
42 @rtype str |
42 @exception InvalidModulePath raised to indicate an invalid module path |
43 @exception InvalidModulePath raised to indicate an invalid module path |
43 """ |
44 """ |
44 (head, tail) = os.path.split(path) |
45 (head, tail) = os.path.split(path) |
45 if head == '' or tail == '': |
46 if head == "" or tail == "": |
46 raise InvalidModulePath('Invalid python file path: "{0}"' |
47 raise InvalidModulePath( |
47 ' Missing path or file name'.format(path)) |
48 'Invalid python file path: "{0}"' " Missing path or file name".format(path) |
48 |
49 ) |
|
50 |
49 qname = [os.path.splitext(tail)[0]] |
51 qname = [os.path.splitext(tail)[0]] |
50 while head not in ['/', '.', '']: |
52 while head not in ["/", ".", ""]: |
51 if os.path.isfile(os.path.join(head, '__init__.py')): |
53 if os.path.isfile(os.path.join(head, "__init__.py")): |
52 (head, tail) = os.path.split(head) |
54 (head, tail) = os.path.split(head) |
53 qname.insert(0, tail) |
55 qname.insert(0, tail) |
54 else: |
56 else: |
55 break |
57 break |
56 |
58 |
57 qualname = '.'.join(qname) |
59 qualname = ".".join(qname) |
58 return qualname |
60 return qualname |
59 |
61 |
60 |
62 |
61 def namespacePathJoin(namespace, name): |
63 def namespacePathJoin(namespace, name): |
62 """ |
64 """ |
63 Function to extend a given namespace path. |
65 Function to extend a given namespace path. |
64 |
66 |
65 @param namespace namespace to be extended |
67 @param namespace namespace to be extended |
66 @type str |
68 @type str |
67 @param name node name to be appended |
69 @param name node name to be appended |
68 @type str |
70 @type str |
69 @return extended namespace |
71 @return extended namespace |
110 if isinstance(node, ast.Name): |
112 if isinstance(node, ast.Name): |
111 if node.id in aliases: |
113 if node.id in aliases: |
112 return aliases[node.id] |
114 return aliases[node.id] |
113 return node.id |
115 return node.id |
114 elif isinstance(node, ast.Attribute): |
116 elif isinstance(node, ast.Attribute): |
115 name = "{0}.{1}".format(getAttrQualName(node.value, aliases), |
117 name = "{0}.{1}".format(getAttrQualName(node.value, aliases), node.attr) |
116 node.attr) |
|
117 if name in aliases: |
118 if name in aliases: |
118 return aliases[name] |
119 return aliases[name] |
119 return name |
120 return name |
120 else: |
121 else: |
121 return "" |
122 return "" |
122 |
123 |
123 |
124 |
124 def getCallName(node, aliases): |
125 def getCallName(node, aliases): |
125 """ |
126 """ |
126 Function to extract the call name from an ast.Call node. |
127 Function to extract the call name from an ast.Call node. |
127 |
128 |
128 @param node node to extract information from |
129 @param node node to extract information from |
129 @type ast.Call |
130 @type ast.Call |
130 @param aliases dictionary of import aliases |
131 @param aliases dictionary of import aliases |
131 @type dict |
132 @type dict |
132 @return name of the ast.Call node |
133 @return name of the ast.Call node |
133 @rtype str |
134 @rtype str |
134 """ |
135 """ |
135 if isinstance(node.func, ast.Name): |
136 if isinstance(node.func, ast.Name): |
136 if deepgetattr(node, 'func.id') in aliases: |
137 if deepgetattr(node, "func.id") in aliases: |
137 return aliases[deepgetattr(node, 'func.id')] |
138 return aliases[deepgetattr(node, "func.id")] |
138 return deepgetattr(node, 'func.id') |
139 return deepgetattr(node, "func.id") |
139 elif isinstance(node.func, ast.Attribute): |
140 elif isinstance(node.func, ast.Attribute): |
140 return getAttrQualName(node.func, aliases) |
141 return getAttrQualName(node.func, aliases) |
141 else: |
142 else: |
142 return "" |
143 return "" |
143 |
144 |
144 |
145 |
145 def getQualAttr(node, aliases): |
146 def getQualAttr(node, aliases): |
146 """ |
147 """ |
147 Function to extract the qualified name from an ast.Attribute node. |
148 Function to extract the qualified name from an ast.Attribute node. |
148 |
149 |
149 @param node node to extract information from |
150 @param node node to extract information from |
150 @type ast.Attribute |
151 @type ast.Attribute |
151 @param aliases dictionary of import aliases |
152 @param aliases dictionary of import aliases |
152 @type dict |
153 @type dict |
153 @return qualified attribute name |
154 @return qualified attribute name |
154 @rtype str |
155 @rtype str |
155 """ |
156 """ |
156 prefix = "" |
157 prefix = "" |
157 if isinstance(node, ast.Attribute): |
158 if isinstance(node, ast.Attribute): |
158 with contextlib.suppress(Exception): |
159 with contextlib.suppress(Exception): |
159 val = deepgetattr(node, 'value.id') |
160 val = deepgetattr(node, "value.id") |
160 prefix = ( |
161 prefix = aliases[val] if val in aliases else deepgetattr(node, "value.id") |
161 aliases[val] if val in aliases |
|
162 else deepgetattr(node, 'value.id') |
|
163 ) |
|
164 # Id we can't get the fully qualified name for an attr, just return |
162 # Id we can't get the fully qualified name for an attr, just return |
165 # its base name. |
163 # its base name. |
166 |
164 |
167 return "{0}.{1}".format(prefix, node.attr) |
165 return "{0}.{1}".format(prefix, node.attr) |
168 else: |
166 else: |
169 return "" |
167 return "" |
170 |
168 |
171 |
169 |
172 def deepgetattr(obj, attr): |
170 def deepgetattr(obj, attr): |
173 """ |
171 """ |
174 Function to recurs through an attribute chain to get the ultimate value. |
172 Function to recurs through an attribute chain to get the ultimate value. |
175 |
173 |
176 @param obj reference to the object to be recursed |
174 @param obj reference to the object to be recursed |
177 @type ast.Name or ast.Attribute |
175 @type ast.Name or ast.Attribute |
178 @param attr attribute chain to be parsed |
176 @param attr attribute chain to be parsed |
179 @type ast.Attribute |
177 @type ast.Attribute |
180 @return ultimate value |
178 @return ultimate value |
181 @rtype ast.AST |
179 @rtype ast.AST |
182 """ |
180 """ |
183 for key in attr.split('.'): |
181 for key in attr.split("."): |
184 obj = getattr(obj, key) |
182 obj = getattr(obj, key) |
185 return obj |
183 return obj |
186 |
184 |
187 |
185 |
188 def linerange(node): |
186 def linerange(node): |
189 """ |
187 """ |
190 Function to get line number range from a node. |
188 Function to get line number range from a node. |
191 |
189 |
192 @param node node to extract a line range from |
190 @param node node to extract a line range from |
193 @type ast.AST |
191 @type ast.AST |
194 @return list containing the line number range |
192 @return list containing the line number range |
195 @rtype list of int |
193 @rtype list of int |
196 """ |
194 """ |
197 strip = { |
195 strip = {"body": None, "orelse": None, "handlers": None, "finalbody": None} |
198 "body": None, |
|
199 "orelse": None, |
|
200 "handlers": None, |
|
201 "finalbody": None |
|
202 } |
|
203 for key in strip: |
196 for key in strip: |
204 if hasattr(node, key): |
197 if hasattr(node, key): |
205 strip[key] = getattr(node, key) |
198 strip[key] = getattr(node, key) |
206 node.key = [] |
199 node.key = [] |
207 |
200 |
208 lines_min = 9999999999 |
201 lines_min = 9999999999 |
209 lines_max = -1 |
202 lines_max = -1 |
210 for n in ast.walk(node): |
203 for n in ast.walk(node): |
211 if hasattr(n, 'lineno'): |
204 if hasattr(n, "lineno"): |
212 lines_min = min(lines_min, n.lineno) |
205 lines_min = min(lines_min, n.lineno) |
213 lines_max = max(lines_max, n.lineno) |
206 lines_max = max(lines_max, n.lineno) |
214 |
207 |
215 for key in strip: |
208 for key in strip: |
216 if strip[key] is not None: |
209 if strip[key] is not None: |
217 node.key = strip[key] |
210 node.key = strip[key] |
218 |
211 |
219 if lines_max > -1: |
212 if lines_max > -1: |
220 return list(range(lines_min, lines_max + 1)) |
213 return list(range(lines_min, lines_max + 1)) |
221 |
214 |
222 return [0, 1] |
215 return [0, 1] |
223 |
216 |
224 |
217 |
225 def linerange_fix(node): |
218 def linerange_fix(node): |
226 """ |
219 """ |
227 Function to get a line number range working around a known Python bug |
220 Function to get a line number range working around a known Python bug |
228 with multi-line strings. |
221 with multi-line strings. |
229 |
222 |
230 @param node node to extract a line range from |
223 @param node node to extract a line range from |
231 @type ast.AST |
224 @type ast.AST |
232 @return list containing the line number range |
225 @return list containing the line number range |
233 @rtype list of int |
226 @rtype list of int |
234 """ |
227 """ |
235 # deal with multiline strings lineno behavior (Python issue #16806) |
228 # deal with multiline strings lineno behavior (Python issue #16806) |
236 lines = linerange(node) |
229 lines = linerange(node) |
237 if ( |
230 if hasattr(node, "_securitySibling") and hasattr(node._securitySibling, "lineno"): |
238 hasattr(node, '_securitySibling') and |
|
239 hasattr(node._securitySibling, 'lineno') |
|
240 ): |
|
241 start = min(lines) |
231 start = min(lines) |
242 delta = node._securitySibling.lineno - start |
232 delta = node._securitySibling.lineno - start |
243 if delta > 1: |
233 if delta > 1: |
244 return list(range(start, node._securitySibling.lineno)) |
234 return list(range(start, node._securitySibling.lineno)) |
245 |
235 |
246 return lines |
236 return lines |
247 |
237 |
248 |
238 |
249 def escapedBytesRepresentation(b): |
239 def escapedBytesRepresentation(b): |
250 """ |
240 """ |
251 Function to escape bytes for comparison with other strings. |
241 Function to escape bytes for comparison with other strings. |
252 |
242 |
253 In practice it turns control characters into acceptable codepoints then |
243 In practice it turns control characters into acceptable codepoints then |
254 encodes them into bytes again to turn unprintable bytes into printable |
244 encodes them into bytes again to turn unprintable bytes into printable |
255 escape sequences. |
245 escape sequences. |
256 |
246 |
257 This is safe to do for the whole range 0..255 and result matches |
247 This is safe to do for the whole range 0..255 and result matches |
258 unicode_escape on a unicode string. |
248 unicode_escape on a unicode string. |
259 |
249 |
260 @param b bytes object to be escaped |
250 @param b bytes object to be escaped |
261 @type bytes |
251 @type bytes |
262 @return escaped bytes object |
252 @return escaped bytes object |
263 @rtype bytes |
253 @rtype bytes |
264 """ |
254 """ |
265 return b.decode('unicode_escape').encode('unicode_escape') |
255 return b.decode("unicode_escape").encode("unicode_escape") |
266 |
256 |
267 |
257 |
268 def concatString(node, stop=None): |
258 def concatString(node, stop=None): |
269 """ |
259 """ |
270 Function to build a string from an ast.BinOp chain. |
260 Function to build a string from an ast.BinOp chain. |
271 |
261 |
272 This will build a string from a series of ast.Str/ast.Constant nodes |
262 This will build a string from a series of ast.Str/ast.Constant nodes |
273 wrapped in ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val |
263 wrapped in ast.BinOp nodes. Something like "a" + "b" + "c" or "a %s" % val |
274 etc. The provided node can be any participant in the BinOp chain. |
264 etc. The provided node can be any participant in the BinOp chain. |
275 |
265 |
276 @param node node to be processed |
266 @param node node to be processed |
277 @type ast.BinOp or ast.Str/ast.Constant |
267 @type ast.BinOp or ast.Str/ast.Constant |
278 @param stop base node to stop at |
268 @param stop base node to stop at |
279 @type ast.BinOp or ast.Str/ast.Constant |
269 @type ast.BinOp or ast.Str/ast.Constant |
280 @return tuple containing the root node of the expression and the string |
270 @return tuple containing the root node of the expression and the string |
281 value |
271 value |
282 @rtype tuple of (ast.AST, str) |
272 @rtype tuple of (ast.AST, str) |
283 """ |
273 """ |
|
274 |
284 def _get(node, bits, stop=None): |
275 def _get(node, bits, stop=None): |
285 if node != stop: |
276 if node != stop: |
286 bits.append( |
277 bits.append( |
287 _get(node.left, bits, stop) |
278 _get(node.left, bits, stop) |
288 if isinstance(node.left, ast.BinOp) |
279 if isinstance(node.left, ast.BinOp) |