| // 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); | |
| } | |
| } |