OLD | NEW |
| (Empty) |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 import itertools | |
6 import keyword | |
7 import symbol | |
8 import token | |
9 | |
10 from catapult_base.refactor.annotated_symbol import base_symbol | |
11 from catapult_base.refactor import snippet | |
12 | |
13 | |
14 __all__ = [ | |
15 'AsName', | |
16 'DottedName', | |
17 'Import', | |
18 'ImportFrom', | |
19 'ImportName', | |
20 ] | |
21 | |
22 | |
23 class DottedName(base_symbol.AnnotatedSymbol): | |
24 @classmethod | |
25 def Annotate(cls, symbol_type, children): | |
26 if symbol_type != symbol.dotted_name: | |
27 return None | |
28 return cls(symbol_type, children) | |
29 | |
30 @property | |
31 def value(self): | |
32 return ''.join(token_snippet.value for token_snippet in self._children) | |
33 | |
34 @value.setter | |
35 def value(self, value): | |
36 value_parts = value.split('.') | |
37 for value_part in value_parts: | |
38 if keyword.iskeyword(value_part): | |
39 raise ValueError('%s is a reserved keyword.' % value_part) | |
40 | |
41 # If we have too many children, cut the list down to size. | |
42 self._children = self._children[:len(value_parts)*2-1] | |
43 | |
44 # Update child nodes. | |
45 for child, value_part in itertools.izip_longest( | |
46 self._children[::2], value_parts): | |
47 if child: | |
48 # Modify existing children. This helps preserve comments and spaces. | |
49 child.value = value_part | |
50 else: | |
51 # Add children as needed. | |
52 self._children.append(snippet.TokenSnippet.Create(token.DOT, '.')) | |
53 self._children.append( | |
54 snippet.TokenSnippet.Create(token.NAME, value_part)) | |
55 | |
56 | |
57 class AsName(base_symbol.AnnotatedSymbol): | |
58 @classmethod | |
59 def Annotate(cls, symbol_type, children): | |
60 if (symbol_type != symbol.dotted_as_name and | |
61 symbol_type != symbol.import_as_name): | |
62 return None | |
63 return cls(symbol_type, children) | |
64 | |
65 @property | |
66 def name(self): | |
67 return self.children[0].value | |
68 | |
69 @name.setter | |
70 def name(self, value): | |
71 self.children[0].value = value | |
72 | |
73 @property | |
74 def alias(self): | |
75 if len(self.children) < 3: | |
76 return None | |
77 return self.children[2].value | |
78 | |
79 @alias.setter | |
80 def alias(self, value): | |
81 if keyword.iskeyword(value): | |
82 raise ValueError('%s is a reserved keyword.' % value) | |
83 | |
84 if value: | |
85 if len(self.children) < 3: | |
86 # If we currently have no alias, add one. | |
87 self.children.append( | |
88 snippet.TokenSnippet.Create(token.NAME, 'as', (0, 1))) | |
89 self.children.append( | |
90 snippet.TokenSnippet.Create(token.NAME, value, (0, 1))) | |
91 else: | |
92 # We already have an alias. Just update the value. | |
93 self.children[2].value = value | |
94 else: | |
95 # Removing the alias. Strip the "as foo". | |
96 self.children = [self.children[0]] | |
97 | |
98 | |
99 class Import(base_symbol.AnnotatedSymbol): | |
100 """An import statement. | |
101 | |
102 Example: | |
103 import a.b.c as d | |
104 from a.b import c as d | |
105 | |
106 In these examples, | |
107 path == 'a.b.c' | |
108 alias == 'd' | |
109 root == 'a.b' (only for "from" imports) | |
110 module == 'c' (only for "from" imports) | |
111 name (read-only) == the name used by references to the module, which is the | |
112 alias if there is one, the full module path in "full" imports, and the | |
113 module name in "from" imports. | |
114 """ | |
115 @property | |
116 def has_from(self): | |
117 """Returns True iff the import statment is of the form "from x import y".""" | |
118 raise NotImplementedError() | |
119 | |
120 @property | |
121 def values(self): | |
122 raise NotImplementedError() | |
123 | |
124 @property | |
125 def paths(self): | |
126 raise NotImplementedError() | |
127 | |
128 @property | |
129 def aliases(self): | |
130 raise NotImplementedError() | |
131 | |
132 @property | |
133 def path(self): | |
134 """The full dotted path of the module.""" | |
135 raise NotImplementedError() | |
136 | |
137 @path.setter | |
138 def path(self, value): | |
139 raise NotImplementedError() | |
140 | |
141 @property | |
142 def alias(self): | |
143 """The alias, if the module is renamed with "as". None otherwise.""" | |
144 raise NotImplementedError() | |
145 | |
146 @alias.setter | |
147 def alias(self, value): | |
148 raise NotImplementedError() | |
149 | |
150 @property | |
151 def name(self): | |
152 """The name used to reference this import's module.""" | |
153 raise NotImplementedError() | |
154 | |
155 | |
156 class ImportName(Import): | |
157 @classmethod | |
158 def Annotate(cls, symbol_type, children): | |
159 if symbol_type != symbol.import_stmt: | |
160 return None | |
161 if children[0].type != symbol.import_name: | |
162 return None | |
163 assert len(children) == 1 | |
164 return cls(symbol_type, children[0].children) | |
165 | |
166 @property | |
167 def has_from(self): | |
168 return False | |
169 | |
170 @property | |
171 def values(self): | |
172 dotted_as_names = self.children[1] | |
173 return tuple((dotted_as_name.name, dotted_as_name.alias) | |
174 for dotted_as_name in dotted_as_names.children[::2]) | |
175 | |
176 @property | |
177 def paths(self): | |
178 return tuple(path for path, _ in self.values) | |
179 | |
180 @property | |
181 def aliases(self): | |
182 return tuple(alias for _, alias in self.values) | |
183 | |
184 @property | |
185 def _dotted_as_name(self): | |
186 dotted_as_names = self.children[1] | |
187 if len(dotted_as_names.children) != 1: | |
188 raise NotImplementedError( | |
189 'This method only works if the statement has one import.') | |
190 return dotted_as_names.children[0] | |
191 | |
192 @property | |
193 def path(self): | |
194 return self._dotted_as_name.name | |
195 | |
196 @path.setter | |
197 def path(self, value): # pylint: disable=arguments-differ | |
198 self._dotted_as_name.name = value | |
199 | |
200 @property | |
201 def alias(self): | |
202 return self._dotted_as_name.alias | |
203 | |
204 @alias.setter | |
205 def alias(self, value): # pylint: disable=arguments-differ | |
206 self._dotted_as_name.alias = value | |
207 | |
208 @property | |
209 def name(self): | |
210 if self.alias: | |
211 return self.alias | |
212 else: | |
213 return self.path | |
214 | |
215 | |
216 class ImportFrom(Import): | |
217 @classmethod | |
218 def Annotate(cls, symbol_type, children): | |
219 if symbol_type != symbol.import_stmt: | |
220 return None | |
221 if children[0].type != symbol.import_from: | |
222 return None | |
223 assert len(children) == 1 | |
224 return cls(symbol_type, children[0].children) | |
225 | |
226 @property | |
227 def has_from(self): | |
228 return True | |
229 | |
230 @property | |
231 def values(self): | |
232 try: | |
233 import_as_names = self.FindChild(symbol.import_as_names) | |
234 except ValueError: | |
235 return (('*', None),) | |
236 | |
237 return tuple((import_as_name.name, import_as_name.alias) | |
238 for import_as_name in import_as_names.children[::2]) | |
239 | |
240 @property | |
241 def paths(self): | |
242 module = self.module | |
243 return tuple('.'.join((module, name)) for name, _ in self.values) | |
244 | |
245 @property | |
246 def aliases(self): | |
247 return tuple(alias for _, alias in self.values) | |
248 | |
249 @property | |
250 def root(self): | |
251 return self.FindChild(symbol.dotted_name).value | |
252 | |
253 @root.setter | |
254 def root(self, value): | |
255 self.FindChild(symbol.dotted_name).value = value | |
256 | |
257 @property | |
258 def _import_as_name(self): | |
259 try: | |
260 import_as_names = self.FindChild(symbol.import_as_names) | |
261 except ValueError: | |
262 return None | |
263 | |
264 if len(import_as_names.children) != 1: | |
265 raise NotImplementedError( | |
266 'This method only works if the statement has one import.') | |
267 | |
268 return import_as_names.children[0] | |
269 | |
270 @property | |
271 def module(self): | |
272 import_as_name = self._import_as_name | |
273 if import_as_name: | |
274 return import_as_name.name | |
275 else: | |
276 return '*' | |
277 | |
278 @module.setter | |
279 def module(self, value): | |
280 if keyword.iskeyword(value): | |
281 raise ValueError('%s is a reserved keyword.' % value) | |
282 | |
283 import_as_name = self._import_as_name | |
284 if value == '*': | |
285 # TODO: Implement this. | |
286 raise NotImplementedError() | |
287 else: | |
288 if import_as_name: | |
289 import_as_name.name = value | |
290 else: | |
291 # TODO: Implement this. | |
292 raise NotImplementedError() | |
293 | |
294 @property | |
295 def path(self): | |
296 return '.'.join((self.root, self.module)) | |
297 | |
298 @path.setter | |
299 def path(self, value): # pylint: disable=arguments-differ | |
300 self.root, _, self.module = value.rpartition('.') | |
301 | |
302 @property | |
303 def alias(self): | |
304 import_as_name = self._import_as_name | |
305 if import_as_name: | |
306 return import_as_name.alias | |
307 else: | |
308 return None | |
309 | |
310 @alias.setter | |
311 def alias(self, value): # pylint: disable=arguments-differ | |
312 import_as_name = self._import_as_name | |
313 if not import_as_name: | |
314 raise NotImplementedError('Cannot change alias for "import *".') | |
315 import_as_name.alias = value | |
316 | |
317 @property | |
318 def name(self): | |
319 if self.alias: | |
320 return self.alias | |
321 else: | |
322 return self.module | |
OLD | NEW |