blob: 37057d06ced3ba6d945deaf5be835b90bdb08826 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/computer/computer_closingLabels.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../abstract_context.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ClosingLabelsComputerTest);
});
}
@reflectiveTest
class ClosingLabelsComputerTest extends AbstractContextTest {
late String sourcePath;
@override
void setUp() {
super.setUp();
sourcePath = convertPath('/home/test/lib/test.dart');
}
Future<void> test_adjacentLinesExcluded() async {
var content = '''
void myMethod() {
return /*1*/new Wrapper(
/*2*/new Thing(1,
2)/*2:Thing*/
)/*1:Wrapper*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
/// When constructors span many like this, the node's start position is on the first line
/// of the expression and not where the opening paren is, so this test ensures we
/// don't end up with lots of unwanted labels on each line here.
Future<void> test_chainedConstructorOverManyLines() async {
var content = '''
void f() {
return new thing
.whatIsSplit
.acrossManyLines(1, 2);
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 0);
}
/// When chaining methods like this, the node's start position is on the first line
/// of the expression and not where the opening paren is, so this test ensures we
/// don't end up with lots of unwanted labels on each line here.
Future<void> test_chainedMethodsOverManyLines() async {
var content = '''
List<ClosingLabel> compute() {
_unit.accept(new _DartUnitClosingLabelsComputerVisitor(this));
return _closingLabelsByEndLine.values
.where((l) => l.any((cl) => cl.spannedLines >= 2))
.expand((cls) => cls)
.map((clwlc) => clwlc.label)
.toList();
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 0);
}
Future<void> test_constConstructor() async {
var content = '''
void myMethod() {
return /*1*/new Wrapper(
/*2*/const Class(
1,
2
)/*2:Class*/
)/*1:Wrapper*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_constNamedConstructor() async {
var content = '''
void myMethod() {
return /*1*/new Wrapper(
/*2*/const Class.fromThing(
1,
2
)/*2:Class.fromThing*/
)/*1:Wrapper*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_knownBadCode1() async {
// This code crashed during testing when I accidentally inserted a test snippet.
var content = """
@override
Widget build(BuildContext context) {
new SliverGrid(
gridDelegate: gridDelegate,
delegate: myMethod(<test('', () {
});>[
"a",
'b',
"c",
]),
),
),
],
),
);
}
""";
// TODO(dantup) Results here are currently bad so this test is just checking
// that we don't crash. Need to confirm what to do here; the bad labels
// might not be fixed until the code is using the new shared parser.
// https://github.com/dart-lang/sdk/issues/30370
await _computeElements(content);
}
Future<void> test_labelsShownForMultipleElements() async {
var content = '''
Widget build(BuildContext context) {
return /*1*/new Row(
child: new RaisedButton(),
)/*1:Row*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 1);
}
Future<void> test_labelsShownForMultipleElements_2() async {
var content = '''
Widget build(BuildContext context) {
return /*1*/new Row(
child: /*2*/new RaisedButton(
onPressed: increment,
)/*2:RaisedButton*/,
)/*1:Row*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_listLiterals() async {
var content = '''
void myMethod() {
return /*1*/new Wrapper(
Widget.createWidget(/*2*/<Widget>[
1,
2
]/*2:<Widget>[]*/)
)/*1:Wrapper*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
/// When a line contains the end of a label, we need to ensure we also include any
/// other labels that end on the same line, even if they are 1-2 lines, otherwise
/// it isn't obvious which closing bracket goes with the label.
Future<void> test_mixedLineSpanning() async {
var content = '''
void f() {
/*1*/new Foo((m) {
/*2*/new Bar(
labels,
/*3*/new Padding(
new ClosingLabel(expectedStart, expectedLength, expectedLabel))/*3:Padding*/)/*2:Bar*/;
})/*1:Foo*/;
}
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 3);
}
Future<void> test_multipleNested() async {
var content = """
Widget build(BuildContext context) {
return /*1*/new Row(
children: /*2*/<Widget>[
/*3*/new RaisedButton(
onPressed: increment,
child: /*4*/new Text(
'Increment'
)/*4:Text*/,
)/*3:RaisedButton*/,
_makeWidget(
'a',
'b'
),
new Text('Count: \$counter'),
]/*2:<Widget>[]*/,
)/*1:Row*/;
}
""";
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 4);
}
Future<void> test_newConstructor() async {
var content = '''
void myMethod() {
return /*1*/new Wrapper(
/*2*/new Class(
1,
2
)/*2:Class*/
)/*1:Wrapper*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_newNamedConstructor() async {
var content = '''
void myMethod() {
return /*1*/new Wrapper(
/*2*/new Class.fromThing(
1,
2
)/*2:Class.fromThing*/
)/*1:Wrapper*/;
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_noLabelsForOneElement() async {
var content = '''
Widget build(BuildContext context) {
return new Row(
);
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 0);
}
Future<void> test_NoLabelsFromInterpolatedStrings() async {
var content = """
void f(HighlightRegionType type, int offset, int length) {
/*1*/new Wrapper(
/*2*/new Fail(
'Not expected to find (offset=\$offset; length=\$length; type=\$type) in\\n'
'\${regions.join('\\n')}')/*2:Fail*/
)/*1:Wrapper*/;
}
""";
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_prefixedConstConstructor() async {
var content = """
import 'dart:async' as a;
void myMethod() {
return /*1*/new Wrapper(
/*2*/const a.Future(
1,
2
)/*2:a.Future*/
)/*1:Wrapper*/;
}
""";
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_prefixedConstNamedConstructor() async {
var content = """
import 'dart:async' as a;
void myMethod() {
return /*1*/new Wrapper(
/*2*/const a.Future.delayed(
1,
2
)/*2:a.Future.delayed*/
)/*1:Wrapper*/;
}
""";
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_prefixedNewConstructor() async {
var content = """
import 'dart:async' as a;
void myMethod() {
return /*1*/new Wrapper(
/*2*/new a.Future(
1,
2
)/*2:a.Future*/
)/*1:Wrapper*/;
}
""";
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_prefixedNewNamedConstructor() async {
var content = """
import 'dart:async' as a;
void myMethod() {
return /*1*/new Wrapper(
/*2*/new a.Future.delayed(
1,
2
)/*2:a.Future.delayed*/
)/*1:Wrapper*/;
}
""";
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 2);
}
Future<void> test_sameLineExcluded() async {
var content = '''
void myMethod() {
return new Thing();
}
''';
var labels = await _computeElements(content);
_compareLabels(labels, content, expectedLabelCount: 0);
}
/// Compares provided closing labels with expected
/// labels extracted from the comments in the provided content.
void _compareLabels(List<ClosingLabel> labels, String content,
{int? expectedLabelCount}) {
// Require the test pass us the expected count to guard
// against expected annotations being mistyped and not
// extracted by the regex.
expect(labels, hasLength(expectedLabelCount));
// Find all numeric markers for label starts.
var regex = RegExp('/\\*(\\d+)\\*/');
var expectedLabels = regex.allMatches(content);
// Check we didn't get more than expected, since the loop below only
// checks for the presence of matches, not absence.
expect(labels, hasLength(expectedLabels.length));
// Go through each marker, find the expected label/end and
// ensure it's in the results.
expectedLabels.forEach((m) {
var i = m.group(1);
// Find the end marker.
var endMatch = RegExp('/\\*$i:(.+?)\\*/').firstMatch(content)!;
var expectedStart = m.end;
var expectedLength = endMatch.start - expectedStart;
var expectedLabel = endMatch.group(1)!;
expect(labels,
contains(ClosingLabel(expectedStart, expectedLength, expectedLabel)));
});
}
Future<List<ClosingLabel>> _computeElements(String sourceContent) async {
newFile(sourcePath, content: sourceContent);
var result =
await session.getResolvedUnit2(sourcePath) as ResolvedUnitResult;
var computer = DartUnitClosingLabelsComputer(result.lineInfo, result.unit!);
return computer.compute();
}
}