| OLD | NEW |
| 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 Loading... |
| 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 |
| OLD | NEW |