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

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

Issue 411243002: closure_linter: 2.3.4 => 2.3.14 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: remove checker Created 6 years, 5 months 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 | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2008 The Closure Linter Authors. All Rights Reserved. 3 # Copyright 2008 The Closure Linter Authors. All Rights Reserved.
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with 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 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
11 # Unless required by applicable law or agreed to in writing, software 11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS-IS" BASIS, 12 # distributed under the License is distributed on an "AS-IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and 14 # See the License for the specific language governing permissions and
15 # limitations under the License. 15 # limitations under the License.
16 16
17 """Core methods for checking EcmaScript files for common style guide violations. 17 """Core methods for checking EcmaScript files for common style guide violations.
18 """ 18 """
19 19
20 __author__ = ('robbyw@google.com (Robert Walker)', 20 __author__ = ('robbyw@google.com (Robert Walker)',
21 'ajp@google.com (Andy Perelson)', 21 'ajp@google.com (Andy Perelson)',
22 'jacobr@google.com (Jacob Richman)') 22 'jacobr@google.com (Jacob Richman)')
23 23
24 import re 24 import re
25 25
26 import gflags as flags
27
26 from closure_linter import checkerbase 28 from closure_linter import checkerbase
27 from closure_linter import ecmametadatapass 29 from closure_linter import ecmametadatapass
28 from closure_linter import error_check 30 from closure_linter import error_check
31 from closure_linter import errorrules
29 from closure_linter import errors 32 from closure_linter import errors
30 from closure_linter import indentation 33 from closure_linter import indentation
34 from closure_linter import javascripttokenizer
31 from closure_linter import javascripttokens 35 from closure_linter import javascripttokens
32 from closure_linter import javascripttokenizer
33 from closure_linter import statetracker 36 from closure_linter import statetracker
34 from closure_linter import tokenutil 37 from closure_linter import tokenutil
35 from closure_linter.common import error 38 from closure_linter.common import error
36 from closure_linter.common import htmlutil
37 from closure_linter.common import lintrunner
38 from closure_linter.common import position 39 from closure_linter.common import position
39 from closure_linter.common import tokens 40
40 import gflags as flags
41 41
42 FLAGS = flags.FLAGS 42 FLAGS = flags.FLAGS
43 flags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow') 43 flags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow')
44 44
45 # TODO(robbyw): Check for extra parens on return statements 45 # TODO(robbyw): Check for extra parens on return statements
46 # TODO(robbyw): Check for 0px in strings 46 # TODO(robbyw): Check for 0px in strings
47 # TODO(robbyw): Ensure inline jsDoc is in {} 47 # TODO(robbyw): Ensure inline jsDoc is in {}
48 # TODO(robbyw): Check for valid JS types in parameter docs 48 # TODO(robbyw): Check for valid JS types in parameter docs
49 49
50 # Shorthand 50 # Shorthand
51 Context = ecmametadatapass.EcmaContext 51 Context = ecmametadatapass.EcmaContext
52 Error = error.Error 52 Error = error.Error
53 Modes = javascripttokenizer.JavaScriptModes 53 Modes = javascripttokenizer.JavaScriptModes
54 Position = position.Position 54 Position = position.Position
55 Rule = error_check.Rule 55 Rule = error_check.Rule
56 Type = javascripttokens.JavaScriptTokenType 56 Type = javascripttokens.JavaScriptTokenType
57 57
58
58 class EcmaScriptLintRules(checkerbase.LintRulesBase): 59 class EcmaScriptLintRules(checkerbase.LintRulesBase):
59 """EmcaScript lint style checking rules. 60 """EmcaScript lint style checking rules.
60 61
61 Can be used to find common style errors in JavaScript, ActionScript and other 62 Can be used to find common style errors in JavaScript, ActionScript and other
62 Ecma like scripting languages. Style checkers for Ecma scripting languages 63 Ecma like scripting languages. Style checkers for Ecma scripting languages
63 should inherit from this style checker. 64 should inherit from this style checker.
64 Please do not add any state to EcmaScriptLintRules or to any subclasses. 65 Please do not add any state to EcmaScriptLintRules or to any subclasses.
65 66
66 All state should be added to the StateTracker subclass used for a particular 67 All state should be added to the StateTracker subclass used for a particular
67 language. 68 language.
68 """ 69 """
69 70
71 # It will be initialized in constructor so the flags are initialized.
72 max_line_length = -1
73
70 # Static constants. 74 # Static constants.
71 MAX_LINE_LENGTH = 80
72
73 MISSING_PARAMETER_SPACE = re.compile(r',\S') 75 MISSING_PARAMETER_SPACE = re.compile(r',\S')
74 76
75 EXTRA_SPACE = re.compile('(\(\s|\s\))') 77 EXTRA_SPACE = re.compile(r'(\(\s|\s\))')
76 78
77 ENDS_WITH_SPACE = re.compile('\s$') 79 ENDS_WITH_SPACE = re.compile(r'\s$')
78 80
79 ILLEGAL_TAB = re.compile(r'\t') 81 ILLEGAL_TAB = re.compile(r'\t')
80 82
81 # Regex used to split up complex types to check for invalid use of ? and |. 83 # Regex used to split up complex types to check for invalid use of ? and |.
82 TYPE_SPLIT = re.compile(r'[,<>()]') 84 TYPE_SPLIT = re.compile(r'[,<>()]')
83 85
84 # Regex for form of author lines after the @author tag. 86 # Regex for form of author lines after the @author tag.
85 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)') 87 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)')
86 88
87 # Acceptable tokens to remove for line too long testing. 89 # Acceptable tokens to remove for line too long testing.
88 LONG_LINE_IGNORE = frozenset(['*', '//', '@see'] + 90 LONG_LINE_IGNORE = frozenset(
91 ['*', '//', '@see'] +
89 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE]) 92 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE])
90 93
94 JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED = frozenset([
95 '@param', '@return', '@returns'])
96
91 def __init__(self): 97 def __init__(self):
92 """Initialize this lint rule object.""" 98 """Initialize this lint rule object."""
93 checkerbase.LintRulesBase.__init__(self) 99 checkerbase.LintRulesBase.__init__(self)
100 if EcmaScriptLintRules.max_line_length == -1:
101 EcmaScriptLintRules.max_line_length = errorrules.GetMaxLineLength()
94 102
95 def Initialize(self, checker, limited_doc_checks, is_html): 103 def Initialize(self, checker, limited_doc_checks, is_html):
96 """Initialize this lint rule object before parsing a new file.""" 104 """Initialize this lint rule object before parsing a new file."""
97 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks, 105 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks,
98 is_html) 106 is_html)
99 self._indentation = indentation.IndentationRules() 107 self._indentation = indentation.IndentationRules()
100 108
101 def HandleMissingParameterDoc(self, token, param_name): 109 def HandleMissingParameterDoc(self, token, param_name):
102 """Handle errors associated with a parameter missing a @param tag.""" 110 """Handle errors associated with a parameter missing a @param tag."""
103 raise TypeError('Abstract method HandleMissingParameterDoc not implemented') 111 raise TypeError('Abstract method HandleMissingParameterDoc not implemented')
104 112
105 def _CheckLineLength(self, last_token, state): 113 def _CheckLineLength(self, last_token, state):
106 """Checks whether the line is too long. 114 """Checks whether the line is too long.
107 115
108 Args: 116 Args:
109 last_token: The last token in the line. 117 last_token: The last token in the line.
118 state: parser_state object that indicates the current state in the page
110 """ 119 """
111 # Start from the last token so that we have the flag object attached to 120 # Start from the last token so that we have the flag object attached to
112 # and DOC_FLAG tokens. 121 # and DOC_FLAG tokens.
113 line_number = last_token.line_number 122 line_number = last_token.line_number
114 token = last_token 123 token = last_token
115 124
116 # Build a representation of the string where spaces indicate potential 125 # Build a representation of the string where spaces indicate potential
117 # line-break locations. 126 # line-break locations.
118 line = [] 127 line = []
119 while token and token.line_number == line_number: 128 while token and token.line_number == line_number:
120 if state.IsTypeToken(token): 129 if state.IsTypeToken(token):
121 line.insert(0, 'x' * len(token.string)) 130 line.insert(0, 'x' * len(token.string))
122 elif token.type in (Type.IDENTIFIER, Type.NORMAL): 131 elif token.type in (Type.IDENTIFIER, Type.NORMAL):
123 # Dots are acceptable places to wrap. 132 # Dots are acceptable places to wrap.
124 line.insert(0, token.string.replace('.', ' ')) 133 line.insert(0, token.string.replace('.', ' '))
125 else: 134 else:
126 line.insert(0, token.string) 135 line.insert(0, token.string)
127 token = token.previous 136 token = token.previous
128 137
129 line = ''.join(line) 138 line = ''.join(line)
130 line = line.rstrip('\n\r\f') 139 line = line.rstrip('\n\r\f')
131 try: 140 try:
132 length = len(unicode(line, 'utf-8')) 141 length = len(unicode(line, 'utf-8'))
133 except: 142 except (LookupError, UnicodeDecodeError):
134 # Unknown encoding. The line length may be wrong, as was originally the 143 # Unknown encoding. The line length may be wrong, as was originally the
135 # case for utf-8 (see bug 1735846). For now just accept the default 144 # case for utf-8 (see bug 1735846). For now just accept the default
136 # length, but as we find problems we can either add test for other 145 # length, but as we find problems we can either add test for other
137 # possible encodings or return without an error to protect against 146 # possible encodings or return without an error to protect against
138 # false positives at the cost of more false negatives. 147 # false positives at the cost of more false negatives.
139 length = len(line) 148 length = len(line)
140 149
141 if length > self.MAX_LINE_LENGTH: 150 if length > EcmaScriptLintRules.max_line_length:
142 151
143 # If the line matches one of the exceptions, then it's ok. 152 # If the line matches one of the exceptions, then it's ok.
144 for long_line_regexp in self.GetLongLineExceptions(): 153 for long_line_regexp in self.GetLongLineExceptions():
145 if long_line_regexp.match(last_token.line): 154 if long_line_regexp.match(last_token.line):
146 return 155 return
147 156
148 # If the line consists of only one "word", or multiple words but all 157 # If the line consists of only one "word", or multiple words but all
149 # except one are ignoreable, then it's ok. 158 # except one are ignoreable, then it's ok.
150 parts = set(line.split()) 159 parts = set(line.split())
151 160
152 # We allow two "words" (type and name) when the line contains @param 161 # We allow two "words" (type and name) when the line contains @param
153 max = 1 162 max_parts = 1
154 if '@param' in parts: 163 if '@param' in parts:
155 max = 2 164 max_parts = 2
156 165
157 # Custom tags like @requires may have url like descriptions, so ignore 166 # Custom tags like @requires may have url like descriptions, so ignore
158 # the tag, similar to how we handle @see. 167 # the tag, similar to how we handle @see.
159 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags]) 168 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags])
160 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) > max): 169 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags))
161 self._HandleError(errors.LINE_TOO_LONG, 170 > max_parts):
171 self._HandleError(
172 errors.LINE_TOO_LONG,
162 'Line too long (%d characters).' % len(line), last_token) 173 'Line too long (%d characters).' % len(line), last_token)
163 174
164 def _CheckJsDocType(self, token): 175 def _CheckJsDocType(self, token):
165 """Checks the given type for style errors. 176 """Checks the given type for style errors.
166 177
167 Args: 178 Args:
168 token: The DOC_FLAG token for the flag whose type to check. 179 token: The DOC_FLAG token for the flag whose type to check.
169 """ 180 """
170 flag = token.attached_object 181 flag = token.attached_object
171 type = flag.type 182 flag_type = flag.type
172 if type and type is not None and not type.isspace(): 183 if flag_type and flag_type is not None and not flag_type.isspace():
173 pieces = self.TYPE_SPLIT.split(type) 184 pieces = self.TYPE_SPLIT.split(flag_type)
174 if len(pieces) == 1 and type.count('|') == 1 and ( 185 if len(pieces) == 1 and flag_type.count('|') == 1 and (
175 type.endswith('|null') or type.startswith('null|')): 186 flag_type.endswith('|null') or flag_type.startswith('null|')):
176 self._HandleError(errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL, 187 self._HandleError(
177 'Prefer "?Type" to "Type|null": "%s"' % type, token) 188 errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL,
189 'Prefer "?Type" to "Type|null": "%s"' % flag_type, token)
178 190
179 for p in pieces: 191 # TODO(user): We should do actual parsing of JsDoc types to report an
180 if p.count('|') and p.count('?'): 192 # error for wrong usage of '?' and '|' e.g. {?number|string|null} etc.
181 # TODO(robbyw): We should do actual parsing of JsDoc types. As is,
182 # this won't report an error for {number|Array.<string>?}, etc.
183 self._HandleError(errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE,
184 'JsDoc types cannot contain both "?" and "|": "%s"' % p, token)
185 193
186 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and ( 194 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and (
187 flag.type_start_token.type != Type.DOC_START_BRACE or 195 flag.type_start_token.type != Type.DOC_START_BRACE or
188 flag.type_end_token.type != Type.DOC_END_BRACE): 196 flag.type_end_token.type != Type.DOC_END_BRACE):
189 self._HandleError(errors.MISSING_BRACES_AROUND_TYPE, 197 self._HandleError(
198 errors.MISSING_BRACES_AROUND_TYPE,
190 'Type must always be surrounded by curly braces.', token) 199 'Type must always be surrounded by curly braces.', token)
191 200
192 def _CheckForMissingSpaceBeforeToken(self, token): 201 def _CheckForMissingSpaceBeforeToken(self, token):
193 """Checks for a missing space at the beginning of a token. 202 """Checks for a missing space at the beginning of a token.
194 203
195 Reports a MISSING_SPACE error if the token does not begin with a space or 204 Reports a MISSING_SPACE error if the token does not begin with a space or
196 the previous token doesn't end with a space and the previous token is on the 205 the previous token doesn't end with a space and the previous token is on the
197 same line as the token. 206 same line as the token.
198 207
199 Args: 208 Args:
200 token: The token being checked 209 token: The token being checked
201 """ 210 """
202 # TODO(user): Check if too many spaces? 211 # TODO(user): Check if too many spaces?
203 if (len(token.string) == len(token.string.lstrip()) and 212 if (len(token.string) == len(token.string.lstrip()) and
204 token.previous and token.line_number == token.previous.line_number and 213 token.previous and token.line_number == token.previous.line_number and
205 len(token.previous.string) - len(token.previous.string.rstrip()) == 0): 214 len(token.previous.string) - len(token.previous.string.rstrip()) == 0):
206 self._HandleError( 215 self._HandleError(
207 errors.MISSING_SPACE, 216 errors.MISSING_SPACE,
208 'Missing space before "%s"' % token.string, 217 'Missing space before "%s"' % token.string,
209 token, 218 token,
210 Position.AtBeginning()) 219 position=Position.AtBeginning())
220
221 def _CheckOperator(self, token):
222 """Checks an operator for spacing and line style.
223
224 Args:
225 token: The operator token.
226 """
227 last_code = token.metadata.last_code
228
229 if not self._ExpectSpaceBeforeOperator(token):
230 if (token.previous and token.previous.type == Type.WHITESPACE and
231 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)):
232 self._HandleError(
233 errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string,
234 token.previous, position=Position.All(token.previous.string))
235
236 elif (token.previous and
237 not token.previous.IsComment() and
238 token.previous.type in Type.EXPRESSION_ENDER_TYPES):
239 self._HandleError(errors.MISSING_SPACE,
240 'Missing space before "%s"' % token.string, token,
241 position=Position.AtBeginning())
242
243 # Check that binary operators are not used to start lines.
244 if ((not last_code or last_code.line_number != token.line_number) and
245 not token.metadata.IsUnaryOperator()):
246 self._HandleError(
247 errors.LINE_STARTS_WITH_OPERATOR,
248 'Binary operator should go on previous line "%s"' % token.string,
249 token)
211 250
212 def _ExpectSpaceBeforeOperator(self, token): 251 def _ExpectSpaceBeforeOperator(self, token):
213 """Returns whether a space should appear before the given operator token. 252 """Returns whether a space should appear before the given operator token.
214 253
215 Args: 254 Args:
216 token: The operator token. 255 token: The operator token.
217 256
218 Returns: 257 Returns:
219 Whether there should be a space before the token. 258 Whether there should be a space before the token.
220 """ 259 """
(...skipping 19 matching lines...) Expand all
240 279
241 Args: 280 Args:
242 token: The current token under consideration 281 token: The current token under consideration
243 state: parser_state object that indicates the current state in the page 282 state: parser_state object that indicates the current state in the page
244 """ 283 """
245 # Store some convenience variables 284 # Store some convenience variables
246 first_in_line = token.IsFirstInLine() 285 first_in_line = token.IsFirstInLine()
247 last_in_line = token.IsLastInLine() 286 last_in_line = token.IsLastInLine()
248 last_non_space_token = state.GetLastNonSpaceToken() 287 last_non_space_token = state.GetLastNonSpaceToken()
249 288
250 type = token.type 289 token_type = token.type
251 290
252 # Process the line change. 291 # Process the line change.
253 if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION): 292 if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION):
254 # TODO(robbyw): Support checking indentation in HTML files. 293 # TODO(robbyw): Support checking indentation in HTML files.
255 indentation_errors = self._indentation.CheckToken(token, state) 294 indentation_errors = self._indentation.CheckToken(token, state)
256 for indentation_error in indentation_errors: 295 for indentation_error in indentation_errors:
257 self._HandleError(*indentation_error) 296 self._HandleError(*indentation_error)
258 297
259 if last_in_line: 298 if last_in_line:
260 self._CheckLineLength(token, state) 299 self._CheckLineLength(token, state)
261 300
262 if type == Type.PARAMETERS: 301 if token_type == Type.PARAMETERS:
263 # Find missing spaces in parameter lists. 302 # Find missing spaces in parameter lists.
264 if self.MISSING_PARAMETER_SPACE.search(token.string): 303 if self.MISSING_PARAMETER_SPACE.search(token.string):
304 fix_data = ', '.join([s.strip() for s in token.string.split(',')])
265 self._HandleError(errors.MISSING_SPACE, 'Missing space after ","', 305 self._HandleError(errors.MISSING_SPACE, 'Missing space after ","',
266 token) 306 token, position=None, fix_data=fix_data.strip())
267 307
268 # Find extra spaces at the beginning of parameter lists. Make sure 308 # Find extra spaces at the beginning of parameter lists. Make sure
269 # we aren't at the beginning of a continuing multi-line list. 309 # we aren't at the beginning of a continuing multi-line list.
270 if not first_in_line: 310 if not first_in_line:
271 space_count = len(token.string) - len(token.string.lstrip()) 311 space_count = len(token.string) - len(token.string.lstrip())
272 if space_count: 312 if space_count:
273 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("', 313 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("',
274 token, Position(0, space_count)) 314 token, position=Position(0, space_count))
275 315
276 elif (type == Type.START_BLOCK and 316 elif (token_type == Type.START_BLOCK and
277 token.metadata.context.type == Context.BLOCK): 317 token.metadata.context.type == Context.BLOCK):
278 self._CheckForMissingSpaceBeforeToken(token) 318 self._CheckForMissingSpaceBeforeToken(token)
279 319
280 elif type == Type.END_BLOCK: 320 elif token_type == Type.END_BLOCK:
281 # This check is for object literal end block tokens, but there is no need 321 # This check is for object literal end block tokens, but there is no need
282 # to test that condition since a comma at the end of any other kind of 322 # to test that condition since a comma at the end of any other kind of
283 # block is undoubtedly a parse error. 323 # block is undoubtedly a parse error.
284 last_code = token.metadata.last_code 324 last_code = token.metadata.last_code
285 if last_code.IsOperator(','): 325 if last_code.IsOperator(','):
286 self._HandleError(errors.COMMA_AT_END_OF_LITERAL, 326 self._HandleError(
327 errors.COMMA_AT_END_OF_LITERAL,
287 'Illegal comma at end of object literal', last_code, 328 'Illegal comma at end of object literal', last_code,
288 Position.All(last_code.string)) 329 position=Position.All(last_code.string))
289 330
290 if state.InFunction() and state.IsFunctionClose(): 331 if state.InFunction() and state.IsFunctionClose():
291 is_immediately_called = (token.next and 332 is_immediately_called = (token.next and
292 token.next.type == Type.START_PAREN) 333 token.next.type == Type.START_PAREN)
293 if state.InTopLevelFunction(): 334 if state.InTopLevelFunction():
294 # When the function was top-level and not immediately called, check 335 # A semicolons should not be included at the end of a function
295 # that it's terminated by a semi-colon. 336 # declaration.
296 if state.InAssignedFunction(): 337 if not state.InAssignedFunction():
297 if not is_immediately_called and (last_in_line or
298 not token.next.type == Type.SEMICOLON):
299 self._HandleError(errors.MISSING_SEMICOLON_AFTER_FUNCTION,
300 'Missing semicolon after function assigned to a variable',
301 token, Position.AtEnd(token.string))
302 else:
303 if not last_in_line and token.next.type == Type.SEMICOLON: 338 if not last_in_line and token.next.type == Type.SEMICOLON:
304 self._HandleError(errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, 339 self._HandleError(
340 errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
305 'Illegal semicolon after function declaration', 341 'Illegal semicolon after function declaration',
306 token.next, Position.All(token.next.string)) 342 token.next, position=Position.All(token.next.string))
307 343
308 if (state.InInterfaceMethod() and last_code.type != Type.START_BLOCK): 344 # A semicolon should be included at the end of a function expression
345 # that is not immediately called.
346 if state.InAssignedFunction():
347 if not is_immediately_called and (
348 last_in_line or token.next.type != Type.SEMICOLON):
349 self._HandleError(
350 errors.MISSING_SEMICOLON_AFTER_FUNCTION,
351 'Missing semicolon after function assigned to a variable',
352 token, position=Position.AtEnd(token.string))
353
354 if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK:
309 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, 355 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
310 'Interface methods cannot contain code', last_code) 356 'Interface methods cannot contain code', last_code)
311 357
312 elif (state.IsBlockClose() and 358 elif (state.IsBlockClose() and
313 token.next and token.next.type == Type.SEMICOLON): 359 token.next and token.next.type == Type.SEMICOLON):
314 self._HandleError(errors.REDUNDANT_SEMICOLON, 360 if (last_code.metadata.context.parent.type != Context.OBJECT_LITERAL
315 'No semicolon is required to end a code block', 361 and last_code.metadata.context.type != Context.OBJECT_LITERAL):
316 token.next, Position.All(token.next.string)) 362 self._HandleError(
363 errors.REDUNDANT_SEMICOLON,
364 'No semicolon is required to end a code block',
365 token.next, position=Position.All(token.next.string))
317 366
318 elif type == Type.SEMICOLON: 367 elif token_type == Type.SEMICOLON:
319 if token.previous and token.previous.type == Type.WHITESPACE: 368 if token.previous and token.previous.type == Type.WHITESPACE:
320 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ";"', 369 self._HandleError(
321 token.previous, Position.All(token.previous.string)) 370 errors.EXTRA_SPACE, 'Extra space before ";"',
371 token.previous, position=Position.All(token.previous.string))
322 372
323 if token.next and token.next.line_number == token.line_number: 373 if token.next and token.next.line_number == token.line_number:
324 if token.metadata.context.type != Context.FOR_GROUP_BLOCK: 374 if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
325 # TODO(robbyw): Error about no multi-statement lines. 375 # TODO(robbyw): Error about no multi-statement lines.
326 pass 376 pass
327 377
328 elif token.next.type not in ( 378 elif token.next.type not in (
329 Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN): 379 Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN):
330 self._HandleError(errors.MISSING_SPACE, 380 self._HandleError(
381 errors.MISSING_SPACE,
331 'Missing space after ";" in for statement', 382 'Missing space after ";" in for statement',
332 token.next, 383 token.next,
333 Position.AtBeginning()) 384 position=Position.AtBeginning())
334 385
335 last_code = token.metadata.last_code 386 last_code = token.metadata.last_code
336 if last_code and last_code.type == Type.SEMICOLON: 387 if last_code and last_code.type == Type.SEMICOLON:
337 # Allow a single double semi colon in for loops for cases like: 388 # Allow a single double semi colon in for loops for cases like:
338 # for (;;) { }. 389 # for (;;) { }.
339 # NOTE(user): This is not a perfect check, and will not throw an error 390 # NOTE(user): This is not a perfect check, and will not throw an error
340 # for cases like: for (var i = 0;; i < n; i++) {}, but then your code 391 # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
341 # probably won't work either. 392 # probably won't work either.
342 for_token = tokenutil.CustomSearch(last_code, 393 for_token = tokenutil.CustomSearch(
394 last_code,
343 lambda token: token.type == Type.KEYWORD and token.string == 'for', 395 lambda token: token.type == Type.KEYWORD and token.string == 'for',
344 end_func=lambda token: token.type == Type.SEMICOLON, 396 end_func=lambda token: token.type == Type.SEMICOLON,
345 distance=None, 397 distance=None,
346 reverse=True) 398 reverse=True)
347 399
348 if not for_token: 400 if not for_token:
349 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', 401 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon',
350 token, Position.All(token.string)) 402 token, position=Position.All(token.string))
351 403
352 elif type == Type.START_PAREN: 404 elif token_type == Type.START_PAREN:
353 if token.previous and token.previous.type == Type.KEYWORD: 405 if token.previous and token.previous.type == Type.KEYWORD:
354 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', 406 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("',
355 token, Position.AtBeginning()) 407 token, position=Position.AtBeginning())
356 elif token.previous and token.previous.type == Type.WHITESPACE: 408 elif token.previous and token.previous.type == Type.WHITESPACE:
357 before_space = token.previous.previous 409 before_space = token.previous.previous
358 if (before_space and before_space.line_number == token.line_number and 410 if (before_space and before_space.line_number == token.line_number and
359 before_space.type == Type.IDENTIFIER): 411 before_space.type == Type.IDENTIFIER):
360 self._HandleError(errors.EXTRA_SPACE, 'Extra space before "("', 412 self._HandleError(
361 token.previous, Position.All(token.previous.string)) 413 errors.EXTRA_SPACE, 'Extra space before "("',
414 token.previous, position=Position.All(token.previous.string))
362 415
363 elif type == Type.START_BRACKET: 416 elif token_type == Type.START_BRACKET:
364 self._HandleStartBracket(token, last_non_space_token) 417 self._HandleStartBracket(token, last_non_space_token)
365 elif type in (Type.END_PAREN, Type.END_BRACKET): 418 elif token_type in (Type.END_PAREN, Type.END_BRACKET):
366 # Ensure there is no space before closing parentheses, except when 419 # Ensure there is no space before closing parentheses, except when
367 # it's in a for statement with an omitted section, or when it's at the 420 # it's in a for statement with an omitted section, or when it's at the
368 # beginning of a line. 421 # beginning of a line.
369 if (token.previous and token.previous.type == Type.WHITESPACE and 422 if (token.previous and token.previous.type == Type.WHITESPACE and
370 not token.previous.IsFirstInLine() and 423 not token.previous.IsFirstInLine() and
371 not (last_non_space_token and last_non_space_token.line_number == 424 not (last_non_space_token and last_non_space_token.line_number ==
372 token.line_number and 425 token.line_number and
373 last_non_space_token.type == Type.SEMICOLON)): 426 last_non_space_token.type == Type.SEMICOLON)):
374 self._HandleError(errors.EXTRA_SPACE, 'Extra space before "%s"' % 427 self._HandleError(
375 token.string, token.previous, Position.All(token.previous.string)) 428 errors.EXTRA_SPACE, 'Extra space before "%s"' %
429 token.string, token.previous,
430 position=Position.All(token.previous.string))
376 431
377 if token.type == Type.END_BRACKET: 432 if token.type == Type.END_BRACKET:
378 last_code = token.metadata.last_code 433 last_code = token.metadata.last_code
379 if last_code.IsOperator(','): 434 if last_code.IsOperator(','):
380 self._HandleError(errors.COMMA_AT_END_OF_LITERAL, 435 self._HandleError(
436 errors.COMMA_AT_END_OF_LITERAL,
381 'Illegal comma at end of array literal', last_code, 437 'Illegal comma at end of array literal', last_code,
382 Position.All(last_code.string)) 438 position=Position.All(last_code.string))
383 439
384 elif type == Type.WHITESPACE: 440 elif token_type == Type.WHITESPACE:
385 if self.ILLEGAL_TAB.search(token.string): 441 if self.ILLEGAL_TAB.search(token.string):
386 if token.IsFirstInLine(): 442 if token.IsFirstInLine():
387 if token.next: 443 if token.next:
388 self._HandleError(errors.ILLEGAL_TAB, 444 self._HandleError(
445 errors.ILLEGAL_TAB,
389 'Illegal tab in whitespace before "%s"' % token.next.string, 446 'Illegal tab in whitespace before "%s"' % token.next.string,
390 token, Position.All(token.string)) 447 token, position=Position.All(token.string))
391 else: 448 else:
392 self._HandleError(errors.ILLEGAL_TAB, 449 self._HandleError(
450 errors.ILLEGAL_TAB,
393 'Illegal tab in whitespace', 451 'Illegal tab in whitespace',
394 token, Position.All(token.string)) 452 token, position=Position.All(token.string))
395 else: 453 else:
396 self._HandleError(errors.ILLEGAL_TAB, 454 self._HandleError(
455 errors.ILLEGAL_TAB,
397 'Illegal tab in whitespace after "%s"' % token.previous.string, 456 'Illegal tab in whitespace after "%s"' % token.previous.string,
398 token, Position.All(token.string)) 457 token, position=Position.All(token.string))
399 458
400 # Check whitespace length if it's not the first token of the line and 459 # Check whitespace length if it's not the first token of the line and
401 # if it's not immediately before a comment. 460 # if it's not immediately before a comment.
402 if last_in_line: 461 if last_in_line:
403 # Check for extra whitespace at the end of a line. 462 # Check for extra whitespace at the end of a line.
404 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line', 463 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line',
405 token, Position.All(token.string)) 464 token, position=Position.All(token.string))
406 elif not first_in_line and not token.next.IsComment(): 465 elif not first_in_line and not token.next.IsComment():
407 if token.length > 1: 466 if token.length > 1:
408 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "%s"' % 467 self._HandleError(
468 errors.EXTRA_SPACE, 'Extra space after "%s"' %
409 token.previous.string, token, 469 token.previous.string, token,
410 Position(1, len(token.string) - 1)) 470 position=Position(1, len(token.string) - 1))
411 471
412 elif type == Type.OPERATOR: 472 elif token_type == Type.OPERATOR:
413 last_code = token.metadata.last_code 473 self._CheckOperator(token)
414 474 elif token_type == Type.DOC_FLAG:
415 if not self._ExpectSpaceBeforeOperator(token):
416 if (token.previous and token.previous.type == Type.WHITESPACE and
417 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)):
418 self._HandleError(errors.EXTRA_SPACE,
419 'Extra space before "%s"' % token.string, token.previous,
420 Position.All(token.previous.string))
421
422 elif (token.previous and
423 not token.previous.IsComment() and
424 token.previous.type in Type.EXPRESSION_ENDER_TYPES):
425 self._HandleError(errors.MISSING_SPACE,
426 'Missing space before "%s"' % token.string, token,
427 Position.AtBeginning())
428
429 # Check that binary operators are not used to start lines.
430 if ((not last_code or last_code.line_number != token.line_number) and
431 not token.metadata.IsUnaryOperator()):
432 self._HandleError(errors.LINE_STARTS_WITH_OPERATOR,
433 'Binary operator should go on previous line "%s"' % token.string,
434 token)
435
436 elif type == Type.DOC_FLAG:
437 flag = token.attached_object 475 flag = token.attached_object
438 476
439 if flag.flag_type == 'bug': 477 if flag.flag_type == 'bug':
440 # TODO(robbyw): Check for exactly 1 space on the left. 478 # TODO(robbyw): Check for exactly 1 space on the left.
441 string = token.next.string.lstrip() 479 string = token.next.string.lstrip()
442 string = string.split(' ', 1)[0] 480 string = string.split(' ', 1)[0]
443 481
444 if not string.isdigit(): 482 if not string.isdigit():
445 self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG, 483 self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
446 '@bug should be followed by a bug number', token) 484 '@bug should be followed by a bug number', token)
447 485
448 elif flag.flag_type == 'suppress': 486 elif flag.flag_type == 'suppress':
449 if flag.type is None: 487 if flag.type is None:
450 # A syntactically invalid suppress tag will get tokenized as a normal 488 # A syntactically invalid suppress tag will get tokenized as a normal
451 # flag, indicating an error. 489 # flag, indicating an error.
452 self._HandleError(errors.INCORRECT_SUPPRESS_SYNTAX, 490 self._HandleError(
491 errors.INCORRECT_SUPPRESS_SYNTAX,
453 'Invalid suppress syntax: should be @suppress {errortype}. ' 492 'Invalid suppress syntax: should be @suppress {errortype}. '
454 'Spaces matter.', token) 493 'Spaces matter.', token)
455 else: 494 else:
456 for suppress_type in flag.type.split('|'): 495 for suppress_type in re.split(r'\||,', flag.type):
457 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: 496 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES:
458 self._HandleError(errors.INVALID_SUPPRESS_TYPE, 497 self._HandleError(
459 'Invalid suppression type: %s' % suppress_type, 498 errors.INVALID_SUPPRESS_TYPE,
460 token) 499 'Invalid suppression type: %s' % suppress_type, token)
461 500
462 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and 501 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and
463 flag.flag_type == 'author'): 502 flag.flag_type == 'author'):
464 # TODO(user): In non strict mode check the author tag for as much as 503 # TODO(user): In non strict mode check the author tag for as much as
465 # it exists, though the full form checked below isn't required. 504 # it exists, though the full form checked below isn't required.
466 string = token.next.string 505 string = token.next.string
467 result = self.AUTHOR_SPEC.match(string) 506 result = self.AUTHOR_SPEC.match(string)
468 if not result: 507 if not result:
469 self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION, 508 self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION,
470 'Author tag line should be of the form: ' 509 'Author tag line should be of the form: '
471 '@author foo@somewhere.com (Your Name)', 510 '@author foo@somewhere.com (Your Name)',
472 token.next) 511 token.next)
473 else: 512 else:
474 # Check spacing between email address and name. Do this before 513 # Check spacing between email address and name. Do this before
475 # checking earlier spacing so positions are easier to calculate for 514 # checking earlier spacing so positions are easier to calculate for
476 # autofixing. 515 # autofixing.
477 num_spaces = len(result.group(2)) 516 num_spaces = len(result.group(2))
478 if num_spaces < 1: 517 if num_spaces < 1:
479 self._HandleError(errors.MISSING_SPACE, 518 self._HandleError(errors.MISSING_SPACE,
480 'Missing space after email address', 519 'Missing space after email address',
481 token.next, Position(result.start(2), 0)) 520 token.next, position=Position(result.start(2), 0))
482 elif num_spaces > 1: 521 elif num_spaces > 1:
483 self._HandleError(errors.EXTRA_SPACE, 522 self._HandleError(
484 'Extra space after email address', 523 errors.EXTRA_SPACE, 'Extra space after email address',
485 token.next, 524 token.next,
486 Position(result.start(2) + 1, num_spaces - 1)) 525 position=Position(result.start(2) + 1, num_spaces - 1))
487 526
488 # Check for extra spaces before email address. Can't be too few, if 527 # Check for extra spaces before email address. Can't be too few, if
489 # not at least one we wouldn't match @author tag. 528 # not at least one we wouldn't match @author tag.
490 num_spaces = len(result.group(1)) 529 num_spaces = len(result.group(1))
491 if num_spaces > 1: 530 if num_spaces > 1:
492 self._HandleError(errors.EXTRA_SPACE, 531 self._HandleError(errors.EXTRA_SPACE,
493 'Extra space before email address', 532 'Extra space before email address',
494 token.next, Position(1, num_spaces - 1)) 533 token.next, position=Position(1, num_spaces - 1))
495 534
496 elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and 535 elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and
497 not self._limited_doc_checks): 536 not self._limited_doc_checks):
498 if flag.flag_type == 'param': 537 if flag.flag_type == 'param':
499 if flag.name is None: 538 if flag.name is None:
500 self._HandleError(errors.MISSING_JSDOC_PARAM_NAME, 539 self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
501 'Missing name in @param tag', token) 540 'Missing name in @param tag', token)
502 541
503 if not flag.description or flag.description is None: 542 if not flag.description or flag.description is None:
504 flag_name = token.type 543 flag_name = token.type
505 if 'name' in token.values: 544 if 'name' in token.values:
506 flag_name = '@' + token.values['name'] 545 flag_name = '@' + token.values['name']
507 self._HandleError(errors.MISSING_JSDOC_TAG_DESCRIPTION, 546
508 'Missing description in %s tag' % flag_name, token) 547 if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED:
548 self._HandleError(
549 errors.MISSING_JSDOC_TAG_DESCRIPTION,
550 'Missing description in %s tag' % flag_name, token)
509 else: 551 else:
510 self._CheckForMissingSpaceBeforeToken(flag.description_start_token) 552 self._CheckForMissingSpaceBeforeToken(flag.description_start_token)
511 553
512 # We want punctuation to be inside of any tags ending a description,
513 # so strip tags before checking description. See bug 1127192. Note
514 # that depending on how lines break, the real description end token
515 # may consist only of stripped html and the effective end token can
516 # be different.
517 end_token = flag.description_end_token
518 end_string = htmlutil.StripTags(end_token.string).strip()
519 while (end_string == '' and not
520 end_token.type in Type.FLAG_ENDING_TYPES):
521 end_token = end_token.previous
522 if end_token.type in Type.FLAG_DESCRIPTION_TYPES:
523 end_string = htmlutil.StripTags(end_token.string).rstrip()
524
525 if not (end_string.endswith('.') or end_string.endswith('?') or
526 end_string.endswith('!')):
527 # Find the position for the missing punctuation, inside of any html
528 # tags.
529 desc_str = end_token.string.rstrip()
530 while desc_str.endswith('>'):
531 start_tag_index = desc_str.rfind('<')
532 if start_tag_index < 0:
533 break
534 desc_str = desc_str[:start_tag_index].rstrip()
535 end_position = Position(len(desc_str), 0)
536
537 self._HandleError(
538 errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER,
539 ('%s descriptions must end with valid punctuation such as a '
540 'period.' % token.string),
541 end_token, end_position)
542
543 if flag.flag_type in state.GetDocFlag().HAS_TYPE: 554 if flag.flag_type in state.GetDocFlag().HAS_TYPE:
544 if flag.type_start_token is not None: 555 if flag.type_start_token is not None:
545 self._CheckForMissingSpaceBeforeToken( 556 self._CheckForMissingSpaceBeforeToken(
546 token.attached_object.type_start_token) 557 token.attached_object.type_start_token)
547 558
548 if flag.type and flag.type != '' and not flag.type.isspace(): 559 if flag.type and not flag.type.isspace():
549 self._CheckJsDocType(token) 560 self._CheckJsDocType(token)
550 561
551 if type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): 562 if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
552 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and 563 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and
553 token.values['name'] not in FLAGS.custom_jsdoc_tags): 564 token.values['name'] not in FLAGS.custom_jsdoc_tags):
554 self._HandleError(errors.INVALID_JSDOC_TAG, 565 self._HandleError(
555 'Invalid JsDoc tag: %s' % token.values['name'], token) 566 errors.INVALID_JSDOC_TAG,
567 'Invalid JsDoc tag: %s' % token.values['name'], token)
556 568
557 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and 569 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and
558 token.values['name'] == 'inheritDoc' and 570 token.values['name'] == 'inheritDoc' and
559 type == Type.DOC_INLINE_FLAG): 571 token_type == Type.DOC_INLINE_FLAG):
560 self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC, 572 self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
561 'Unnecessary braces around @inheritDoc', 573 'Unnecessary braces around @inheritDoc',
562 token) 574 token)
563 575
564 elif type == Type.SIMPLE_LVALUE: 576 elif token_type == Type.SIMPLE_LVALUE:
565 identifier = token.values['identifier'] 577 identifier = token.values['identifier']
566 578
567 if ((not state.InFunction() or state.InConstructor()) and 579 if ((not state.InFunction() or state.InConstructor()) and
568 not state.InParentheses() and not state.InObjectLiteralDescendant()): 580 state.InTopLevel() and not state.InObjectLiteralDescendant()):
569 jsdoc = state.GetDocComment() 581 jsdoc = state.GetDocComment()
570 if not state.HasDocComment(identifier): 582 if not state.HasDocComment(identifier):
571 # Only test for documentation on identifiers with .s in them to 583 # Only test for documentation on identifiers with .s in them to
572 # avoid checking things like simple variables. We don't require 584 # avoid checking things like simple variables. We don't require
573 # documenting assignments to .prototype itself (bug 1880803). 585 # documenting assignments to .prototype itself (bug 1880803).
574 if (not state.InConstructor() and 586 if (not state.InConstructor() and
575 identifier.find('.') != -1 and not 587 identifier.find('.') != -1 and not
576 identifier.endswith('.prototype') and not 588 identifier.endswith('.prototype') and not
577 self._limited_doc_checks): 589 self._limited_doc_checks):
578 comment = state.GetLastComment() 590 comment = state.GetLastComment()
579 if not (comment and comment.lower().count('jsdoc inherited')): 591 if not (comment and comment.lower().count('jsdoc inherited')):
580 self._HandleError(errors.MISSING_MEMBER_DOCUMENTATION, 592 self._HandleError(
593 errors.MISSING_MEMBER_DOCUMENTATION,
581 "No docs found for member '%s'" % identifier, 594 "No docs found for member '%s'" % identifier,
582 token); 595 token)
583 elif jsdoc and (not state.InConstructor() or 596 elif jsdoc and (not state.InConstructor() or
584 identifier.startswith('this.')): 597 identifier.startswith('this.')):
585 # We are at the top level and the function/member is documented. 598 # We are at the top level and the function/member is documented.
586 if identifier.endswith('_') and not identifier.endswith('__'): 599 if identifier.endswith('_') and not identifier.endswith('__'):
587 # Can have a private class which inherits documentation from a 600 # Can have a private class which inherits documentation from a
588 # public superclass. 601 # public superclass.
589 # 602 #
590 # @inheritDoc is deprecated in favor of using @override, and they 603 # @inheritDoc is deprecated in favor of using @override, and they
591 if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor') 604 if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor')
592 and not ('accessControls' in jsdoc.suppressions)): 605 and ('accessControls' not in jsdoc.suppressions)):
593 self._HandleError(errors.INVALID_OVERRIDE_PRIVATE, 606 self._HandleError(
607 errors.INVALID_OVERRIDE_PRIVATE,
594 '%s should not override a private member.' % identifier, 608 '%s should not override a private member.' % identifier,
595 jsdoc.GetFlag('override').flag_token) 609 jsdoc.GetFlag('override').flag_token)
596 if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor') 610 if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor')
597 and not ('accessControls' in jsdoc.suppressions)): 611 and ('accessControls' not in jsdoc.suppressions)):
598 self._HandleError(errors.INVALID_INHERIT_DOC_PRIVATE, 612 self._HandleError(
613 errors.INVALID_INHERIT_DOC_PRIVATE,
599 '%s should not inherit from a private member.' % identifier, 614 '%s should not inherit from a private member.' % identifier,
600 jsdoc.GetFlag('inheritDoc').flag_token) 615 jsdoc.GetFlag('inheritDoc').flag_token)
601 if (not jsdoc.HasFlag('private') and 616 if (not jsdoc.HasFlag('private') and
602 not ('underscore' in jsdoc.suppressions) and not 617 ('underscore' not in jsdoc.suppressions) and not
603 ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and 618 ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and
604 ('accessControls' in jsdoc.suppressions))): 619 ('accessControls' in jsdoc.suppressions))):
605 self._HandleError(errors.MISSING_PRIVATE, 620 self._HandleError(
621 errors.MISSING_PRIVATE,
606 'Member "%s" must have @private JsDoc.' % 622 'Member "%s" must have @private JsDoc.' %
607 identifier, token) 623 identifier, token)
608 if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions: 624 if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions:
609 self._HandleError(errors.UNNECESSARY_SUPPRESS, 625 self._HandleError(
626 errors.UNNECESSARY_SUPPRESS,
610 '@suppress {underscore} is not necessary with @private', 627 '@suppress {underscore} is not necessary with @private',
611 jsdoc.suppressions['underscore']) 628 jsdoc.suppressions['underscore'])
612 elif (jsdoc.HasFlag('private') and 629 elif (jsdoc.HasFlag('private') and
613 not self.InExplicitlyTypedLanguage()): 630 not self.InExplicitlyTypedLanguage()):
614 # It is convention to hide public fields in some ECMA 631 # It is convention to hide public fields in some ECMA
615 # implementations from documentation using the @private tag. 632 # implementations from documentation using the @private tag.
616 self._HandleError(errors.EXTRA_PRIVATE, 633 self._HandleError(
634 errors.EXTRA_PRIVATE,
617 'Member "%s" must not have @private JsDoc' % 635 'Member "%s" must not have @private JsDoc' %
618 identifier, token) 636 identifier, token)
619 637
620 # These flags are only legal on localizable message definitions; 638 # These flags are only legal on localizable message definitions;
621 # such variables always begin with the prefix MSG_. 639 # such variables always begin with the prefix MSG_.
622 for f in ('desc', 'hidden', 'meaning'): 640 for f in ('desc', 'hidden', 'meaning'):
623 if (jsdoc.HasFlag(f) 641 if (jsdoc.HasFlag(f)
624 and not identifier.startswith('MSG_') 642 and not identifier.startswith('MSG_')
625 and identifier.find('.MSG_') == -1): 643 and identifier.find('.MSG_') == -1):
626 self._HandleError(errors.INVALID_USE_OF_DESC_TAG, 644 self._HandleError(
645 errors.INVALID_USE_OF_DESC_TAG,
627 'Member "%s" should not have @%s JsDoc' % (identifier, f), 646 'Member "%s" should not have @%s JsDoc' % (identifier, f),
628 token) 647 token)
629 648
630 # Check for illegaly assigning live objects as prototype property values. 649 # Check for illegaly assigning live objects as prototype property values.
631 index = identifier.find('.prototype.') 650 index = identifier.find('.prototype.')
632 # Ignore anything with additional .s after the prototype. 651 # Ignore anything with additional .s after the prototype.
633 if index != -1 and identifier.find('.', index + 11) == -1: 652 if index != -1 and identifier.find('.', index + 11) == -1:
634 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) 653 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
635 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES) 654 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES)
636 if next_code and ( 655 if next_code and (
637 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or 656 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or
638 next_code.IsOperator('new')): 657 next_code.IsOperator('new')):
639 self._HandleError(errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE, 658 self._HandleError(
659 errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
640 'Member %s cannot have a non-primitive value' % identifier, 660 'Member %s cannot have a non-primitive value' % identifier,
641 token) 661 token)
642 662
643 elif type == Type.END_PARAMETERS: 663 elif token_type == Type.END_PARAMETERS:
644 # Find extra space at the end of parameter lists. We check the token 664 # Find extra space at the end of parameter lists. We check the token
645 # prior to the current one when it is a closing paren. 665 # prior to the current one when it is a closing paren.
646 if (token.previous and token.previous.type == Type.PARAMETERS 666 if (token.previous and token.previous.type == Type.PARAMETERS
647 and self.ENDS_WITH_SPACE.search(token.previous.string)): 667 and self.ENDS_WITH_SPACE.search(token.previous.string)):
648 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"', 668 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
649 token.previous) 669 token.previous)
650 670
651 jsdoc = state.GetDocComment() 671 jsdoc = state.GetDocComment()
652 if state.GetFunction().is_interface: 672 if state.GetFunction().is_interface:
653 if token.previous and token.previous.type == Type.PARAMETERS: 673 if token.previous and token.previous.type == Type.PARAMETERS:
654 self._HandleError(errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS, 674 self._HandleError(
675 errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
655 'Interface constructor cannot have parameters', 676 'Interface constructor cannot have parameters',
656 token.previous) 677 token.previous)
657 elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see') 678 elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
658 and not jsdoc.InheritsDocumentation() 679 and not jsdoc.InheritsDocumentation()
659 and not state.InObjectLiteralDescendant() and not 680 and not state.InObjectLiteralDescendant() and not
660 jsdoc.IsInvalidated()): 681 jsdoc.IsInvalidated()):
661 distance, edit = jsdoc.CompareParameters(state.GetParams()) 682 distance, edit = jsdoc.CompareParameters(state.GetParams())
662 if distance: 683 if distance:
663 params_iter = iter(state.GetParams()) 684 params_iter = iter(state.GetParams())
664 docs_iter = iter(jsdoc.ordered_params) 685 docs_iter = iter(jsdoc.ordered_params)
665 686
666 for op in edit: 687 for op in edit:
667 if op == 'I': 688 if op == 'I':
668 # Insertion. 689 # Insertion.
669 # Parsing doc comments is the same for all languages 690 # Parsing doc comments is the same for all languages
670 # but some languages care about parameters that don't have 691 # but some languages care about parameters that don't have
671 # doc comments and some languages don't care. 692 # doc comments and some languages don't care.
672 # Languages that don't allow variables to by typed such as 693 # Languages that don't allow variables to by typed such as
673 # JavaScript care but languages such as ActionScript or Java 694 # JavaScript care but languages such as ActionScript or Java
674 # that allow variables to be typed don't care. 695 # that allow variables to be typed don't care.
675 if not self._limited_doc_checks: 696 if not self._limited_doc_checks:
676 self.HandleMissingParameterDoc(token, params_iter.next()) 697 self.HandleMissingParameterDoc(token, params_iter.next())
677 698
678 elif op == 'D': 699 elif op == 'D':
679 # Deletion 700 # Deletion
680 self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION, 701 self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION,
681 'Found docs for non-existing parameter: "%s"' % 702 'Found docs for non-existing parameter: "%s"' %
682 docs_iter.next(), token) 703 docs_iter.next(), token)
683 elif op == 'S': 704 elif op == 'S':
684 # Substitution 705 # Substitution
685 if not self._limited_doc_checks: 706 if not self._limited_doc_checks:
686 self._HandleError(errors.WRONG_PARAMETER_DOCUMENTATION, 707 self._HandleError(
708 errors.WRONG_PARAMETER_DOCUMENTATION,
687 'Parameter mismatch: got "%s", expected "%s"' % 709 'Parameter mismatch: got "%s", expected "%s"' %
688 (params_iter.next(), docs_iter.next()), token) 710 (params_iter.next(), docs_iter.next()), token)
689 711
690 else: 712 else:
691 # Equality - just advance the iterators 713 # Equality - just advance the iterators
692 params_iter.next() 714 params_iter.next()
693 docs_iter.next() 715 docs_iter.next()
694 716
695 elif type == Type.STRING_TEXT: 717 elif token_type == Type.STRING_TEXT:
696 # If this is the first token after the start of the string, but it's at 718 # If this is the first token after the start of the string, but it's at
697 # the end of a line, we know we have a multi-line string. 719 # the end of a line, we know we have a multi-line string.
698 if token.previous.type in (Type.SINGLE_QUOTE_STRING_START, 720 if token.previous.type in (
721 Type.SINGLE_QUOTE_STRING_START,
699 Type.DOUBLE_QUOTE_STRING_START) and last_in_line: 722 Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
700 self._HandleError(errors.MULTI_LINE_STRING, 723 self._HandleError(errors.MULTI_LINE_STRING,
701 'Multi-line strings are not allowed', token) 724 'Multi-line strings are not allowed', token)
702
703 725
704 # This check is orthogonal to the ones above, and repeats some types, so 726 # This check is orthogonal to the ones above, and repeats some types, so
705 # it is a plain if and not an elif. 727 # it is a plain if and not an elif.
706 if token.type in Type.COMMENT_TYPES: 728 if token.type in Type.COMMENT_TYPES:
707 if self.ILLEGAL_TAB.search(token.string): 729 if self.ILLEGAL_TAB.search(token.string):
708 self._HandleError(errors.ILLEGAL_TAB, 730 self._HandleError(errors.ILLEGAL_TAB,
709 'Illegal tab in comment "%s"' % token.string, token) 731 'Illegal tab in comment "%s"' % token.string, token)
710 732
711 trimmed = token.string.rstrip() 733 trimmed = token.string.rstrip()
712 if last_in_line and token.string != trimmed: 734 if last_in_line and token.string != trimmed:
713 # Check for extra whitespace at the end of a line. 735 # Check for extra whitespace at the end of a line.
714 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line', 736 self._HandleError(
715 token, Position(len(trimmed), len(token.string) - len(trimmed))) 737 errors.EXTRA_SPACE, 'Extra space at end of line', token,
738 position=Position(len(trimmed), len(token.string) - len(trimmed)))
716 739
717 # This check is also orthogonal since it is based on metadata. 740 # This check is also orthogonal since it is based on metadata.
718 if token.metadata.is_implied_semicolon: 741 if token.metadata.is_implied_semicolon:
719 self._HandleError(errors.MISSING_SEMICOLON, 742 self._HandleError(errors.MISSING_SEMICOLON,
720 'Missing semicolon at end of line', token) 743 'Missing semicolon at end of line', token)
721 744
722 def _HandleStartBracket(self, token, last_non_space_token): 745 def _HandleStartBracket(self, token, last_non_space_token):
723 """Handles a token that is an open bracket. 746 """Handles a token that is an open bracket.
724 747
725 Args: 748 Args:
726 token: The token to handle. 749 token: The token to handle.
727 last_non_space_token: The last token that was not a space. 750 last_non_space_token: The last token that was not a space.
728 """ 751 """
729 if (not token.IsFirstInLine() and token.previous.type == Type.WHITESPACE and 752 if (not token.IsFirstInLine() and token.previous.type == Type.WHITESPACE and
730 last_non_space_token and 753 last_non_space_token and
731 last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES): 754 last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES):
732 self._HandleError(errors.EXTRA_SPACE, 'Extra space before "["', 755 self._HandleError(
733 token.previous, Position.All(token.previous.string)) 756 errors.EXTRA_SPACE, 'Extra space before "["',
757 token.previous, position=Position.All(token.previous.string))
734 # If the [ token is the first token in a line we shouldn't complain 758 # If the [ token is the first token in a line we shouldn't complain
735 # about a missing space before [. This is because some Ecma script 759 # about a missing space before [. This is because some Ecma script
736 # languages allow syntax like: 760 # languages allow syntax like:
737 # [Annotation] 761 # [Annotation]
738 # class MyClass {...} 762 # class MyClass {...}
739 # So we don't want to blindly warn about missing spaces before [. 763 # So we don't want to blindly warn about missing spaces before [.
740 # In the the future, when rules for computing exactly how many spaces 764 # In the the future, when rules for computing exactly how many spaces
741 # lines should be indented are added, then we can return errors for 765 # lines should be indented are added, then we can return errors for
742 # [ tokens that are improperly indented. 766 # [ tokens that are improperly indented.
743 # For example: 767 # For example:
744 # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName = 768 # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName =
745 # [a,b,c]; 769 # [a,b,c];
746 # should trigger a proper indentation warning message as [ is not indented 770 # should trigger a proper indentation warning message as [ is not indented
747 # by four spaces. 771 # by four spaces.
748 elif (not token.IsFirstInLine() and token.previous and 772 elif (not token.IsFirstInLine() and token.previous and
749 not token.previous.type in ( 773 token.previous.type not in (
750 [Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] + 774 [Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] +
751 Type.EXPRESSION_ENDER_TYPES)): 775 Type.EXPRESSION_ENDER_TYPES)):
752 self._HandleError(errors.MISSING_SPACE, 'Missing space before "["', 776 self._HandleError(errors.MISSING_SPACE, 'Missing space before "["',
753 token, Position.AtBeginning()) 777 token, position=Position.AtBeginning())
754 778
755 def Finalize(self, state, tokenizer_mode): 779 def Finalize(self, state):
780 """Perform all checks that need to occur after all lines are processed.
781
782 Args:
783 state: State of the parser after parsing all tokens
784
785 Raises:
786 TypeError: If not overridden.
787 """
756 last_non_space_token = state.GetLastNonSpaceToken() 788 last_non_space_token = state.GetLastNonSpaceToken()
757 # Check last line for ending with newline. 789 # Check last line for ending with newline.
758 if state.GetLastLine() and not (state.GetLastLine().isspace() or 790 if state.GetLastLine() and not (
791 state.GetLastLine().isspace() or
759 state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()): 792 state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()):
760 self._HandleError( 793 self._HandleError(
761 errors.FILE_MISSING_NEWLINE, 794 errors.FILE_MISSING_NEWLINE,
762 'File does not end with new line. (%s)' % state.GetLastLine(), 795 'File does not end with new line. (%s)' % state.GetLastLine(),
763 last_non_space_token) 796 last_non_space_token)
764 797
765 # Check that the mode is not mid comment, argument list, etc.
766 if not tokenizer_mode == Modes.TEXT_MODE:
767 self._HandleError(
768 errors.FILE_IN_BLOCK,
769 'File ended in mode "%s".' % tokenizer_mode,
770 last_non_space_token)
771
772 try: 798 try:
773 self._indentation.Finalize() 799 self._indentation.Finalize()
774 except Exception, e: 800 except Exception, e:
775 self._HandleError( 801 self._HandleError(
776 errors.FILE_DOES_NOT_PARSE, 802 errors.FILE_DOES_NOT_PARSE,
777 str(e), 803 str(e),
778 last_non_space_token) 804 last_non_space_token)
779 805
780 def GetLongLineExceptions(self): 806 def GetLongLineExceptions(self):
781 """Gets a list of regexps for lines which can be longer than the limit.""" 807 """Gets a list of regexps for lines which can be longer than the limit.
808
809 Returns:
810 A list of regexps, used as matches (rather than searches).
811 """
782 return [] 812 return []
783 813
784 def InExplicitlyTypedLanguage(self): 814 def InExplicitlyTypedLanguage(self):
785 """Returns whether this ecma implementation is explicitly typed.""" 815 """Returns whether this ecma implementation is explicitly typed."""
786 return False 816 return False
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698