diff --git a/pkg/analyzer/test/src/dart/parser/top_level_function_test.dart b/pkg/analyzer/test/src/dart/parser/top_level_function_test.dart index 265a4dc5dd98..96fee849a033 100644 --- a/pkg/analyzer/test/src/dart/parser/top_level_function_test.dart +++ b/pkg/analyzer/test/src/dart/parser/top_level_function_test.dart @@ -61,6 +61,77 @@ FunctionDeclaration '''); } + test_recovery_body_issue56355() { + // https://github.com/dart-lang/sdk/issues/56355 + var parseResult = parseStringWithErrors(r''' +void get() { + http.Response response = http +} +'''); + + // Note, there is a cycle that should not be there. + var node = parseResult.findNode.singleFunctionDeclaration; + assertParsedNodeText( + node, + withTokenPreviousNext: true, + withOffsets: true, + r''' +FunctionDeclaration + returnType: NamedType + name: T0 void @0 + next: T1 |get| + name: T1 get @5 + previous: T0 |void| + next: T2 |(| + functionExpression: FunctionExpression + parameters: FormalParameterList + leftParenthesis: T2 ( @8 + previous: T1 |get| + next: T3 |)| + rightParenthesis: T3 ) @9 + previous: T2 |(| + next: T4 |{| + body: BlockFunctionBody + block: Block + leftBracket: T4 { @11 + previous: T3 |)| + next: T5 |http| + statements + ExpressionStatement + expression: PrefixedIdentifier + prefix: SimpleIdentifier + token: T5 http @15 + previous: T4 |{| + next: T6 |.| + period: T6 . @19 + previous: T5 |http| + next: T7 |Response| + identifier: SimpleIdentifier + token: T7 Response @20 + previous: T6 |.| + next: T8 |response| + semicolon: T9 ; @45 + previousX: T10 http @40 + previousX: T11 = @38 + previous: T8 |response| + next: T10 |http| + next: T9 |;| + next: T8 |response| + ExpressionStatement + expression: SimpleIdentifier + token: T8 response @29 + previous: T9 |;| + next: T12 |;| + semicolon: T12 ; @45 + previous: T8 |response| + next: T13 |}| + rightBracket: T13 } @45 + previous: T12 |;| + next: T14 || +''', + ); + } + test_setter_augment() { var parseResult = parseStringWithErrors(r''' augment set foo(int _) {} diff --git a/pkg/analyzer/test/src/diagnostics/parser_diagnostics.dart b/pkg/analyzer/test/src/diagnostics/parser_diagnostics.dart index e9801a90a6a1..69895b22a408 100644 --- a/pkg/analyzer/test/src/diagnostics/parser_diagnostics.dart +++ b/pkg/analyzer/test/src/diagnostics/parser_diagnostics.dart @@ -23,11 +23,13 @@ class ParserDiagnosticsTest { String expected, { bool withCheckingLinking = false, bool withOffsets = false, + bool withTokenPreviousNext = false, }) { var actual = _parsedNodeText( node, withCheckingLinking: withCheckingLinking, withOffsets: withOffsets, + withTokenPreviousNext: withTokenPreviousNext, ); if (actual != expected) { print(actual); @@ -68,6 +70,7 @@ class ParserDiagnosticsTest { AstNode node, { required bool withCheckingLinking, required bool withOffsets, + required bool withTokenPreviousNext, }) { var buffer = StringBuffer(); var sink = TreeStringSink( @@ -83,7 +86,8 @@ class ParserDiagnosticsTest { sink: sink, elementPrinter: elementPrinter, configuration: ResolvedNodeTextConfiguration() - ..withCheckingLinking = withCheckingLinking, + ..withCheckingLinking = withCheckingLinking + ..withTokenPreviousNext = withTokenPreviousNext, withResolution: false, withOffsets: withOffsets, ), diff --git a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart index 6483fdfdb81e..66057c97f1f2 100644 --- a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart +++ b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart @@ -29,6 +29,8 @@ class ResolvedAstPrinter extends ThrowingAstVisitor { /// If `true`, resolution should be printed. final bool _withResolution; + final Map _tokenIdMap = Map.identity(); + ResolvedAstPrinter({ required TreeStringSink sink, required ElementPrinter elementPrinter, @@ -1758,6 +1760,13 @@ Expected parent: (${parent.runtimeType}) $parent } } + String _getTokenId(Token? token) { + if (token == null) { + return ''; + } + return _tokenIdMap[token] ??= 'T${_tokenIdMap.length}'; + } + void _writeDeclaredElement(Element? element) { if (_withResolution) { if (element is LocalVariableElement) { @@ -1973,8 +1982,16 @@ Expected parent: (${parent.runtimeType}) $parent } void _writeToken(String name, Token? token) { - if (token != null) { - _sink.writeWithIndent('$name: '); + if (token == null) { + return; + } + + _sink.writeIndentedLine(() { + _sink.write('$name: '); + if (configuration.withTokenPreviousNext) { + _sink.write(_getTokenId(token)); + _sink.write(' '); + } _sink.write(token.lexeme.ifNotEmptyOrElse('')); if (_withOffsets) { _sink.write(' @${token.offset}'); @@ -1982,7 +1999,32 @@ Expected parent: (${parent.runtimeType}) $parent if (token.isSynthetic) { _sink.write(' '); } - _sink.writeln(); + }); + + if (configuration.withTokenPreviousNext) { + _sink.withIndent(() { + if (token.previous case var previous?) { + if (!previous.isEof) { + if (_tokenIdMap[previous] == null) { + _writeToken('previousX', previous); + } else { + _sink.writelnWithIndent( + 'previous: ${_getTokenId(previous)} |${previous.lexeme}|', + ); + } + } + } else { + _sink.writelnWithIndent('previous: '); + } + + if (token.next case var next?) { + _sink.writelnWithIndent( + 'next: ${_getTokenId(next)} |${next.lexeme}|', + ); + } else { + _sink.writelnWithIndent('next: '); + } + }); } } @@ -2089,4 +2131,7 @@ class ResolvedNodeTextConfiguration { /// If `true`, `redirectedConstructor` properties of [ConstructorElement]s /// should be printer. bool withRedirectedConstructors = false; + + /// If `true`, print IDs of each token, `previous` and `next` tokens. + bool withTokenPreviousNext = false; }