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  DOC_TYPE_START_BLOCK = 'Type <'
57  DOC_TYPE_END_BLOCK = 'Type >'
58  DOC_TYPE_MODIFIER = 'modifier'
59  SIMPLE_LVALUE = 'lvalue='
60  KEYWORD = 'keyword'
61  OPERATOR = 'operator'
62  IDENTIFIER = 'identifier'
63
64  STRING_TYPES = frozenset([
65      SINGLE_QUOTE_STRING_START, SINGLE_QUOTE_STRING_END,
66      DOUBLE_QUOTE_STRING_START, DOUBLE_QUOTE_STRING_END, STRING_TEXT])
67
68  COMMENT_TYPES = frozenset([
69      START_SINGLE_LINE_COMMENT, COMMENT,
70      START_BLOCK_COMMENT, START_DOC_COMMENT,
71      END_BLOCK_COMMENT, END_DOC_COMMENT,
72      DOC_START_BRACE, DOC_END_BRACE,
73      DOC_FLAG, DOC_INLINE_FLAG, DOC_PREFIX,
74      DOC_TYPE_START_BLOCK, DOC_TYPE_END_BLOCK, DOC_TYPE_MODIFIER])
75
76  FLAG_DESCRIPTION_TYPES = frozenset([
77      DOC_INLINE_FLAG, COMMENT, DOC_START_BRACE, DOC_END_BRACE,
78      DOC_TYPE_START_BLOCK, DOC_TYPE_END_BLOCK, DOC_TYPE_MODIFIER])
79
80  FLAG_ENDING_TYPES = frozenset([DOC_FLAG, END_DOC_COMMENT])
81
82  NON_CODE_TYPES = COMMENT_TYPES | frozenset([
83      tokens.TokenType.WHITESPACE, tokens.TokenType.BLANK_LINE])
84
85  UNARY_OPERATORS = ['!', 'new', 'delete', 'typeof', 'void']
86
87  UNARY_OK_OPERATORS = ['--', '++', '-', '+'] + UNARY_OPERATORS
88
89  UNARY_POST_OPERATORS = ['--', '++']
90
91  # An expression ender is any token that can end an object - i.e. we could have
92  # x.y or [1, 2], or (10 + 9) or {a: 10}.
93  EXPRESSION_ENDER_TYPES = [tokens.TokenType.NORMAL, IDENTIFIER, NUMBER,
94                            SIMPLE_LVALUE, END_BRACKET, END_PAREN, END_BLOCK,
95                            SINGLE_QUOTE_STRING_END, DOUBLE_QUOTE_STRING_END]
96
97
98class JavaScriptToken(tokens.Token):
99  """JavaScript token subclass of Token, provides extra instance checks.
100
101  The following token types have data in attached_object:
102    - All JsDoc flags: a parser.JsDocFlag object.
103  """
104
105  def IsKeyword(self, keyword):
106    """Tests if this token is the given keyword.
107
108    Args:
109      keyword: The keyword to compare to.
110
111    Returns:
112      True if this token is a keyword token with the given name.
113    """
114    return self.type == JavaScriptTokenType.KEYWORD and self.string == keyword
115
116  def IsOperator(self, operator):
117    """Tests if this token is the given operator.
118
119    Args:
120      operator: The operator to compare to.
121
122    Returns:
123      True if this token is a operator token with the given name.
124    """
125    return self.type == JavaScriptTokenType.OPERATOR and self.string == operator
126
127  def IsAssignment(self):
128    """Tests if this token is an assignment operator.
129
130    Returns:
131      True if this token is an assignment operator.
132    """
133    return (self.type == JavaScriptTokenType.OPERATOR and
134            self.string.endswith('=') and
135            self.string not in ('==', '!=', '>=', '<=', '===', '!=='))
136
137  def IsComment(self):
138    """Tests if this token is any part of a comment.
139
140    Returns:
141      True if this token is any part of a comment.
142    """
143    return self.type in JavaScriptTokenType.COMMENT_TYPES
144
145  def IsCode(self):
146    """Tests if this token is code, as opposed to a comment or whitespace."""
147    return self.type not in JavaScriptTokenType.NON_CODE_TYPES
148
149  def __repr__(self):
150    return '<JavaScriptToken: %d, %s, "%s", %r, %r>' % (self.line_number,
151                                                        self.type, self.string,
152                                                        self.values,
153                                                        self.metadata)
154