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