| // Copyright (c) 2017, 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:analysis_server/protocol/protocol_generated.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/source/line_info.dart'; | |
| /// A computer for [CompilationUnit] closing labels. | |
| class DartUnitClosingLabelsComputer { | |
| final LineInfo _lineInfo; | |
| final CompilationUnit _unit; | |
| final List<ClosingLabel> _closingLabels = []; | |
| final Set<ClosingLabel> hasNestingSet = {}; | |
| final Set<ClosingLabel> isSingleLineSet = {}; | |
| DartUnitClosingLabelsComputer(this._lineInfo, this._unit); | |
| /// Returns a list of closing labels, not `null`. | |
| List<ClosingLabel> compute() { | |
| _unit.accept(_DartUnitClosingLabelsComputerVisitor(this)); | |
| return _closingLabels.where((ClosingLabel label) { | |
| // Filter labels that don't have some nesting. | |
| // Filter labels that start and end on the same line. | |
| return hasNestingSet.contains(label) && !isSingleLineSet.contains(label); | |
| }).toList(); | |
| } | |
| void setHasNesting(ClosingLabel label) { | |
| hasNestingSet.add(label); | |
| } | |
| void setSingleLine(ClosingLabel label) { | |
| isSingleLineSet.add(label); | |
| } | |
| } | |
| /// An AST visitor for [DartUnitClosingLabelsComputer]. | |
| class _DartUnitClosingLabelsComputerVisitor extends RecursiveAstVisitor<void> { | |
| final DartUnitClosingLabelsComputer computer; | |
| int interpolatedStringsEntered = 0; | |
| List<ClosingLabel> labelStack = []; | |
| _DartUnitClosingLabelsComputerVisitor(this.computer); | |
| ClosingLabel? get _currentLabel => | |
| labelStack.isEmpty ? null : labelStack.last; | |
| @override | |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| var labelText = node.constructorName.type.name.name; | |
| var name = node.constructorName.name; | |
| if (name != null) { | |
| labelText += '.${name.name}'; | |
| } | |
| // We override the node used for doing line calculations because otherwise | |
| // constructors that split over multiple lines (but have parens on same | |
| // line) would incorrectly get labels, because node.start on an instance | |
| // creation expression starts at the start of the expression. | |
| var label = _addLabel(node, labelText, checkLinesUsing: node.argumentList); | |
| if (label != null) _pushLabel(label); | |
| try { | |
| super.visitInstanceCreationExpression(node); | |
| } finally { | |
| if (label != null) _popLabel(); | |
| } | |
| } | |
| @override | |
| void visitListLiteral(ListLiteral node) { | |
| var args = node.typeArguments?.arguments; | |
| var typeName = args != null ? args[0].toString() : null; | |
| ClosingLabel? label; | |
| if (typeName != null) { | |
| label = _addLabel(node, '<$typeName>[]'); | |
| } | |
| if (label != null) _pushLabel(label); | |
| try { | |
| super.visitListLiteral(node); | |
| } finally { | |
| if (label != null) _popLabel(); | |
| } | |
| } | |
| @override | |
| void visitStringInterpolation(StringInterpolation node) { | |
| interpolatedStringsEntered++; | |
| try { | |
| super.visitStringInterpolation(node); | |
| } finally { | |
| interpolatedStringsEntered--; | |
| } | |
| } | |
| ClosingLabel? _addLabel(AstNode node, String label, | |
| {AstNode? checkLinesUsing}) { | |
| // Never add labels if we're inside strings. | |
| if (interpolatedStringsEntered > 0) { | |
| return null; | |
| } | |
| checkLinesUsing = checkLinesUsing ?? node; | |
| var start = computer._lineInfo.getLocation(checkLinesUsing.offset); | |
| var end = computer._lineInfo.getLocation(checkLinesUsing.end - 1); | |
| var closingLabel = ClosingLabel(node.offset, node.length, label); | |
| var spannedLines = end.lineNumber - start.lineNumber; | |
| if (spannedLines < 1) { | |
| computer.setSingleLine(closingLabel); | |
| } | |
| var parent = _currentLabel; | |
| if (parent != null) { | |
| computer.setHasNesting(parent); | |
| computer.setHasNesting(closingLabel); | |
| } | |
| computer._closingLabels.add(closingLabel); | |
| return closingLabel; | |
| } | |
| void _popLabel() { | |
| labelStack.removeLast(); | |
| } | |
| void _pushLabel(ClosingLabel label) { | |
| labelStack.add(label); | |
| } | |
| } |