Linter Demo Errors: 0Warnings: 1File: /home/fstrocco/Dart/dart/benchmark/dartstyle/lib/src/line_writer.dart // Copyright (c) 2014, 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. library dart_style.src.line_writer; import 'chunk.dart'; import 'dart_formatter.dart'; import 'debug.dart' as debug; import 'line_splitting/line_splitter.dart'; import 'whitespace.dart'; /// Given a series of chunks, splits them into lines and writes the result to /// a buffer. class LineWriter { final _buffer = new StringBuffer(); final List _chunks; final String _lineEnding; /// The number of characters allowed in a single line. final int pageWidth; /// The number of characters of additional indentation to apply to each line. /// /// This is used when formatting blocks to get the output into the right /// column based on where the block appears. final int _blockIndentation; /// The cache of blocks that have already been formatted. final Map<_BlockKey, FormatResult> _blockCache; /// The offset in [_buffer] where the selection starts in the formatted code. /// /// This will be `null` if there is no selection or the writer hasn't reached /// the beginning of the selection yet. int _selectionStart; /// The offset in [_buffer] where the selection ends in the formatted code. /// /// This will be `null` if there is no selection or the writer hasn't reached /// the end of the selection yet. int _selectionEnd; /// The number of characters that have been written to the output. int get length => _buffer.length; LineWriter(DartFormatter formatter, this._chunks) : _lineEnding = formatter.lineEnding, pageWidth = formatter.pageWidth, _blockIndentation = 0, _blockCache = {}; /// Creates a line writer for a block. LineWriter._(this._chunks, this._lineEnding, this.pageWidth, this._blockIndentation, this._blockCache) { // There is always a newline after the opening delimiter. _buffer.write(_lineEnding); } /// Gets the results of formatting the child block of [chunk] at with /// starting [column]. /// /// If that block has already been formatted, reuses the results. /// /// The column is the column for the delimiters. The contents of the block /// are always implicitly one level deeper than that. /// /// main() { /// function(() { /// block; /// }); /// } /// /// When we format the anonymous lambda, [column] will be 2, not 4. FormatResult formatBlock(Chunk chunk, int column) { var key = new _BlockKey(chunk, column); // Use the cached one if we have it. var cached = _blockCache[key]; if (cached != null) return cached; var writer = new LineWriter._( chunk.blockChunks, _lineEnding, pageWidth, column, _blockCache); // TODO(rnystrom): Passing in an initial indent here is hacky. The // LineWriter ensures all but the first chunk have a block indent, and this // handles the first chunk. Do something cleaner. var result = writer.writeLines(Indent.block, flushLeft: chunk.flushLeft); return _blockCache[key] = result; } /// Takes all of the chunks and divides them into sublists and line splits /// each list. /// /// Since this is linear and line splitting is worse it's good to feed the /// line splitter smaller lists of chunks when possible. FormatResult writeLines(int firstLineIndent, {bool isCompilationUnit: false, bool flushLeft: false}) { // Now that we know what hard splits there will be, break the chunks into // independently splittable lines. var newlines = 0; var indent = firstLineIndent; var totalCost = 0; var start = 0; for (var i = 0; i < _chunks.length; i++) { var chunk = _chunks[i]; if (!chunk.canDivide) continue; totalCost += _completeLine(newlines, indent, start, i + 1, flushLeft: flushLeft); // Get ready for the next line. newlines = chunk.isDouble ? 2 : 1; indent = chunk.indent; flushLeft = chunk.flushLeft; start = i + 1; } if (start < _chunks.length) { totalCost += _completeLine(newlines, indent, start, _chunks.length, flushLeft: flushLeft); } // Be a good citizen, end with a newline. if (isCompilationUnit) _buffer.write(_lineEnding); return new FormatResult( _buffer.toString(), totalCost, _selectionStart, _selectionEnd); } /// Takes the first [length] of the chunks with leading [indent], removes /// them, and runs the [LineSplitter] on them. int _completeLine(int newlines, int indent, int start, int end, {bool flushLeft}) { // Write the newlines required by the previous line. for (var j = 0; j < newlines; j++) { _buffer.write(_lineEnding); } var chunks = _chunks.sublist(start, end); if (debug.traceLineWriter) { debug.log(debug.green("\nWriting:")); debug.dumpChunks(start, chunks); debug.log(); } // Run the line splitter. var splitter = new LineSplitter(this, chunks, _blockIndentation, indent, flushLeft: flushLeft); var splits = splitter.apply(); // Write the indentation of the first line. if (!flushLeft) { _buffer.write(" " * (indent + _blockIndentation)); } // Write each chunk with the appropriate splits between them. for (var i = 0; i < chunks.length; i++) { var chunk = chunks[i]; _writeChunk(chunk); if (chunk.blockChunks.isNotEmpty) { if (!splits.shouldSplitAt(i)) { // This block didn't split (which implies none of the child blocks // of that block split either, recursively), so write them all inline. _writeChunksUnsplit(chunk.blockChunks); } else { // Include the formatted block contents. var block = formatBlock(chunk, splits.getColumn(i)); // If this block contains one of the selection markers, tell the // writer where it ended up in the final output. if (block.selectionStart != null) { _selectionStart = length + block.selectionStart; } if (block.selectionEnd != null) { _selectionEnd = length + block.selectionEnd; } _buffer.write(block.text); } } if (i == chunks.length - 1) { // Don't write trailing whitespace after the last chunk. } else if (splits.shouldSplitAt(i)) { _buffer.write(_lineEnding); if (chunk.isDouble) _buffer.write(_lineEnding); _buffer.write(" " * (splits.getColumn(i))); } else { if (chunk.spaceWhenUnsplit) _buffer.write(" "); } } return splits.cost; } /// Writes [chunks] (and any child chunks of them, recursively) without any /// splitting. void _writeChunksUnsplit(List chunks) { for (var chunk in chunks) { _writeChunk(chunk); if (chunk.spaceWhenUnsplit) _buffer.write(" "); // Recurse into the block. _writeChunksUnsplit(chunk.blockChunks); } } /// Writes [chunk] to the output and updates the selection if the chunk /// contains a selection marker. void _writeChunk(Chunk chunk) { if (chunk.selectionStart != null) { _selectionStart = length + chunk.selectionStart; } if (chunk.selectionEnd != null) { _selectionEnd = length + chunk.selectionEnd; } _buffer.write(chunk.text); } } /// Key type for the formatted block cache. /// /// To cache formatted blocks, we just need to know which block it is (the /// index of its parent chunk) and how far it was indented when we formatted it /// (the starting column). class _BlockKey { /// The index of the chunk in the surrounding chunk list that contains this /// block. final Chunk chunk; /// The absolute zero-based column number where the block starts. final int column; _BlockKey(this.chunk, this.column); bool operator ==(other) { if (other is! _BlockKey) return false; return chunk == other.chunk && column == other.column; } int get hashCode => chunk.hashCode ^ column.hashCode; } /// The result of formatting a series of chunks. class FormatResult { /// The resulting formatted text, including newlines and leading whitespace /// to reach the proper column. final String text; /// The numeric cost of the chosen solution. final int cost; /// Where in the resulting buffer the selection starting point should appear /// if it was contained within this split list of chunks. /// /// Otherwise, this is `null`. final int selectionStart; /// Where in the resulting buffer the selection end point should appear if it /// was contained within this split list of chunks. /// /// Otherwise, this is `null`. final int selectionEnd; FormatResult(this.text, this.cost, this.selectionStart, this.selectionEnd); }