| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // ignore: deprecated_member_use_from_same_package | |
| import 'package:_fe_analyzer_shared/src/scanner/errors.g.dart'; | |
| import '../messages/codes.dart'; | |
| import 'error_token.dart'; | |
| import 'token.dart' show Token, TokenType; | |
| import 'token_constants.dart'; | |
| // ignore: deprecated_member_use_from_same_package | |
| export 'package:_fe_analyzer_shared/src/scanner/errors.g.dart'; | |
| /** | |
| * Translates the given error [token] into an analyzer error and reports it | |
| * using [reportError]. | |
| */ | |
| void translateErrorToken(ErrorToken token, ReportError reportError) { | |
| int charOffset = token.charOffset; | |
| // TODO(paulberry,ahe): why is endOffset sometimes null? | |
| int endOffset = token.endOffset ?? charOffset; | |
| void _makeError(ScannerErrorCode errorCode, List<Object>? arguments) { | |
| if (_isAtEnd(token, charOffset)) { | |
| // Analyzer never generates an error message past the end of the input, | |
| // since such an error would not be visible in an editor. | |
| // TODO(paulberry,ahe): would it make sense to replicate this behavior | |
| // in cfe, or move it elsewhere in analyzer? | |
| charOffset--; | |
| } | |
| reportError(errorCode, charOffset, arguments); | |
| } | |
| Code<dynamic> errorCode = token.errorCode; | |
| switch (errorCode.analyzerCodes?.first) { | |
| case "UNTERMINATED_STRING_LITERAL": | |
| // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| // string; analyzer expects the end of the string. | |
| reportError( | |
| ScannerErrorCode.UNTERMINATED_STRING_LITERAL, | |
| endOffset - 1, | |
| null, | |
| ); | |
| return; | |
| case "UNTERMINATED_MULTI_LINE_COMMENT": | |
| // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| // comment; analyzer expects the end of the comment. | |
| reportError( | |
| ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT, | |
| endOffset - 1, | |
| null, | |
| ); | |
| return; | |
| case "MISSING_DIGIT": | |
| // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| // number; analyzer expects the end of the number. | |
| charOffset = endOffset - 1; | |
| return _makeError(ScannerErrorCode.MISSING_DIGIT, null); | |
| case "MISSING_HEX_DIGIT": | |
| // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| // number; analyzer expects the end of the number. | |
| charOffset = endOffset - 1; | |
| return _makeError(ScannerErrorCode.MISSING_HEX_DIGIT, null); | |
| case "ILLEGAL_CHARACTER": | |
| // We can safely assume `token.character` is non-`null` because this error | |
| // is only reported when there is a character associated with the token. | |
| return _makeError(ScannerErrorCode.ILLEGAL_CHARACTER, [token.character!]); | |
| case "UNEXPECTED_SEPARATOR_IN_NUMBER": | |
| return _makeError(ScannerErrorCode.UNEXPECTED_SEPARATOR_IN_NUMBER, null); | |
| case "UNSUPPORTED_OPERATOR": | |
| return _makeError(ScannerErrorCode.UNSUPPORTED_OPERATOR, [ | |
| (token as UnsupportedOperator).token.lexeme, | |
| ]); | |
| default: | |
| if (errorCode == codeUnmatchedToken) { | |
| charOffset = token.begin!.endToken!.charOffset; | |
| TokenType type = token.begin!.type; | |
| if (type == TokenType.OPEN_CURLY_BRACKET || | |
| type == TokenType.STRING_INTERPOLATION_EXPRESSION) { | |
| return _makeError(ScannerErrorCode.EXPECTED_TOKEN, ['}']); | |
| } | |
| if (type == TokenType.OPEN_SQUARE_BRACKET) { | |
| return _makeError(ScannerErrorCode.EXPECTED_TOKEN, [']']); | |
| } | |
| if (type == TokenType.OPEN_PAREN) { | |
| return _makeError(ScannerErrorCode.EXPECTED_TOKEN, [')']); | |
| } | |
| if (type == TokenType.LT) { | |
| return _makeError(ScannerErrorCode.EXPECTED_TOKEN, ['>']); | |
| } | |
| } else if (errorCode == codeUnexpectedDollarInString) { | |
| return _makeError(ScannerErrorCode.MISSING_IDENTIFIER, null); | |
| } | |
| throw new UnimplementedError( | |
| '$errorCode "${errorCode.analyzerCodes?.first}"', | |
| ); | |
| } | |
| } | |
| /** | |
| * Determines whether the given [charOffset], which came from the non-EOF token | |
| * [token], represents the end of the input. | |
| */ | |
| bool _isAtEnd(Token token, int charOffset) { | |
| while (true) { | |
| // Skip to the next token. | |
| token = token.next!; | |
| // If we've found an EOF token, its charOffset indicates where the end of | |
| // the input is. | |
| if (token.isEof) return token.charOffset == charOffset; | |
| // If we've found a non-error token, then we know there is additional input | |
| // text after [charOffset]. | |
| if (token.type.kind != BAD_INPUT_TOKEN) return false; | |
| // Otherwise keep looking. | |
| } | |
| } | |
| /** | |
| * Used to report a scan error at the given offset. | |
| * The [errorCode] is the error code indicating the nature of the error. | |
| * The [arguments] are any arguments needed to complete the error message. | |
| */ | |
| typedef ReportError( | |
| ScannerErrorCode errorCode, | |
| int offset, | |
| List<Object>? arguments, | |
| ); |