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

Side by Side Diff: third_party/closure_linter/closure_linter/ecmametadatapass.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 2010 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 """Metadata pass for annotating tokens in EcmaScript files."""
18
19 __author__ = ('robbyw@google.com (Robert Walker)')
20
21 from closure_linter import javascripttokens
22 from closure_linter import tokenutil
23
24
25 TokenType = javascripttokens.JavaScriptTokenType
26
27
28 class ParseError(Exception):
29 """Exception indicating a parse error at the given token.
30
31 Attributes:
32 token: The token where the parse error occurred.
33 """
34
35 def __init__(self, token, message=None):
36 """Initialize a parse error at the given token with an optional message.
37
38 Args:
39 token: The token where the parse error occurred.
40 message: A message describing the parse error.
41 """
42 Exception.__init__(self, message)
43 self.token = token
44
45
46 class EcmaContext(object):
47 """Context object for EcmaScript languages.
48
49 Attributes:
50 type: The context type.
51 start_token: The token where this context starts.
52 end_token: The token where this context ends.
53 parent: The parent context.
54 """
55
56 # The root context.
57 ROOT = 'root'
58
59 # A block of code.
60 BLOCK = 'block'
61
62 # A pseudo-block of code for a given case or default section.
63 CASE_BLOCK = 'case_block'
64
65 # Block of statements in a for loop's parentheses.
66 FOR_GROUP_BLOCK = 'for_block'
67
68 # An implied block of code for 1 line if, while, and for statements
69 IMPLIED_BLOCK = 'implied_block'
70
71 # An index in to an array or object.
72 INDEX = 'index'
73
74 # An array literal in [].
75 ARRAY_LITERAL = 'array_literal'
76
77 # An object literal in {}.
78 OBJECT_LITERAL = 'object_literal'
79
80 # An individual element in an array or object literal.
81 LITERAL_ELEMENT = 'literal_element'
82
83 # The portion of a ternary statement between ? and :
84 TERNARY_TRUE = 'ternary_true'
85
86 # The portion of a ternary statment after :
87 TERNARY_FALSE = 'ternary_false'
88
89 # The entire switch statment. This will contain a GROUP with the variable
90 # and a BLOCK with the code.
91
92 # Since that BLOCK is not a normal block, it can not contain statements except
93 # for case and default.
94 SWITCH = 'switch'
95
96 # A normal comment.
97 COMMENT = 'comment'
98
99 # A JsDoc comment.
100 DOC = 'doc'
101
102 # An individual statement.
103 STATEMENT = 'statement'
104
105 # Code within parentheses.
106 GROUP = 'group'
107
108 # Parameter names in a function declaration.
109 PARAMETERS = 'parameters'
110
111 # A set of variable declarations appearing after the 'var' keyword.
112 VAR = 'var'
113
114 # Context types that are blocks.
115 BLOCK_TYPES = frozenset([
116 ROOT, BLOCK, CASE_BLOCK, FOR_GROUP_BLOCK, IMPLIED_BLOCK])
117
118 def __init__(self, context_type, start_token, parent=None):
119 """Initializes the context object.
120
121 Args:
122 context_type: The context type.
123 start_token: The token where this context starts.
124 parent: The parent context.
125
126 Attributes:
127 type: The context type.
128 start_token: The token where this context starts.
129 end_token: The token where this context ends.
130 parent: The parent context.
131 children: The child contexts of this context, in order.
132 """
133 self.type = context_type
134 self.start_token = start_token
135 self.end_token = None
136
137 self.parent = None
138 self.children = []
139
140 if parent:
141 parent.AddChild(self)
142
143 def __repr__(self):
144 """Returns a string representation of the context object."""
145 stack = []
146 context = self
147 while context:
148 stack.append(context.type)
149 context = context.parent
150 return 'Context(%s)' % ' > '.join(stack)
151
152 def AddChild(self, child):
153 """Adds a child to this context and sets child's parent to this context.
154
155 Args:
156 child: A child EcmaContext. The child's parent will be set to this
157 context.
158 """
159
160 child.parent = self
161
162 self.children.append(child)
163 self.children.sort(EcmaContext._CompareContexts)
164
165 def GetRoot(self):
166 """Get the root context that contains this context, if any."""
167 context = self
168 while context:
169 if context.type is EcmaContext.ROOT:
170 return context
171 context = context.parent
172
173 @staticmethod
174 def _CompareContexts(context1, context2):
175 """Sorts contexts 1 and 2 by start token document position."""
176 return tokenutil.Compare(context1.start_token, context2.start_token)
177
178
179 class EcmaMetaData(object):
180 """Token metadata for EcmaScript languages.
181
182 Attributes:
183 last_code: The last code token to appear before this one.
184 context: The context this token appears in.
185 operator_type: The operator type, will be one of the *_OPERATOR constants
186 defined below.
187 aliased_symbol: The full symbol being identified, as a string (e.g. an
188 'XhrIo' alias for 'goog.net.XhrIo'). Only applicable to identifier
189 tokens. This is set in aliaspass.py and is a best guess.
190 is_alias_definition: True if the symbol is part of an alias definition.
191 If so, these symbols won't be counted towards goog.requires/provides.
192 """
193
194 UNARY_OPERATOR = 'unary'
195
196 UNARY_POST_OPERATOR = 'unary_post'
197
198 BINARY_OPERATOR = 'binary'
199
200 TERNARY_OPERATOR = 'ternary'
201
202 def __init__(self):
203 """Initializes a token metadata object."""
204 self.last_code = None
205 self.context = None
206 self.operator_type = None
207 self.is_implied_semicolon = False
208 self.is_implied_block = False
209 self.is_implied_block_close = False
210 self.aliased_symbol = None
211 self.is_alias_definition = False
212
213 def __repr__(self):
214 """Returns a string representation of the context object."""
215 parts = ['%r' % self.context]
216 if self.operator_type:
217 parts.append('optype: %r' % self.operator_type)
218 if self.is_implied_semicolon:
219 parts.append('implied;')
220 if self.aliased_symbol:
221 parts.append('alias for: %s' % self.aliased_symbol)
222 return 'MetaData(%s)' % ', '.join(parts)
223
224 def IsUnaryOperator(self):
225 return self.operator_type in (EcmaMetaData.UNARY_OPERATOR,
226 EcmaMetaData.UNARY_POST_OPERATOR)
227
228 def IsUnaryPostOperator(self):
229 return self.operator_type == EcmaMetaData.UNARY_POST_OPERATOR
230
231
232 class EcmaMetaDataPass(object):
233 """A pass that iterates over all tokens and builds metadata about them."""
234
235 def __init__(self):
236 """Initialize the meta data pass object."""
237 self.Reset()
238
239 def Reset(self):
240 """Resets the metadata pass to prepare for the next file."""
241 self._token = None
242 self._context = None
243 self._AddContext(EcmaContext.ROOT)
244 self._last_code = None
245
246 def _CreateContext(self, context_type):
247 """Overridable by subclasses to create the appropriate context type."""
248 return EcmaContext(context_type, self._token, self._context)
249
250 def _CreateMetaData(self):
251 """Overridable by subclasses to create the appropriate metadata type."""
252 return EcmaMetaData()
253
254 def _AddContext(self, context_type):
255 """Adds a context of the given type to the context stack.
256
257 Args:
258 context_type: The type of context to create
259 """
260 self._context = self._CreateContext(context_type)
261
262 def _PopContext(self):
263 """Moves up one level in the context stack.
264
265 Returns:
266 The former context.
267
268 Raises:
269 ParseError: If the root context is popped.
270 """
271 top_context = self._context
272 top_context.end_token = self._token
273 self._context = top_context.parent
274 if self._context:
275 return top_context
276 else:
277 raise ParseError(self._token)
278
279 def _PopContextType(self, *stop_types):
280 """Pops the context stack until a context of the given type is popped.
281
282 Args:
283 *stop_types: The types of context to pop to - stops at the first match.
284
285 Returns:
286 The context object of the given type that was popped.
287 """
288 last = None
289 while not last or last.type not in stop_types:
290 last = self._PopContext()
291 return last
292
293 def _EndStatement(self):
294 """Process the end of a statement."""
295 self._PopContextType(EcmaContext.STATEMENT)
296 if self._context.type == EcmaContext.IMPLIED_BLOCK:
297 self._token.metadata.is_implied_block_close = True
298 self._PopContext()
299
300 def _ProcessContext(self):
301 """Process the context at the current token.
302
303 Returns:
304 The context that should be assigned to the current token, or None if
305 the current context after this method should be used.
306
307 Raises:
308 ParseError: When the token appears in an invalid context.
309 """
310 token = self._token
311 token_type = token.type
312
313 if self._context.type in EcmaContext.BLOCK_TYPES:
314 # Whenever we're in a block, we add a statement context. We make an
315 # exception for switch statements since they can only contain case: and
316 # default: and therefore don't directly contain statements.
317 # The block we add here may be immediately removed in some cases, but
318 # that causes no harm.
319 parent = self._context.parent
320 if not parent or parent.type != EcmaContext.SWITCH:
321 self._AddContext(EcmaContext.STATEMENT)
322
323 elif self._context.type == EcmaContext.ARRAY_LITERAL:
324 self._AddContext(EcmaContext.LITERAL_ELEMENT)
325
326 if token_type == TokenType.START_PAREN:
327 if self._last_code and self._last_code.IsKeyword('for'):
328 # for loops contain multiple statements in the group unlike while,
329 # switch, if, etc.
330 self._AddContext(EcmaContext.FOR_GROUP_BLOCK)
331 else:
332 self._AddContext(EcmaContext.GROUP)
333
334 elif token_type == TokenType.END_PAREN:
335 result = self._PopContextType(EcmaContext.GROUP,
336 EcmaContext.FOR_GROUP_BLOCK)
337 keyword_token = result.start_token.metadata.last_code
338 # keyword_token will not exist if the open paren is the first line of the
339 # file, for example if all code is wrapped in an immediately executed
340 # annonymous function.
341 if keyword_token and keyword_token.string in ('if', 'for', 'while'):
342 next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
343 if next_code.type != TokenType.START_BLOCK:
344 # Check for do-while.
345 is_do_while = False
346 pre_keyword_token = keyword_token.metadata.last_code
347 if (pre_keyword_token and
348 pre_keyword_token.type == TokenType.END_BLOCK):
349 start_block_token = pre_keyword_token.metadata.context.start_token
350 is_do_while = start_block_token.metadata.last_code.string == 'do'
351
352 # If it's not do-while, it's an implied block.
353 if not is_do_while:
354 self._AddContext(EcmaContext.IMPLIED_BLOCK)
355 token.metadata.is_implied_block = True
356
357 return result
358
359 # else (not else if) with no open brace after it should be considered the
360 # start of an implied block, similar to the case with if, for, and while
361 # above.
362 elif (token_type == TokenType.KEYWORD and
363 token.string == 'else'):
364 next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
365 if (next_code.type != TokenType.START_BLOCK and
366 (next_code.type != TokenType.KEYWORD or next_code.string != 'if')):
367 self._AddContext(EcmaContext.IMPLIED_BLOCK)
368 token.metadata.is_implied_block = True
369
370 elif token_type == TokenType.START_PARAMETERS:
371 self._AddContext(EcmaContext.PARAMETERS)
372
373 elif token_type == TokenType.END_PARAMETERS:
374 return self._PopContextType(EcmaContext.PARAMETERS)
375
376 elif token_type == TokenType.START_BRACKET:
377 if (self._last_code and
378 self._last_code.type in TokenType.EXPRESSION_ENDER_TYPES):
379 self._AddContext(EcmaContext.INDEX)
380 else:
381 self._AddContext(EcmaContext.ARRAY_LITERAL)
382
383 elif token_type == TokenType.END_BRACKET:
384 return self._PopContextType(EcmaContext.INDEX, EcmaContext.ARRAY_LITERAL)
385
386 elif token_type == TokenType.START_BLOCK:
387 if (self._last_code.type in (TokenType.END_PAREN,
388 TokenType.END_PARAMETERS) or
389 self._last_code.IsKeyword('else') or
390 self._last_code.IsKeyword('do') or
391 self._last_code.IsKeyword('try') or
392 self._last_code.IsKeyword('finally') or
393 (self._last_code.IsOperator(':') and
394 self._last_code.metadata.context.type == EcmaContext.CASE_BLOCK)):
395 # else, do, try, and finally all might have no () before {.
396 # Also, handle the bizzare syntax case 10: {...}.
397 self._AddContext(EcmaContext.BLOCK)
398 else:
399 self._AddContext(EcmaContext.OBJECT_LITERAL)
400
401 elif token_type == TokenType.END_BLOCK:
402 context = self._PopContextType(EcmaContext.BLOCK,
403 EcmaContext.OBJECT_LITERAL)
404 if self._context.type == EcmaContext.SWITCH:
405 # The end of the block also means the end of the switch statement it
406 # applies to.
407 return self._PopContext()
408 return context
409
410 elif token.IsKeyword('switch'):
411 self._AddContext(EcmaContext.SWITCH)
412
413 elif (token_type == TokenType.KEYWORD and
414 token.string in ('case', 'default') and
415 self._context.type != EcmaContext.OBJECT_LITERAL):
416 # Pop up to but not including the switch block.
417 while self._context.parent.type != EcmaContext.SWITCH:
418 self._PopContext()
419 if self._context.parent is None:
420 raise ParseError(token, 'Encountered case/default statement '
421 'without switch statement')
422
423 elif token.IsOperator('?'):
424 self._AddContext(EcmaContext.TERNARY_TRUE)
425
426 elif token.IsOperator(':'):
427 if self._context.type == EcmaContext.OBJECT_LITERAL:
428 self._AddContext(EcmaContext.LITERAL_ELEMENT)
429
430 elif self._context.type == EcmaContext.TERNARY_TRUE:
431 self._PopContext()
432 self._AddContext(EcmaContext.TERNARY_FALSE)
433
434 # Handle nested ternary statements like:
435 # foo = bar ? baz ? 1 : 2 : 3
436 # When we encounter the second ":" the context is
437 # ternary_false > ternary_true > statement > root
438 elif (self._context.type == EcmaContext.TERNARY_FALSE and
439 self._context.parent.type == EcmaContext.TERNARY_TRUE):
440 self._PopContext() # Leave current ternary false context.
441 self._PopContext() # Leave current parent ternary true
442 self._AddContext(EcmaContext.TERNARY_FALSE)
443
444 elif self._context.parent.type == EcmaContext.SWITCH:
445 self._AddContext(EcmaContext.CASE_BLOCK)
446
447 elif token.IsKeyword('var'):
448 self._AddContext(EcmaContext.VAR)
449
450 elif token.IsOperator(','):
451 while self._context.type not in (EcmaContext.VAR,
452 EcmaContext.ARRAY_LITERAL,
453 EcmaContext.OBJECT_LITERAL,
454 EcmaContext.STATEMENT,
455 EcmaContext.PARAMETERS,
456 EcmaContext.GROUP):
457 self._PopContext()
458
459 elif token_type == TokenType.SEMICOLON:
460 self._EndStatement()
461
462 def Process(self, first_token):
463 """Processes the token stream starting with the given token."""
464 self._token = first_token
465 while self._token:
466 self._ProcessToken()
467
468 if self._token.IsCode():
469 self._last_code = self._token
470
471 self._token = self._token.next
472
473 try:
474 self._PopContextType(self, EcmaContext.ROOT)
475 except ParseError:
476 # Ignore the "popped to root" error.
477 pass
478
479 def _ProcessToken(self):
480 """Process the given token."""
481 token = self._token
482 token.metadata = self._CreateMetaData()
483 context = (self._ProcessContext() or self._context)
484 token.metadata.context = context
485 token.metadata.last_code = self._last_code
486
487 # Determine the operator type of the token, if applicable.
488 if token.type == TokenType.OPERATOR:
489 token.metadata.operator_type = self._GetOperatorType(token)
490
491 # Determine if there is an implied semicolon after the token.
492 if token.type != TokenType.SEMICOLON:
493 next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
494 # A statement like if (x) does not need a semicolon after it
495 is_implied_block = self._context == EcmaContext.IMPLIED_BLOCK
496 is_last_code_in_line = token.IsCode() and (
497 not next_code or next_code.line_number != token.line_number)
498 is_continued_operator = (token.type == TokenType.OPERATOR and
499 not token.metadata.IsUnaryPostOperator())
500 is_continued_dot = token.string == '.'
501 next_code_is_operator = next_code and next_code.type == TokenType.OPERATOR
502 is_end_of_block = (
503 token.type == TokenType.END_BLOCK and
504 token.metadata.context.type != EcmaContext.OBJECT_LITERAL)
505 is_multiline_string = (token.type == TokenType.STRING_TEXT or
506 token.type == TokenType.TEMPLATE_STRING_START)
507 is_continued_var_decl = (token.IsKeyword('var') and
508 next_code and
509 (next_code.type in [TokenType.IDENTIFIER,
510 TokenType.SIMPLE_LVALUE]) and
511 token.line_number < next_code.line_number)
512 next_code_is_block = next_code and next_code.type == TokenType.START_BLOCK
513 if (is_last_code_in_line and
514 self._StatementCouldEndInContext() and
515 not is_multiline_string and
516 not is_end_of_block and
517 not is_continued_var_decl and
518 not is_continued_operator and
519 not is_continued_dot and
520 not next_code_is_operator and
521 not is_implied_block and
522 not next_code_is_block):
523 token.metadata.is_implied_semicolon = True
524 self._EndStatement()
525
526 def _StatementCouldEndInContext(self):
527 """Returns if the current statement (if any) may end in this context."""
528 # In the basic statement or variable declaration context, statement can
529 # always end in this context.
530 if self._context.type in (EcmaContext.STATEMENT, EcmaContext.VAR):
531 return True
532
533 # End of a ternary false branch inside a statement can also be the
534 # end of the statement, for example:
535 # var x = foo ? foo.bar() : null
536 # In this case the statement ends after the null, when the context stack
537 # looks like ternary_false > var > statement > root.
538 if (self._context.type == EcmaContext.TERNARY_FALSE and
539 self._context.parent.type in (EcmaContext.STATEMENT, EcmaContext.VAR)):
540 return True
541
542 # In all other contexts like object and array literals, ternary true, etc.
543 # the statement can't yet end.
544 return False
545
546 def _GetOperatorType(self, token):
547 """Returns the operator type of the given operator token.
548
549 Args:
550 token: The token to get arity for.
551
552 Returns:
553 The type of the operator. One of the *_OPERATOR constants defined in
554 EcmaMetaData.
555 """
556 if token.string == '?':
557 return EcmaMetaData.TERNARY_OPERATOR
558
559 if token.string in TokenType.UNARY_OPERATORS:
560 return EcmaMetaData.UNARY_OPERATOR
561
562 last_code = token.metadata.last_code
563 if not last_code or last_code.type == TokenType.END_BLOCK:
564 return EcmaMetaData.UNARY_OPERATOR
565
566 if (token.string in TokenType.UNARY_POST_OPERATORS and
567 last_code.type in TokenType.EXPRESSION_ENDER_TYPES):
568 return EcmaMetaData.UNARY_POST_OPERATOR
569
570 if (token.string in TokenType.UNARY_OK_OPERATORS and
571 last_code.type not in TokenType.EXPRESSION_ENDER_TYPES and
572 last_code.string not in TokenType.UNARY_POST_OPERATORS):
573 return EcmaMetaData.UNARY_OPERATOR
574
575 return EcmaMetaData.BINARY_OPERATOR
OLDNEW
« no previous file with comments | « third_party/closure_linter/closure_linter/ecmalintrules.py ('k') | third_party/closure_linter/closure_linter/error_check.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698