| // 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_closing_labels.dart'; | |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; | |
| import 'package:analyzer_testing/src/abstract_context.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| void main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(ClosingLabelsComputerTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class ClosingLabelsComputerTest extends AbstractContextTest { | |
| late String sourcePath; | |
| @override | |
| bool get addFlutterTestPackageDep => true; | |
| @override | |
| void setUp() { | |
| super.setUp(); | |
| sourcePath = convertPath('$testPackageLibPath/test.dart'); | |
| } | |
| Future<void> test_adjacentLinesExcluded() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/new Thing(1, | |
| 2)/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Thing']); | |
| } | |
| /// 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); | |
| } | |
| '''; | |
| await _compareLabels(content, []); | |
| } | |
| /// 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(); | |
| } | |
| '''; | |
| await _compareLabels(content, []); | |
| } | |
| Future<void> test_constructor_const() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/const Class( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Class']); | |
| } | |
| Future<void> test_constructor_named() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/const Class.fromThing( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Class.fromThing']); | |
| } | |
| Future<void> test_constructor_primary() async { | |
| var content = ''' | |
| class Point(var int x, var int y); | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/Point( | |
| 1, | |
| 2, | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Point']); | |
| } | |
| Future<void> test_constructor_primary_named() async { | |
| var content = ''' | |
| class Point.foo(var int x, var int y); | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/Point.foo( | |
| 1, | |
| 2, | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Point.foo']); | |
| } | |
| Future<void> test_groupsAndTests() async { | |
| var content = ''' | |
| import 'package:meta/meta.dart'; | |
| import 'package:flutter_test/flutter_test.dart'; | |
| void main() { | |
| /*[0*/group('my group', () { | |
| /*[1*/group('my nested group', () { | |
| test('my one-line test', () {}); | |
| /*[2*/test('my multiline-line test', () { | |
| // | |
| // | |
| })/*2]*/; | |
| /*[3*/test("my wrapped test with double quotes", () { | |
| // | |
| // | |
| })/*3]*/; | |
| })/*1]*/; | |
| })/*0]*/; | |
| } | |
| @isTest | |
| void wrappedTest(Object description, void Function() body) { | |
| test(description, body); | |
| } | |
| '''; | |
| await _compareLabels(content, [ | |
| "group('my group')", | |
| "group('my nested group')", | |
| "test('my multiline-line test')", | |
| 'test("my wrapped test with double quotes")', | |
| ]); | |
| } | |
| 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 _computeLabels(content); | |
| } | |
| Future<void> test_labelsShownForMultipleElements() async { | |
| var content = ''' | |
| Widget build(BuildContext context) { | |
| return /*[0*/new Row( | |
| child: new RaisedButton(), | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Row']); | |
| } | |
| Future<void> test_labelsShownForMultipleElements_2() async { | |
| var content = ''' | |
| Widget build(BuildContext context) { | |
| return /*[0*/new Row( | |
| child: /*[1*/new RaisedButton( | |
| onPressed: increment, | |
| )/*1]*/, | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Row', 'RaisedButton']); | |
| } | |
| Future<void> test_listLiterals() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| Widget.createWidget(/*[1*/<Widget>[ | |
| 1, | |
| 2 | |
| ]/*1]*/) | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', '<Widget>[]']); | |
| } | |
| /// 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() { | |
| /*[0*/new Foo((m) { | |
| /*[1*/new Bar( | |
| labels, | |
| /*[2*/new Padding( | |
| new ClosingLabel(expectedStart, expectedLength, expectedLabel))/*2]*/)/*1]*/; | |
| })/*0]*/; | |
| } | |
| } | |
| '''; | |
| await _compareLabels(content, ['Foo', 'Bar', 'Padding']); | |
| } | |
| Future<void> test_multipleNested() async { | |
| var content = """ | |
| Widget build(BuildContext context) { | |
| return /*[0*/new Row( | |
| children: /*[1*/<Widget>[ | |
| /*[2*/new RaisedButton( | |
| onPressed: increment, | |
| child: /*[3*/new Text( | |
| 'Increment' | |
| )/*3]*/, | |
| )/*2]*/, | |
| _makeWidget( | |
| 'a', | |
| 'b' | |
| ), | |
| new Text('Count: \$counter'), | |
| ]/*1]*/, | |
| )/*0]*/; | |
| } | |
| """; | |
| await _compareLabels(content, [ | |
| 'Row', | |
| '<Widget>[]', | |
| 'RaisedButton', | |
| 'Text', | |
| ]); | |
| } | |
| Future<void> test_newConstructor() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/new Class( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Class']); | |
| } | |
| Future<void> test_newNamedConstructor() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/new Class.fromThing( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', 'Class.fromThing']); | |
| } | |
| Future<void> test_noLabelsForOneElement() async { | |
| var content = ''' | |
| Widget build(BuildContext context) { | |
| return new Row( | |
| ); | |
| } | |
| '''; | |
| await _compareLabels(content, []); | |
| } | |
| Future<void> test_NoLabelsFromInterpolatedStrings() async { | |
| var content = """ | |
| void f(HighlightRegionType type, int offset, int length) { | |
| /*[0*/new Wrapper( | |
| /*[1*/new Fail( | |
| 'Not expected to find (offset=\$offset; length=\$length; type=\$type) in\\n' | |
| '\${regions.join('\\n')}')/*1]*/ | |
| )/*0]*/; | |
| } | |
| """; | |
| await _compareLabels(content, ['Wrapper', 'Fail']); | |
| } | |
| Future<void> test_nullAwareElement_inList() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| Widget.createWidget(/*[1*/<Widget>[ | |
| 1, | |
| ?null, | |
| 2 | |
| ]/*1]*/) | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', '<Widget>[]']); | |
| } | |
| Future<void> test_nullAwareElement_inList_containingList() async { | |
| var content = ''' | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| Widget.createWidget(/*[1*/<Widget>[ | |
| 1, | |
| ?Widget.createWidget(/*[2*/<Widget>[ | |
| 3 | |
| ]/*2]*/), | |
| 2 | |
| ]/*1]*/) | |
| )/*0]*/; | |
| } | |
| '''; | |
| await _compareLabels(content, ['Wrapper', '<Widget>[]', '<Widget>[]']); | |
| } | |
| Future<void> test_prefixedConstConstructor() async { | |
| var content = """ | |
| import 'dart:async' as a; | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/const a.Future( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| """; | |
| await _compareLabels(content, ['Wrapper', 'a.Future']); | |
| } | |
| Future<void> test_prefixedConstNamedConstructor() async { | |
| var content = """ | |
| import 'dart:async' as a; | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/const a.Future.delayed( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| """; | |
| await _compareLabels(content, ['Wrapper', 'a.Future.delayed']); | |
| } | |
| Future<void> test_prefixedNewConstructor() async { | |
| var content = """ | |
| import 'dart:async' as a; | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/new a.Future( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| """; | |
| await _compareLabels(content, ['Wrapper', 'a.Future']); | |
| } | |
| Future<void> test_prefixedNewNamedConstructor() async { | |
| var content = """ | |
| import 'dart:async' as a; | |
| void myMethod() { | |
| return /*[0*/new Wrapper( | |
| /*[1*/new a.Future.delayed( | |
| 1, | |
| 2 | |
| )/*1]*/ | |
| )/*0]*/; | |
| } | |
| """; | |
| await _compareLabels(content, ['Wrapper', 'a.Future.delayed']); | |
| } | |
| Future<void> test_sameLineExcluded() async { | |
| var content = ''' | |
| void myMethod() { | |
| return new Thing(); | |
| } | |
| '''; | |
| await _compareLabels(content, []); | |
| } | |
| /// Compares provided closing labels with expected | |
| /// labels extracted from the comments in the provided content. | |
| Future<void> _compareLabels(String content, List<String> texts) async { | |
| var testCode = TestCode.parseNormalized(content); | |
| var ranges = testCode.ranges; | |
| expect( | |
| ranges, | |
| hasLength(texts.length), | |
| reason: 'Marked code should have the same number of ranges as the expected label texts', | |
| ); | |
| // Check we got the expected number of labels. | |
| var labels = await _computeLabels(testCode.code); | |
| expect(labels, hasLength(ranges.length)); | |
| // Go through each range and ensure it's in the results with the correct text. | |
| for (var i = 0; i < ranges.length; i++) { | |
| var range = ranges[i].sourceRange; | |
| var expectedLabel = texts[i]; | |
| expect( | |
| labels, | |
| contains(ClosingLabel(range.offset, range.length, expectedLabel)), | |
| ); | |
| } | |
| } | |
| Future<List<ClosingLabel>> _computeLabels(String sourceContent) async { | |
| var file = newFile(sourcePath, sourceContent); | |
| var result = await getResolvedUnit(file); | |
| var computer = DartUnitClosingLabelsComputer(result.lineInfo, result.unit); | |
| return computer.compute(); | |
| } | |
| } |