// 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 _foldingRegions = []; DartUnitFoldingComputer(this._lineInfo, this._unit); /** * Returns a list of folding regions, not `null`. */ List compute() { _addFileHeaderRegion(); _unit.accept(new _DartUnitFoldingComputerVisitor(this)); if (_firstDirective != null && _lastDirective != null && _firstDirective != _lastDirective) { _foldingRegions.add(new 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 considerd 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; } } } _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(new FoldingRegion(kind, startOffset, endOffset - startOffset)); } } _addRegionForAnnotations(List 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; } _recordDirective(Directive node) { _firstDirective ??= node; _lastDirective = node; } } /** * An AST visitor for [DartUnitFoldingComputer]. */ class _DartUnitFoldingComputerVisitor extends RecursiveAstVisitor { final DartUnitFoldingComputer _computer; _DartUnitFoldingComputerVisitor(this._computer); @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 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); } }