Add a skeleton for Dart Folding
Only currently adds a single region around Directives.
Bug: https://github.com/dart-lang/sdk/issues/33033
Change-Id: Ibde0400c5815c00b1f94bf592d8cc9eb7cb592cf
Reviewed-on: https://dart-review.googlesource.com/54232
Commit-Queue: Danny Tuppeny <dantup@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 240ec38..b838056 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -1275,6 +1275,15 @@
analysisServer, path, result.lineInfo, unit);
});
}
+ // TODO:(dantup) Uncomment this and equivilent in
+ // test/analysis/notification_folding_test.dart once the
+ // implementation is complete.
+ // if (analysisServer._hasAnalysisServiceSubscription(
+ // AnalysisService.FOLDING, path)) {
+ // _runDelayed(() {
+ // sendAnalysisNotificationFolding(analysisServer, path, unit);
+ // });
+ // }
if (analysisServer._hasAnalysisServiceSubscription(
AnalysisService.OUTLINE, path)) {
_runDelayed(() {
diff --git a/pkg/analysis_server/lib/src/computer/computer_folding.dart b/pkg/analysis_server/lib/src/computer/computer_folding.dart
new file mode 100644
index 0000000..19b0c0d
--- /dev/null
+++ b/pkg/analysis_server/lib/src/computer/computer_folding.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, 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:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+
+/**
+ * A computer for [CompilationUnit] folding.
+ */
+class DartUnitFoldingComputer {
+ final CompilationUnit _unit;
+
+ Directive _firstDirective, _lastDirective;
+ final List<FoldingRegion> _foldingRegions = [];
+
+ DartUnitFoldingComputer(this._unit);
+
+ /**
+ * Returns a list of folding regions, not `null`.
+ */
+ List<FoldingRegion> compute() {
+ _unit.accept(new _DartUnitFoldingComputerVisitor(this));
+
+ if (_firstDirective != null &&
+ _lastDirective != null &&
+ _firstDirective != _lastDirective) {
+ _foldingRegions.add(new FoldingRegion(FoldingKind.DIRECTIVES,
+ _firstDirective.offset, _lastDirective.end - _firstDirective.offset));
+ }
+
+ return _foldingRegions;
+ }
+}
+
+/**
+ * An AST visitor for [DartUnitFoldingComputer].
+ */
+class _DartUnitFoldingComputerVisitor extends RecursiveAstVisitor<Object> {
+ final DartUnitFoldingComputer _computer;
+ _DartUnitFoldingComputerVisitor(this._computer);
+
+ @override
+ visitImportDirective(ImportDirective node) {
+ if (_computer._firstDirective == null) {
+ _computer._firstDirective = node;
+ }
+ _computer._lastDirective = node;
+ return super.visitImportDirective(node);
+ }
+}
diff --git a/pkg/analysis_server/lib/src/operation/operation_analysis.dart b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
index 3a9fbc2..c08b148 100644
--- a/pkg/analysis_server/lib/src/operation/operation_analysis.dart
+++ b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
@@ -6,6 +6,7 @@
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/computer/computer_closingLabels.dart';
+import 'package:analysis_server/src/computer/computer_folding.dart';
import 'package:analysis_server/src/computer/computer_highlights.dart';
import 'package:analysis_server/src/computer/computer_highlights2.dart';
import 'package:analysis_server/src/computer/computer_outline.dart';
@@ -80,6 +81,15 @@
});
}
+void sendAnalysisNotificationFolding(
+ AnalysisServer server, String file, CompilationUnit dartUnit) {
+ _sendNotification(server, () {
+ var regions = new DartUnitFoldingComputer(dartUnit).compute();
+ var params = new protocol.AnalysisFoldingParams(file, regions);
+ server.sendNotification(params.toNotification());
+ });
+}
+
void sendAnalysisNotificationFlushResults(
AnalysisServer server, List<String> files) {
_sendNotification(server, () {
diff --git a/pkg/analysis_server/test/analysis/notification_folding_test.dart b/pkg/analysis_server/test/analysis/notification_folding_test.dart
new file mode 100644
index 0000000..51a400b
--- /dev/null
+++ b/pkg/analysis_server/test/analysis/notification_folding_test.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2018, 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.dart';
+import 'package:analysis_server/protocol/protocol_constants.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/protocol_server.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../analysis_abstract.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ // TODO(dantup): Uncomment once implementation is complete.
+ // Cannot just mark the tests as @failingTest as they time out
+ // (no FOLDING notification ever) and failingTest doesn't seem
+ // to cover that.
+ // defineReflectiveTests(_AnalysisNotificationFoldingTest);
+ });
+}
+
+@reflectiveTest
+class _AnalysisNotificationFoldingTest extends AbstractAnalysisTest {
+ static const sampleCode = '''
+import 'dart:async';
+import 'dart:core';
+
+main async() {}
+''';
+
+ static final expectedResults = [
+ new FoldingRegion(FoldingKind.DIRECTIVES, 0, 40)
+ ];
+
+ List<FoldingRegion> lastRegions;
+
+ Completer _regionsReceived;
+
+ void processNotification(Notification notification) {
+ if (notification.event == ANALYSIS_NOTIFICATION_FOLDING) {
+ var params = new AnalysisFoldingParams.fromNotification(notification);
+ if (params.file == testFile) {
+ lastRegions = params.regions;
+ _regionsReceived.complete(null);
+ }
+ } else if (notification.event == SERVER_NOTIFICATION_ERROR) {
+ var params = new ServerErrorParams.fromNotification(notification);
+ throw "${params.message}\n${params.stackTrace}";
+ }
+ }
+
+ @override
+ void setUp() {
+ super.setUp();
+ createProject();
+ }
+
+ void subscribeForFolding() {
+ addAnalysisSubscription(AnalysisService.FOLDING, testFile);
+ }
+
+ test_afterAnalysis() async {
+ addTestFile(sampleCode);
+ await waitForTasksFinished();
+ expect(lastRegions, isNull);
+
+ await waitForFolding(() => subscribeForFolding());
+
+ expect(lastRegions, expectedResults);
+ }
+
+ test_afterUpdate() async {
+ addTestFile('');
+ // Currently required to get notifications on updates
+ setPriorityFiles([testFile]);
+
+ // Before subscribing, we shouldn't have had any folding regions.
+ await waitForTasksFinished();
+ expect(lastRegions, isNull);
+
+ // With no content, there should be zero regions.
+ await waitForFolding(() => subscribeForFolding());
+ expect(lastRegions, hasLength(0));
+
+ // With sample code there will be folding regions.
+ await waitForFolding(() => modifyTestFile(sampleCode));
+
+ expect(lastRegions, expectedResults);
+ }
+
+ Future waitForFolding(action()) {
+ _regionsReceived = new Completer();
+ action();
+ return _regionsReceived.future;
+ }
+}
diff --git a/pkg/analysis_server/test/analysis/test_all.dart b/pkg/analysis_server/test/analysis/test_all.dart
index 76e1f5b..fa72b86 100644
--- a/pkg/analysis_server/test/analysis/test_all.dart
+++ b/pkg/analysis_server/test/analysis/test_all.dart
@@ -13,6 +13,7 @@
as notification_analyzedFiles_test;
import 'notification_closingLabels_test.dart'
as notification_closingLabels_test;
+import 'notification_folding_test.dart' as notification_folding_test;
import 'notification_errors_test.dart' as notification_errors_test;
import 'notification_highlights_test.dart' as notification_highlights_test;
import 'notification_highlights_test2.dart' as notification_highlights_test2;
@@ -36,6 +37,7 @@
notification_analysis_options_test.main();
notification_analyzedFiles_test.main();
notification_closingLabels_test.main();
+ notification_folding_test.main();
notification_errors_test.main();
notification_highlights_test.main();
notification_highlights_test2.main();
diff --git a/pkg/analysis_server/test/src/computer/folding_computer_test.dart b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
new file mode 100644
index 0000000..c9d00a1
--- /dev/null
+++ b/pkg/analysis_server/test/src/computer/folding_computer_test.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2018, 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/src/computer/computer_folding.dart';
+import 'package:analysis_server/src/protocol_server.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(FoldingComputerTest);
+ });
+}
+
+@reflectiveTest
+class FoldingComputerTest extends AbstractContextTest {
+ String sourcePath;
+
+ setUp() {
+ super.setUp();
+ sourcePath = resourceProvider.convertPath('/p/lib/source.dart');
+ }
+
+ test_single_import_directives() async {
+ String content = """
+import 'dart:async';
+
+main() {}
+""";
+
+ // Since there are no region comment markers above
+ final regions = await _computeRegions(content);
+ expect(regions, hasLength(0));
+ }
+
+ test_multiple_import_directives() async {
+ String content = """
+/*1*/import 'dart:async';
+
+// We can have comments
+import 'package:a/b.dart';
+import 'package:b/c.dart';
+
+import '../a.dart';/*1:DIRECTIVES*/
+
+main() {}
+""";
+
+ final regions = await _computeRegions(content);
+ _compareRegions(regions, content);
+ }
+
+ /// Compares provided folding regions with expected
+ /// regions extracted from the comments in the provided content.
+ void _compareRegions(List<FoldingRegion> regions, String content) {
+ // Find all numeric markers for region starts.
+ final regex = new RegExp(r'/\*(\d+)\*/');
+ final expectedRegions = 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(regions, hasLength(expectedRegions.length));
+
+ // Go through each marker, find the expected region start/end and
+ // ensure it's in the results.
+ expectedRegions.forEach((m) {
+ final i = m.group(1);
+ // Find the end marker.
+ final endMatch = new RegExp('/\\*$i:(.+?)\\*/').firstMatch(content);
+
+ final expectedStart = m.end;
+ final expectedLength = endMatch.start - expectedStart;
+ final expectedKindString = endMatch.group(1);
+ final expectedKind = FoldingKind.VALUES
+ .firstWhere((f) => f.toString() == 'FoldingKind.$expectedKindString');
+
+ expect(
+ regions,
+ contains(
+ new FoldingRegion(expectedKind, expectedStart, expectedLength)));
+ });
+ }
+
+ Future<List<FoldingRegion>> _computeRegions(String sourceContent) async {
+ newFile(sourcePath, content: sourceContent);
+ ResolveResult result = await driver.getResult(sourcePath);
+ DartUnitFoldingComputer computer = new DartUnitFoldingComputer(result.unit);
+ return computer.compute();
+ }
+}
diff --git a/pkg/analysis_server/test/src/computer/test_all.dart b/pkg/analysis_server/test/src/computer/test_all.dart
index 4bf0a76..8159db8 100644
--- a/pkg/analysis_server/test/src/computer/test_all.dart
+++ b/pkg/analysis_server/test/src/computer/test_all.dart
@@ -5,6 +5,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'closingLabels_computer_test.dart' as closingLabels_computer_test;
+import 'folding_computer_test.dart' as folding_computer_test;
import 'import_elements_computer_test.dart' as import_elements_computer_test;
import 'imported_elements_computer_test.dart'
as imported_elements_computer_test;
@@ -13,6 +14,7 @@
main() {
defineReflectiveSuite(() {
closingLabels_computer_test.main();
+ folding_computer_test.main();
import_elements_computer_test.main();
imported_elements_computer_test.main();
outline_computer_test.main();