1#!/usr/bin/env python
2#
3# Copyright 2008 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"""Classes to represent JavaScript tokens."""
18
19__author__ = ('robbyw@google.com (Robert Walker)',
20              'ajp@google.com (Andy Perelson)')
21
22from closure_linter.common import tokens
23
24class JavaScriptTokenType(tokens.TokenType):
25  """Enumeration of JavaScript token types, and useful sets of token types."""
26  NUMBER = 'number'
27  START_SINGLE_LINE_COMMENT = '//'
28  START_BLOCK_COMMENT = '/*'
29  START_DOC_COMMENT = '/**'
30  END_BLOCK_COMMENT = '*/'
31  END_DOC_COMMENT = 'doc */'
32  COMMENT = 'comment'
33  SINGLE_QUOTE_STRING_START = "'string"
34  SINGLE_QUOTE_STRING_END = "string'"
35  DOUBLE_QUOTE_STRING_START = '"string'
36  DOUBLE_QUOTE_STRING_END = 'string"'
37  STRING_TEXT = 'string'
38  START_BLOCK = '{'
39  END_BLOCK = '}'
40  START_PAREN = '('
41  END_PAREN = ')'
42  START_BRACKET = '['
43  END_BRACKET = ']'
44  REGEX = '/regex/'
45  FUNCTION_DECLARATION = 'function(...)'
46  FUNCTION_NAME = 'function functionName(...)'
47  START_PARAMETERS = 'startparams('
48  PARAMETERS = 'pa,ra,ms'
49  END_PARAMETERS = ')endparams'
50  SEMICOLON = ';'
51  DOC_FLAG = '@flag'
52  DOC_INLINE_FLAG = '{@flag ...}'
53  DOC_START_BRACE = 'doc {'
54  DOC_END_BRACE = 'doc }'
55  DOC_PREFIX = 'comment prefix: * '
56  SIMPLE_LVALUE = 'lvalue='
57  KEYWORD = 'keyword'
58  OPERATOR = 'operator'
59  IDENTIFIER = 'identifier'
60
61  STRING_TYPES = frozenset([
62      SINGLE_QUOTE_STRING_START, SINGLE_QUOTE_STRING_END,
63      DOUBLE_QUOTE_STRING_START, DOUBLE_QUOTE_STRING_END, STRING_TEXT])
64
65  COMMENT_TYPES = frozenset([START_SINGLE_LINE_COMMENT, COMMENT,
66      START_BLOCK_COMMENT, START_DOC_COMMENT,
67      END_BLOCK_COMMENT, END_DOC_COMMENT,
68      DOC_START_BRACE, DOC_END_BRACE,
69      DOC_FLAG, DOC_INLINE_FLAG, DOC_PREFIX])
70
71  FLAG_DESCRIPTION_TYPES = frozenset([
72      DOC_INLINE_FLAG, COMMENT, DOC_START_BRACE, DOC_END_BRACE])
73
74  FLAG_ENDING_TYPES = frozenset([DOC_FLAG, END_DOC_COMMENT])
75
76  NON_CODE_TYPES = COMMENT_TYPES | frozenset([
77      tokens.TokenType.WHITESPACE, tokens.TokenType.BLANK_LINE])
78
79  UNARY_OPERATORS = ['!', 'new', 'delete', 'typeof', 'void']
80
81  UNARY_OK_OPERATORS = ['--', '++', '-', '+'] + UNARY_OPERATORS
82
83  UNARY_POST_OPERATORS = ['--', '++']
84
85  # An expression ender is any token that can end an object - i.e. we could have
86  # x.y or [1, 2], or (10 + 9) or {a: 10}.
87  EXPRESSION_ENDER_TYPES = [tokens.TokenType.NORMAL, IDENTIFIER, NUMBER,
88                            SIMPLE_LVALUE, END_BRACKET, END_PAREN, END_BLOCK,
89                            SINGLE_QUOTE_STRING_END, DOUBLE_QUOTE_STRING_END]
90
91
92class JavaScriptToken(tokens.Token):
93  """JavaScript token subclass of Token, provides extra instance checks.
94
95  The following token types have data in attached_object:
96    - All JsDoc flags: a parser.JsDocFlag object.
97  """
98
99  def IsKeyword(self, keyword):
100    """Tests if this token is the given keyword.
101
102    Args:
103      keyword: The keyword to compare to.
104
105    Returns:
106      True if this token is a keyword token with the given name.
107    """
108    return self.type == JavaScriptTokenType.KEYWORD and self.string == keyword
109
110  def IsOperator(self, operator):
111    """Tests if this token is the given operator.
112
113    Args:
114      operator: The operator to compare to.
115
116    Returns:
117      True if this token is a operator token with the given name.
118    """
119    return self.type == JavaScriptTokenType.OPERATOR and self.string == operator
120
121  def IsAssignment(self):
122    """Tests if this token is an assignment operator.
123
124    Returns:
125      True if this token is an assignment operator.
126    """
127    return (self.type == JavaScriptTokenType.OPERATOR and
128            self.string.endswith('=') and
129            self.string not in ('==', '!=', '>=', '<=', '===', '!=='))
130
131  def IsComment(self):
132    """Tests if this token is any part of a comment.
133
134    Returns:
135      True if this token is any part of a comment.
136    """
137    return self.type in JavaScriptTokenType.COMMENT_TYPES
138
139  def IsCode(self):
140    """Tests if this token is code, as opposed to a comment or whitespace."""
141    return self.type not in JavaScriptTokenType.NON_CODE_TYPES
142
143  def __repr__(self):
144    return '<JavaScriptToken: %d, %s, "%s", %r, %r>' % (self.line_number,
145                                                        self.type, self.string,
146                                                        self.values,
147                                                        self.metadata)
148