Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(132)

Side by Side Diff: third_party/closure_linter/closure_linter/tokenutil.py

Issue 2592193002: Remove closure_linter from Chrome (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright 2007 The Closure Linter Authors. All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS-IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 """Token utility functions."""
18
19 __author__ = ('robbyw@google.com (Robert Walker)',
20 'ajp@google.com (Andy Perelson)')
21
22 import copy
23 import StringIO
24
25 from closure_linter.common import tokens
26 from closure_linter.javascripttokens import JavaScriptToken
27 from closure_linter.javascripttokens import JavaScriptTokenType
28
29 # Shorthand
30 Type = tokens.TokenType
31
32
33 def GetFirstTokenInSameLine(token):
34 """Returns the first token in the same line as token.
35
36 Args:
37 token: Any token in the line.
38
39 Returns:
40 The first token in the same line as token.
41 """
42 while not token.IsFirstInLine():
43 token = token.previous
44 return token
45
46
47 def GetFirstTokenInPreviousLine(token):
48 """Returns the first token in the previous line as token.
49
50 Args:
51 token: Any token in the line.
52
53 Returns:
54 The first token in the previous line as token, or None if token is on the
55 first line.
56 """
57 first_in_line = GetFirstTokenInSameLine(token)
58 if first_in_line.previous:
59 return GetFirstTokenInSameLine(first_in_line.previous)
60
61 return None
62
63
64 def GetLastTokenInSameLine(token):
65 """Returns the last token in the same line as token.
66
67 Args:
68 token: Any token in the line.
69
70 Returns:
71 The last token in the same line as token.
72 """
73 while not token.IsLastInLine():
74 token = token.next
75 return token
76
77
78 def GetAllTokensInSameLine(token):
79 """Returns all tokens in the same line as the given token.
80
81 Args:
82 token: Any token in the line.
83
84 Returns:
85 All tokens on the same line as the given token.
86 """
87 first_token = GetFirstTokenInSameLine(token)
88 last_token = GetLastTokenInSameLine(token)
89
90 tokens_in_line = []
91 while first_token != last_token:
92 tokens_in_line.append(first_token)
93 first_token = first_token.next
94 tokens_in_line.append(last_token)
95
96 return tokens_in_line
97
98
99 def CustomSearch(start_token, func, end_func=None, distance=None,
100 reverse=False):
101 """Returns the first token where func is True within distance of this token.
102
103 Args:
104 start_token: The token to start searching from
105 func: The function to call to test a token for applicability
106 end_func: The function to call to test a token to determine whether to abort
107 the search.
108 distance: The number of tokens to look through before failing search. Must
109 be positive. If unspecified, will search until the end of the token
110 chain
111 reverse: When true, search the tokens before this one instead of the tokens
112 after it
113
114 Returns:
115 The first token matching func within distance of this token, or None if no
116 such token is found.
117 """
118 token = start_token
119 if reverse:
120 while token and (distance is None or distance > 0):
121 previous = token.previous
122 if previous:
123 if func(previous):
124 return previous
125 if end_func and end_func(previous):
126 return None
127
128 token = previous
129 if distance is not None:
130 distance -= 1
131
132 else:
133 while token and (distance is None or distance > 0):
134 next_token = token.next
135 if next_token:
136 if func(next_token):
137 return next_token
138 if end_func and end_func(next_token):
139 return None
140
141 token = next_token
142 if distance is not None:
143 distance -= 1
144
145 return None
146
147
148 def Search(start_token, token_types, distance=None, reverse=False):
149 """Returns the first token of type in token_types within distance.
150
151 Args:
152 start_token: The token to start searching from
153 token_types: The allowable types of the token being searched for
154 distance: The number of tokens to look through before failing search. Must
155 be positive. If unspecified, will search until the end of the token
156 chain
157 reverse: When true, search the tokens before this one instead of the tokens
158 after it
159
160 Returns:
161 The first token of any type in token_types within distance of this token, or
162 None if no such token is found.
163 """
164 return CustomSearch(start_token, lambda token: token.IsAnyType(token_types),
165 None, distance, reverse)
166
167
168 def SearchExcept(start_token, token_types, distance=None, reverse=False):
169 """Returns the first token not of any type in token_types within distance.
170
171 Args:
172 start_token: The token to start searching from
173 token_types: The unallowable types of the token being searched for
174 distance: The number of tokens to look through before failing search. Must
175 be positive. If unspecified, will search until the end of the token
176 chain
177 reverse: When true, search the tokens before this one instead of the tokens
178 after it
179
180 Returns:
181 The first token of any type in token_types within distance of this token, or
182 None if no such token is found.
183 """
184 return CustomSearch(start_token,
185 lambda token: not token.IsAnyType(token_types),
186 None, distance, reverse)
187
188
189 def SearchUntil(start_token, token_types, end_types, distance=None,
190 reverse=False):
191 """Returns the first token of type in token_types before a token of end_type.
192
193 Args:
194 start_token: The token to start searching from.
195 token_types: The allowable types of the token being searched for.
196 end_types: Types of tokens to abort search if we find.
197 distance: The number of tokens to look through before failing search. Must
198 be positive. If unspecified, will search until the end of the token
199 chain
200 reverse: When true, search the tokens before this one instead of the tokens
201 after it
202
203 Returns:
204 The first token of any type in token_types within distance of this token
205 before any tokens of type in end_type, or None if no such token is found.
206 """
207 return CustomSearch(start_token, lambda token: token.IsAnyType(token_types),
208 lambda token: token.IsAnyType(end_types),
209 distance, reverse)
210
211
212 def DeleteToken(token):
213 """Deletes the given token from the linked list.
214
215 Args:
216 token: The token to delete
217 """
218 # When deleting a token, we do not update the deleted token itself to make
219 # sure the previous and next pointers are still pointing to tokens which are
220 # not deleted. Also it is very hard to keep track of all previously deleted
221 # tokens to update them when their pointers become invalid. So we add this
222 # flag that any token linked list iteration logic can skip deleted node safely
223 # when its current token is deleted.
224 token.is_deleted = True
225 if token.previous:
226 token.previous.next = token.next
227
228 if token.next:
229 token.next.previous = token.previous
230
231 following_token = token.next
232 while following_token and following_token.metadata.last_code == token:
233 following_token.metadata.last_code = token.metadata.last_code
234 following_token = following_token.next
235
236
237 def DeleteTokens(token, token_count):
238 """Deletes the given number of tokens starting with the given token.
239
240 Args:
241 token: The token to start deleting at.
242 token_count: The total number of tokens to delete.
243 """
244 for i in xrange(1, token_count):
245 DeleteToken(token.next)
246 DeleteToken(token)
247
248
249 def InsertTokenBefore(new_token, token):
250 """Insert new_token before token.
251
252 Args:
253 new_token: A token to be added to the stream
254 token: A token already in the stream
255 """
256 new_token.next = token
257 new_token.previous = token.previous
258
259 new_token.metadata = copy.copy(token.metadata)
260
261 if new_token.IsCode():
262 old_last_code = token.metadata.last_code
263 following_token = token
264 while (following_token and
265 following_token.metadata.last_code == old_last_code):
266 following_token.metadata.last_code = new_token
267 following_token = following_token.next
268
269 token.previous = new_token
270 if new_token.previous:
271 new_token.previous.next = new_token
272
273 if new_token.start_index is None:
274 if new_token.line_number == token.line_number:
275 new_token.start_index = token.start_index
276 else:
277 previous_token = new_token.previous
278 if previous_token:
279 new_token.start_index = (previous_token.start_index +
280 len(previous_token.string))
281 else:
282 new_token.start_index = 0
283
284 iterator = new_token.next
285 while iterator and iterator.line_number == new_token.line_number:
286 iterator.start_index += len(new_token.string)
287 iterator = iterator.next
288
289
290 def InsertTokenAfter(new_token, token):
291 """Insert new_token after token.
292
293 Args:
294 new_token: A token to be added to the stream
295 token: A token already in the stream
296 """
297 new_token.previous = token
298 new_token.next = token.next
299
300 new_token.metadata = copy.copy(token.metadata)
301
302 if token.IsCode():
303 new_token.metadata.last_code = token
304
305 if new_token.IsCode():
306 following_token = token.next
307 while following_token and following_token.metadata.last_code == token:
308 following_token.metadata.last_code = new_token
309 following_token = following_token.next
310
311 token.next = new_token
312 if new_token.next:
313 new_token.next.previous = new_token
314
315 if new_token.start_index is None:
316 if new_token.line_number == token.line_number:
317 new_token.start_index = token.start_index + len(token.string)
318 else:
319 new_token.start_index = 0
320
321 iterator = new_token.next
322 while iterator and iterator.line_number == new_token.line_number:
323 iterator.start_index += len(new_token.string)
324 iterator = iterator.next
325
326
327 def InsertTokensAfter(new_tokens, token):
328 """Insert multiple tokens after token.
329
330 Args:
331 new_tokens: An array of tokens to be added to the stream
332 token: A token already in the stream
333 """
334 # TODO(user): It would be nicer to have InsertTokenAfter defer to here
335 # instead of vice-versa.
336 current_token = token
337 for new_token in new_tokens:
338 InsertTokenAfter(new_token, current_token)
339 current_token = new_token
340
341
342 def InsertSpaceTokenAfter(token):
343 """Inserts a space token after the given token.
344
345 Args:
346 token: The token to insert a space token after
347
348 Returns:
349 A single space token
350 """
351 space_token = JavaScriptToken(' ', Type.WHITESPACE, token.line,
352 token.line_number)
353 InsertTokenAfter(space_token, token)
354
355
356 def InsertBlankLineAfter(token):
357 """Inserts a blank line after the given token.
358
359 Args:
360 token: The token to insert a blank line after
361
362 Returns:
363 A single space token
364 """
365 blank_token = JavaScriptToken('', Type.BLANK_LINE, '',
366 token.line_number + 1)
367 InsertLineAfter(token, [blank_token])
368
369
370 def InsertLineAfter(token, new_tokens):
371 """Inserts a new line consisting of new_tokens after the given token.
372
373 Args:
374 token: The token to insert after.
375 new_tokens: The tokens that will make up the new line.
376 """
377 insert_location = token
378 for new_token in new_tokens:
379 InsertTokenAfter(new_token, insert_location)
380 insert_location = new_token
381
382 # Update all subsequent line numbers.
383 next_token = new_tokens[-1].next
384 while next_token:
385 next_token.line_number += 1
386 next_token = next_token.next
387
388
389 def SplitToken(token, position):
390 """Splits the token into two tokens at position.
391
392 Args:
393 token: The token to split
394 position: The position to split at. Will be the beginning of second token.
395
396 Returns:
397 The new second token.
398 """
399 new_string = token.string[position:]
400 token.string = token.string[:position]
401
402 new_token = JavaScriptToken(new_string, token.type, token.line,
403 token.line_number)
404 InsertTokenAfter(new_token, token)
405
406 return new_token
407
408
409 def Compare(token1, token2):
410 """Compares two tokens and determines their relative order.
411
412 Args:
413 token1: The first token to compare.
414 token2: The second token to compare.
415
416 Returns:
417 A negative integer, zero, or a positive integer as the first token is
418 before, equal, or after the second in the token stream.
419 """
420 if token2.line_number != token1.line_number:
421 return token1.line_number - token2.line_number
422 else:
423 return token1.start_index - token2.start_index
424
425
426 def GoogScopeOrNoneFromStartBlock(token):
427 """Determines if the given START_BLOCK is part of a goog.scope statement.
428
429 Args:
430 token: A token of type START_BLOCK.
431
432 Returns:
433 The goog.scope function call token, or None if such call doesn't exist.
434 """
435 if token.type != JavaScriptTokenType.START_BLOCK:
436 return None
437
438 # Search for a goog.scope statement, which will be 5 tokens before the
439 # block. Illustration of the tokens found prior to the start block:
440 # goog.scope(function() {
441 # 5 4 3 21 ^
442
443 maybe_goog_scope = token
444 for unused_i in xrange(5):
445 maybe_goog_scope = (maybe_goog_scope.previous if maybe_goog_scope and
446 maybe_goog_scope.previous else None)
447 if maybe_goog_scope and maybe_goog_scope.string == 'goog.scope':
448 return maybe_goog_scope
449
450
451 def GetTokenRange(start_token, end_token):
452 """Returns a list of tokens between the two given, inclusive.
453
454 Args:
455 start_token: Start token in the range.
456 end_token: End token in the range.
457
458 Returns:
459 A list of tokens, in order, from start_token to end_token (including start
460 and end). Returns none if the tokens do not describe a valid range.
461 """
462
463 token_range = []
464 token = start_token
465
466 while token:
467 token_range.append(token)
468
469 if token == end_token:
470 return token_range
471
472 token = token.next
473
474
475 def TokensToString(token_iterable):
476 """Convert a number of tokens into a string.
477
478 Newlines will be inserted whenever the line_number of two neighboring
479 strings differ.
480
481 Args:
482 token_iterable: The tokens to turn to a string.
483
484 Returns:
485 A string representation of the given tokens.
486 """
487
488 buf = StringIO.StringIO()
489 token_list = list(token_iterable)
490 if not token_list:
491 return ''
492
493 line_number = token_list[0].line_number
494
495 for token in token_list:
496
497 while line_number < token.line_number:
498 line_number += 1
499 buf.write('\n')
500
501 if line_number > token.line_number:
502 line_number = token.line_number
503 buf.write('\n')
504
505 buf.write(token.string)
506
507 return buf.getvalue()
508
509
510 def GetPreviousCodeToken(token):
511 """Returns the code token before the specified token.
512
513 Args:
514 token: A token.
515
516 Returns:
517 The code token before the specified token or None if no such token
518 exists.
519 """
520
521 return CustomSearch(
522 token,
523 lambda t: t and t.type not in JavaScriptTokenType.NON_CODE_TYPES,
524 reverse=True)
525
526
527 def GetNextCodeToken(token):
528 """Returns the next code token after the specified token.
529
530 Args:
531 token: A token.
532
533 Returns:
534 The next code token after the specified token or None if no such token
535 exists.
536 """
537
538 return CustomSearch(
539 token,
540 lambda t: t and t.type not in JavaScriptTokenType.NON_CODE_TYPES,
541 reverse=False)
542
543
544 def GetIdentifierStart(token):
545 """Returns the first token in an identifier.
546
547 Given a token which is part of an identifier, returns the token at the start
548 of the identifier.
549
550 Args:
551 token: A token which is part of an identifier.
552
553 Returns:
554 The token at the start of the identifier or None if the identifier was not
555 of the form 'a.b.c' (e.g. "['a']['b'].c").
556 """
557
558 start_token = token
559 previous_code_token = GetPreviousCodeToken(token)
560
561 while (previous_code_token and (
562 previous_code_token.IsType(JavaScriptTokenType.IDENTIFIER) or
563 IsDot(previous_code_token))):
564 start_token = previous_code_token
565 previous_code_token = GetPreviousCodeToken(previous_code_token)
566
567 if IsDot(start_token):
568 return None
569
570 return start_token
571
572
573 def GetIdentifierForToken(token):
574 """Get the symbol specified by a token.
575
576 Given a token, this function additionally concatenates any parts of an
577 identifying symbol being identified that are split by whitespace or a
578 newline.
579
580 The function will return None if the token is not the first token of an
581 identifier.
582
583 Args:
584 token: The first token of a symbol.
585
586 Returns:
587 The whole symbol, as a string.
588 """
589
590 # Search backward to determine if this token is the first token of the
591 # identifier. If it is not the first token, return None to signal that this
592 # token should be ignored.
593 prev_token = token.previous
594 while prev_token:
595 if (prev_token.IsType(JavaScriptTokenType.IDENTIFIER) or
596 IsDot(prev_token)):
597 return None
598
599 if (prev_token.IsType(tokens.TokenType.WHITESPACE) or
600 prev_token.IsAnyType(JavaScriptTokenType.COMMENT_TYPES)):
601 prev_token = prev_token.previous
602 else:
603 break
604
605 # A "function foo()" declaration.
606 if token.type is JavaScriptTokenType.FUNCTION_NAME:
607 return token.string
608
609 # A "var foo" declaration (if the previous token is 'var')
610 previous_code_token = GetPreviousCodeToken(token)
611
612 if previous_code_token and previous_code_token.IsKeyword('var'):
613 return token.string
614
615 # Otherwise, this is potentially a namespaced (goog.foo.bar) identifier that
616 # could span multiple lines or be broken up by whitespace. We need
617 # to concatenate.
618 identifier_types = set([
619 JavaScriptTokenType.IDENTIFIER,
620 JavaScriptTokenType.SIMPLE_LVALUE
621 ])
622
623 assert token.type in identifier_types
624
625 # Start with the first token
626 symbol_tokens = [token]
627
628 if token.next:
629 for t in token.next:
630 last_symbol_token = symbol_tokens[-1]
631
632 # A dot is part of the previous symbol.
633 if IsDot(t):
634 symbol_tokens.append(t)
635 continue
636
637 # An identifier is part of the previous symbol if the previous one was a
638 # dot.
639 if t.type in identifier_types:
640 if IsDot(last_symbol_token):
641 symbol_tokens.append(t)
642 continue
643 else:
644 break
645
646 # Skip any whitespace
647 if t.type in JavaScriptTokenType.NON_CODE_TYPES:
648 continue
649
650 # This is the end of the identifier. Stop iterating.
651 break
652
653 if symbol_tokens:
654 return ''.join([t.string for t in symbol_tokens])
655
656
657 def GetStringAfterToken(token):
658 """Get string after token.
659
660 Args:
661 token: Search will be done after this token.
662
663 Returns:
664 String if found after token else None (empty string will also
665 return None).
666
667 Search until end of string as in case of empty string Type.STRING_TEXT is not
668 present/found and don't want to return next string.
669 E.g.
670 a = '';
671 b = 'test';
672 When searching for string after 'a' if search is not limited by end of string
673 then it will return 'test' which is not desirable as there is a empty string
674 before that.
675
676 This will return None for cases where string is empty or no string found
677 as in both cases there is no Type.STRING_TEXT.
678 """
679 string_token = SearchUntil(token, JavaScriptTokenType.STRING_TEXT,
680 [JavaScriptTokenType.SINGLE_QUOTE_STRING_END,
681 JavaScriptTokenType.DOUBLE_QUOTE_STRING_END,
682 JavaScriptTokenType.TEMPLATE_STRING_END])
683 if string_token:
684 return string_token.string
685 else:
686 return None
687
688
689 def IsDot(token):
690 """Whether the token represents a "dot" operator (foo.bar)."""
691 return token.type is JavaScriptTokenType.OPERATOR and token.string == '.'
692
693
694 def IsIdentifierOrDot(token):
695 """Whether the token is either an identifier or a '.'."""
696 return (token.type in [JavaScriptTokenType.IDENTIFIER,
697 JavaScriptTokenType.SIMPLE_LVALUE] or
698 IsDot(token))
OLDNEW
« no previous file with comments | « third_party/closure_linter/closure_linter/testutil.py ('k') | third_party/closure_linter/closure_linter/tokenutil_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698