| Index: third_party/closure_compiler/error_filter.py
|
| diff --git a/third_party/closure_compiler/error_filter.py b/third_party/closure_compiler/error_filter.py
|
| index 54cb3a10abc5e8e7e16a8e0dde50b157ad630ed1..a5a549a69b94abd397b6f77cea400f277e2ffea7 100644
|
| --- a/third_party/closure_compiler/error_filter.py
|
| +++ b/third_party/closure_compiler/error_filter.py
|
| @@ -3,73 +3,49 @@
|
| # found in the LICENSE file.
|
|
|
| """Implement filtering out closure compiler errors due to incorrect type-
|
| -checking on promise chains.
|
| +checking on promise-based return types.
|
|
|
| -For example, this code would raise a compiler error:
|
| +The compiler's type-checker doesn't correctly unwrap Promised return values
|
| +prior to type-checking them. There are a couple of scenarios where this occurs,
|
| +examples can be found below in the code that deals with each specific scenario.
|
|
|
| - Promise.resolve()
|
| - .then(
|
| - /** @return {!Promise<string>} */
|
| - function() { return Promise.resolve('foo'); })
|
| - .then(
|
| - /** @param {string} s */
|
| - function(s) { console.log(s); });
|
| -
|
| -The compiler, while type-checking this code, doesn't unwrap the Promise returned
|
| -by the first callback in the Promise chain. It incorrectly deduces that the
|
| -second function needs to take a {Promise<string>} as an argument, and emits an
|
| -error. This class filters out these types of errors.
|
| +This filtering code applies a set of matchers to the errors that the compiler
|
| +emits. Each matcher fits a known pattern for compiler errors arising from the
|
| +issue described above. If any of the matchers matches an error, that error is
|
| +filtered out of the error list.
|
|
|
| Note that this is just a coarse filter. It doesn't, for example, check that the
|
| unwrapped promise type actually matches the type accepted by the next callback
|
| in the Promise chain. Doing so would be the correct way to fix this problem,
|
| but that fix belongs in the compiler.
|
| +
|
| """
|
|
|
| import re
|
|
|
| +
|
| class PromiseErrorFilter:
|
| """Runs checks to filter out promise chain errors."""
|
| def __init__(self):
|
| - # For the code cited above, the compiler would emit an error like:
|
| - # """
|
| - # ERROR - actual parameter 1 of Promise.prototype.then does not match formal
|
| - # parameter
|
| - # found : function (string): undefined
|
| - # required: (function (Promise<string>): ?|null|undefined)
|
| - # function(s) { console.log(s); });
|
| - # ^
|
| - # """
|
| -
|
| - # Matches the initial error message.
|
| - self._error_msg = ("ERROR - actual parameter 1 of Promise.prototype.then "
|
| - "does not match formal parameter")
|
| -
|
| - # Matches the "found:" line.
|
| - # Examples:
|
| - # - function (string): Promise<string>
|
| - # - function ((SomeType|null)): SomeOtherType
|
| - self._found_line_regex = re.compile("found\s*:\s*function\s*\(.*\):\s*.*")
|
| -
|
| - # Matches the "required:" line.
|
| - # Examples:
|
| - # - (function(Promise<string>): ?|null|undefined)
|
| - self._required_line_regex = re.compile(
|
| - "required:\s*\(function\s*\(Promise<.*>\):\s*.*\)")
|
| + self._allowed_error_patterns = [
|
| + ChainedPromisePattern(),
|
| + ReturnedPromisePattern()
|
| + ]
|
|
|
| def filter(self, error_list):
|
| - """Filters promise chain errors out of the given list.
|
| + """Filters out errors matching any of the allowed patterns.
|
|
|
| Args:
|
| error_list: A list of errors from the closure compiler.
|
|
|
| Return:
|
| - A list of errors, with promise chain errors removed.
|
| + A list of errors, with spurious Promise type errors removed.
|
| """
|
| return [error for error in error_list if not self._should_ignore(error)];
|
|
|
| def _should_ignore(self, error):
|
| - """Should the given error be ignored?
|
| + """Check the given error against all the filters. An error should be
|
| + ignored if it is a match for any of the allowed message patterns.
|
|
|
| Args:
|
| error: A single entry from the closure compiler error list.
|
| @@ -77,12 +53,100 @@ class PromiseErrorFilter:
|
| Return:
|
| True if the error should be ignored, False otherwise.
|
| """
|
| + return any([pattern.match(error)
|
| + for pattern in self._allowed_error_patterns]);
|
| +
|
| +
|
| +class ErrorPattern:
|
| + """A matcher for compiler error messages. This matches compiler type errors,
|
| + which look like:
|
| + # ERROR - <some error message>
|
| + # found : <some type expression>
|
| + # required: <some type expression>
|
| + The message and type expressions are customizable.
|
| + """
|
| + def __init__(self, msg, found_pattern, required_pattern):
|
| + # A string literal that is compared to the first line of the error.
|
| + self._error_msg = msg
|
| + # A regex for matching the found type.
|
| + self._found_line_regex = re.compile("found\s*:\s*" + found_pattern)
|
| + # A regex for matching the required type.
|
| + self._required_line_regex = re.compile("required:\s*" + required_pattern)
|
| +
|
| + def match(self, error):
|
| error_lines = error.split('\n')
|
|
|
| + # Match the error message to see if this pattern applies to the given error.
|
| + # If the error message matches, then compare the found and required lines.
|
| if self._error_msg not in error_lines[0]:
|
| - # Only ignore promise chain errors.
|
| return False
|
| else:
|
| - # Check that the rest of the error message matches the expected form.
|
| return (self._found_line_regex.match(error_lines[1]) and
|
| self._required_line_regex.match(error_lines[2]))
|
| +
|
| +
|
| +class ChainedPromisePattern(ErrorPattern):
|
| + """Matcher for spurious errors arising from chained promises. Example code:
|
| +
|
| + Promise.resolve()
|
| + .then(
|
| + /** @return {!Promise<string>} */
|
| + function() { return Promise.resolve('foo'); })
|
| + .then(
|
| + /** @param {string} s */
|
| + function(s) { console.log(s); });
|
| +
|
| + The compiler will emit an error that looks like
|
| +
|
| + ERROR - actual parameter 1 of Promise.prototype.then does not match formal
|
| + parameter
|
| + found : function (string): undefined
|
| + required: (function (Promise<string>): ?|null|undefined)
|
| + """
|
| + def __init__(self):
|
| + # Matches the initial error message.
|
| + msg = ("ERROR - actual parameter 1 of Promise.prototype.then "
|
| + "does not match formal parameter")
|
| +
|
| + # Examples:
|
| + # - function (string): Promise<string>
|
| + # - function ((SomeType|null)): SomeOtherType
|
| + found_pattern = "function\s*\(.*\):\s*.*"
|
| +
|
| + # Examples:
|
| + # - (function(Promise<string>): ?|null|undefined)
|
| + required_pattern = "\(function\s*\(Promise<.*>\):\s*.*\)"
|
| +
|
| + ErrorPattern.__init__(self, msg, found_pattern, required_pattern)
|
| +
|
| +
|
| +class ReturnedPromisePattern(ErrorPattern):
|
| + """Matcher for spurious errors arising from Promised return values. Example
|
| + code:
|
| +
|
| + /** @return {!Promise<string>} */
|
| + var getStringAsync = function() {
|
| + /** @return {!Promise<string>} */
|
| + var generateString = function() {return Promise.resolve('foo');};
|
| + return Promise.resolve().then(generateString);
|
| + };
|
| +
|
| + The compiler will emit an error that looks like
|
| +
|
| + ERROR - inconsistent return type
|
| + found : Promise<Promise<string>>
|
| + required: Promise<string>
|
| + """
|
| + def __init__(self):
|
| + # Matches the initial error message.
|
| + msg = "ERROR - inconsistent return type"
|
| +
|
| + # Example:
|
| + # - Promise<Promise<string>>
|
| + found_pattern = "Promise<Promise<[^<>]*>"
|
| +
|
| + # Example:
|
| + # - Promise<string>
|
| + required_pattern = "Promise<[^<>]*>"
|
| +
|
| + ErrorPattern.__init__(self, msg, found_pattern, required_pattern)
|
|
|