f60536c194
Change-Id: Id2734d4c9ce591c5746bb9aa55810c78183db38f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/135940 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
251 lines
7.8 KiB
Dart
251 lines
7.8 KiB
Dart
// 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/token.dart';
|
|
import 'package:analyzer/dart/ast/visitor.dart';
|
|
import 'package:analyzer/source/line_info.dart';
|
|
import 'package:analyzer/src/generated/source.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
|
|
/// A computer for [CompilationUnit] folding.
|
|
class DartUnitFoldingComputer {
|
|
final LineInfo _lineInfo;
|
|
final CompilationUnit _unit;
|
|
|
|
Directive _firstDirective, _lastDirective;
|
|
final List<FoldingRegion> _foldingRegions = [];
|
|
|
|
DartUnitFoldingComputer(this._lineInfo, this._unit);
|
|
|
|
/// Returns a list of folding regions, not `null`.
|
|
List<FoldingRegion> compute() {
|
|
_addFileHeaderRegion();
|
|
_unit.accept(_DartUnitFoldingComputerVisitor(this));
|
|
|
|
if (_firstDirective != null &&
|
|
_lastDirective != null &&
|
|
_firstDirective != _lastDirective) {
|
|
_foldingRegions.add(FoldingRegion(
|
|
FoldingKind.DIRECTIVES,
|
|
_firstDirective.keyword.end,
|
|
_lastDirective.end - _firstDirective.keyword.end));
|
|
}
|
|
|
|
return _foldingRegions;
|
|
}
|
|
|
|
void _addFileHeaderRegion() {
|
|
Token firstToken = _unit.beginToken;
|
|
while (firstToken?.type == TokenType.SCRIPT_TAG) {
|
|
firstToken = firstToken.next;
|
|
}
|
|
|
|
final Token firstComment = firstToken?.precedingComments;
|
|
if (firstComment == null ||
|
|
firstComment.type != TokenType.SINGLE_LINE_COMMENT) {
|
|
return;
|
|
}
|
|
|
|
// Walk through the comments looking for a blank line to signal the end of
|
|
// the file header.
|
|
Token lastComment = firstComment;
|
|
while (lastComment.next != null) {
|
|
lastComment = lastComment.next;
|
|
|
|
// If we ran out of tokens, use the original token as starting position.
|
|
final hasBlankLine =
|
|
_hasBlankLineBetween(lastComment, lastComment.next ?? firstToken);
|
|
|
|
// Also considered non-single-line-comments as the end
|
|
final nextCommentIsDifferentType = lastComment.next != null &&
|
|
lastComment.next.type != TokenType.SINGLE_LINE_COMMENT;
|
|
|
|
if (hasBlankLine || nextCommentIsDifferentType) {
|
|
_addRegion(firstComment.end, lastComment.end, FoldingKind.FILE_HEADER);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _addRegion(int startOffset, int endOffset, FoldingKind kind) {
|
|
final CharacterLocation start = _lineInfo.getLocation(startOffset);
|
|
final CharacterLocation end = _lineInfo.getLocation(endOffset);
|
|
|
|
if (start.lineNumber != end.lineNumber) {
|
|
_foldingRegions
|
|
.add(FoldingRegion(kind, startOffset, endOffset - startOffset));
|
|
}
|
|
}
|
|
|
|
void _addRegionForAnnotations(List<Annotation> annotations) {
|
|
if (annotations.isNotEmpty) {
|
|
_addRegion(annotations.first.name.end, annotations.last.end,
|
|
FoldingKind.ANNOTATIONS);
|
|
}
|
|
}
|
|
|
|
bool _hasBlankLineBetween(Token first, Token second) {
|
|
final CharacterLocation firstLoc = _lineInfo.getLocation(first.end);
|
|
final CharacterLocation secondLoc = _lineInfo.getLocation(second.offset);
|
|
return secondLoc.lineNumber - firstLoc.lineNumber > 1;
|
|
}
|
|
|
|
void _recordDirective(Directive node) {
|
|
_firstDirective ??= node;
|
|
_lastDirective = node;
|
|
}
|
|
}
|
|
|
|
/// An AST visitor for [DartUnitFoldingComputer].
|
|
class _DartUnitFoldingComputerVisitor extends RecursiveAstVisitor<void> {
|
|
final DartUnitFoldingComputer _computer;
|
|
|
|
_DartUnitFoldingComputerVisitor(this._computer);
|
|
|
|
@override
|
|
void visitAssertInitializer(AssertInitializer node) {
|
|
_computer._addRegion(node.leftParenthesis.end, node.rightParenthesis.offset,
|
|
FoldingKind.INVOCATION);
|
|
super.visitAssertInitializer(node);
|
|
}
|
|
|
|
@override
|
|
void visitAssertStatement(AssertStatement node) {
|
|
_computer._addRegion(node.leftParenthesis.end, node.rightParenthesis.offset,
|
|
FoldingKind.INVOCATION);
|
|
super.visitAssertStatement(node);
|
|
}
|
|
|
|
@override
|
|
void visitBlockFunctionBody(BlockFunctionBody node) {
|
|
_computer._addRegion(node.block.leftBracket.end,
|
|
node.block.rightBracket.offset, FoldingKind.FUNCTION_BODY);
|
|
super.visitBlockFunctionBody(node);
|
|
}
|
|
|
|
@override
|
|
void visitClassDeclaration(ClassDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
_computer._addRegion(
|
|
node.leftBracket.end, node.rightBracket.offset, FoldingKind.CLASS_BODY);
|
|
super.visitClassDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitComment(Comment node) {
|
|
if (node.isDocumentation) {
|
|
_computer._addRegion(
|
|
node.offset, node.end, FoldingKind.DOCUMENTATION_COMMENT);
|
|
}
|
|
super.visitComment(node);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorDeclaration(ConstructorDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
super.visitConstructorDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitExportDirective(ExportDirective node) {
|
|
_computer._recordDirective(node);
|
|
super.visitExportDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
_computer._addRegion(
|
|
node.leftBracket.end, node.rightBracket.offset, FoldingKind.CLASS_BODY);
|
|
super.visitExtensionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFieldDeclaration(FieldDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
super.visitFieldDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionDeclaration(FunctionDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
super.visitFunctionDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
|
|
_computer._addRegion(node.argumentList.leftParenthesis.end,
|
|
node.argumentList.rightParenthesis.offset, FoldingKind.INVOCATION);
|
|
super.visitFunctionExpressionInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitImportDirective(ImportDirective node) {
|
|
_computer._recordDirective(node);
|
|
super.visitImportDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceCreationExpression(InstanceCreationExpression node) {
|
|
_computer._addRegion(node.argumentList.leftParenthesis.end,
|
|
node.argumentList.rightParenthesis.offset, FoldingKind.INVOCATION);
|
|
super.visitInstanceCreationExpression(node);
|
|
}
|
|
|
|
@override
|
|
void visitLibraryDirective(LibraryDirective node) {
|
|
_computer._recordDirective(node);
|
|
super.visitLibraryDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitListLiteral(ListLiteral node) {
|
|
_computer._addRegion(
|
|
node.leftBracket.end, node.rightBracket.offset, FoldingKind.LITERAL);
|
|
super.visitListLiteral(node);
|
|
}
|
|
|
|
@override
|
|
void visitMethodDeclaration(MethodDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
super.visitMethodDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitMethodInvocation(MethodInvocation node) {
|
|
_computer._addRegion(node.argumentList.leftParenthesis.end,
|
|
node.argumentList.rightParenthesis.offset, FoldingKind.INVOCATION);
|
|
super.visitMethodInvocation(node);
|
|
}
|
|
|
|
@override
|
|
void visitMixinDeclaration(MixinDeclaration node) {
|
|
_computer._addRegionForAnnotations(node.metadata);
|
|
// TODO(brianwilkerson) Define `FoldingKind.MIXIN_BODY`?
|
|
_computer._addRegion(
|
|
node.leftBracket.end, node.rightBracket.offset, FoldingKind.CLASS_BODY);
|
|
super.visitMixinDeclaration(node);
|
|
}
|
|
|
|
@override
|
|
void visitPartDirective(PartDirective node) {
|
|
_computer._recordDirective(node);
|
|
super.visitPartDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitPartOfDirective(PartOfDirective node) {
|
|
_computer._recordDirective(node);
|
|
super.visitPartOfDirective(node);
|
|
}
|
|
|
|
@override
|
|
void visitSetOrMapLiteral(SetOrMapLiteral node) {
|
|
_computer._addRegion(
|
|
node.leftBracket.end, node.rightBracket.offset, FoldingKind.LITERAL);
|
|
super.visitSetOrMapLiteral(node);
|
|
}
|
|
}
|