| // 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 'dart:async'; | |
| 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'; | |
| main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(ClosingLabelsComputerTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class ClosingLabelsComputerTest extends AbstractContextTest { | |
| String sourcePath; | |
| setUp() { | |
| super.setUp(); | |
| sourcePath = resourceProvider.convertPath('/p/lib/source.dart'); | |
| } | |
| test_adjacentLinesExcluded() async { | |
| String 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. | |
| test_chainedConstructorOverManyLines() async { | |
| String content = """ | |
| main() { | |
| 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. | |
| test_chainedMethodsOverManyLines() async { | |
| String 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); | |
| } | |
| test_constConstructor() async { | |
| String 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); | |
| } | |
| test_constNamedConstructor() async { | |
| String 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); | |
| } | |
| test_knownBadCode1() async { | |
| // This code crashed during testing when I accidentally inserted a test snippet. | |
| String 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); | |
| } | |
| test_labelsShownForMultipleElements() async { | |
| String content = """ | |
| Widget build(BuildContext context) { | |
| return /*1*/new Row( | |
| child: new RaisedButton(), | |
| )/*1:Row*/; | |
| } | |
| """; | |
| var labels = await _computeElements(content); | |
| _compareLabels(labels, content, expectedLabelCount: 1); | |
| } | |
| test_labelsShownForMultipleElements_2() async { | |
| String 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); | |
| } | |
| test_listLiterals() async { | |
| String 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. | |
| test_mixedLineSpanning() async { | |
| String content = """ | |
| main() { | |
| /*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); | |
| } | |
| test_multipleNested() async { | |
| String 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); | |
| } | |
| test_newConstructor() async { | |
| String 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); | |
| } | |
| test_newNamedConstructor() async { | |
| String 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); | |
| } | |
| test_noLabelsForOneElement() async { | |
| String content = """ | |
| Widget build(BuildContext context) { | |
| return new Row( | |
| ); | |
| } | |
| """; | |
| var labels = await _computeElements(content); | |
| _compareLabels(labels, content, expectedLabelCount: 0); | |
| } | |
| test_NoLabelsFromInterpolatedStrings() async { | |
| String content = """ | |
| void main(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); | |
| } | |
| test_prefixedConstConstructor() async { | |
| String 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); | |
| } | |
| test_prefixedConstNamedConstructor() async { | |
| String 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); | |
| } | |
| test_prefixedNewConstructor() async { | |
| String 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); | |
| } | |
| test_prefixedNewNamedConstructor() async { | |
| String 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); | |
| } | |
| test_sameLineExcluded() async { | |
| String 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 = new 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 = new RegExp("/\\*$i:(.+?)\\*/").firstMatch(content); | |
| var expectedStart = m.end; | |
| var expectedLength = endMatch.start - expectedStart; | |
| var expectedLabel = endMatch.group(1); | |
| expect( | |
| labels, | |
| contains( | |
| new ClosingLabel(expectedStart, expectedLength, expectedLabel))); | |
| }); | |
| } | |
| Future<List<ClosingLabel>> _computeElements(String sourceContent) async { | |
| newFile(sourcePath, content: sourceContent); | |
| ResolveResult result = await driver.getResult(sourcePath); | |
| DartUnitClosingLabelsComputer computer = | |
| new DartUnitClosingLabelsComputer(result.lineInfo, result.unit); | |
| return computer.compute(); | |
| } | |
| } |