Skip to content

Commit

Permalink
Add a skeleton for Dart Folding
Browse files Browse the repository at this point in the history
Only currently adds a single region around Directives.

Bug: #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>
  • Loading branch information
DanTup authored and commit-bot@chromium.org committed May 8, 2018
1 parent ebcf5be commit b73541c
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 0 deletions.
9 changes: 9 additions & 0 deletions pkg/analysis_server/lib/src/analysis_server.dart
Expand Up @@ -1275,6 +1275,15 @@ class ServerContextManagerCallbacks extends ContextManagerCallbacks {
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(() {
Expand Down
52 changes: 52 additions & 0 deletions 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);
}
}
10 changes: 10 additions & 0 deletions pkg/analysis_server/lib/src/operation/operation_analysis.dart
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';

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';
Expand Down Expand Up @@ -80,6 +81,15 @@ void sendAnalysisNotificationClosingLabels(AnalysisServer server, String file,
});
}

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, () {
Expand Down
100 changes: 100 additions & 0 deletions 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;
}
}
2 changes: 2 additions & 0 deletions pkg/analysis_server/test/analysis/test_all.dart
Expand Up @@ -13,6 +13,7 @@ import 'notification_analyzedFiles_test.dart'
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;
Expand All @@ -36,6 +37,7 @@ main() {
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();
Expand Down
96 changes: 96 additions & 0 deletions 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();
}
}
2 changes: 2 additions & 0 deletions pkg/analysis_server/test/src/computer/test_all.dart
Expand Up @@ -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;
Expand All @@ -13,6 +14,7 @@ import 'outline_computer_test.dart' as outline_computer_test;
main() {
defineReflectiveSuite(() {
closingLabels_computer_test.main();
folding_computer_test.main();
import_elements_computer_test.main();
imported_elements_computer_test.main();
outline_computer_test.main();
Expand Down

0 comments on commit b73541c

Please sign in to comment.